Skip to content

Security Model

josh goble edited this page Aug 16, 2026 · 1 revision

Security model

What Keelarr reads, what it writes, and what it deliberately never touches.

Access to the controller

A password is required before anything works. Minimum eight characters, hashed with scrypt, stored in auth.json with mode 0600.

Sessions are HMAC tokens over the issue time, in a SameSite=Lax cookie. Comparison is constant-time. Signing out or restarting the controller ends them.

/api/health is deliberately outside the gate — it is what the container healthcheck probes, and it reports only that the process is answering.

Everything else under /api/* returns 401 without a session. Verified from the host and from another machine on the network.

The Docker socket

Keelarr mounts /var/run/docker.sock. This is root-equivalent access to the host, and it is worth understanding that plainly: anything that can drive the Docker socket can start a privileged container.

That is inherent to the job — a tool that manages your containers has to be able to manage your containers. What follows from it is that the controller should not be exposed to the internet. Put it on your LAN, or behind a VPN or authenticating reverse proxy. The password is there to stop a curious housemate, not a determined attacker with network access.

API keys

Keelarr reads each app's API key from its config file, at the moment it is needed, and:

  • never persists it — not in settings, not in a cache
  • never logs it
  • never returns it from the API

What the API returns is the source path it was read from and a truncated sha256 fingerprint. A test asserts that no 32-character hex string appears in any response body.

Keelarr also never generates an app's API key. It reads what the app made.

Command output

Reading an app's configuration returns its secrets — an Arr's API key, SABnzbd's Usenet username and password in cleartext, Tautulli's Plex token.

The logger redacts sensitive keys in structured context, but a command's output is one opaque string and passed straight through. At debug level, every one of those secrets was landing in data/keelarr.log and in docker logs.

Config reads are now marked sensitive, and the log records byte counts instead of contents. Verified on a live NAS.

If you ran an early version at debug level, your log may contain secrets. Check it, and rotate anything you find.

Indexers

Counted, never read or written. They carry credentials you paid for, and Keelarr has no business in them.

The prerequisite check reports how many indexers Prowlarr has, because zero means nothing in the stack can find releases. It never looks at what they are.

Existing configuration

Never overwritten:

  • A download client pointing somewhere unexpected is reported as drift and left exactly as it is
  • Two candidates and no clear match is reported as ambiguous and left alone
  • Only a genuinely absent connection is written

App databases

Moved, never edited. Config snapshots are tarballs — a directory in, a directory out. Nothing parses another app's schema, so nothing breaks on their next migration.

This is the line between "restore this app's own file for it" and "write into this app's database". The first is safe; the second means Keelarr owns the corruption when the app next migrates.

What Keelarr sends anywhere

Nothing. There is no telemetry, no phone-home, no update ping to anything except the container registries it pulls images from.

Secrets in requests

SABnzbd's API key is sent in a POST body rather than a query string — a key in a URL ends up in the app's own access log.

Reporting a vulnerability

Open an issue for anything non-sensitive. For something you would rather not post publicly, say so in an issue without details and a private channel can be arranged.

Clone this wiki locally