Security model and hardening¶
BitAgent's HTTP server has no opinion about authentication. It trusts whoever connects. Every security control in this document is either an env var that BitAgent does honour, or a network/proxy layer that you put in front of it.
This page is the operator's reference for how to deploy BitAgent without making yourself the next obvious target.
Threat model¶
In scope. A self-hoster's deployment exposed via a single host with port 3333 reachable. The operator's IP visible on the public BitTorrent DHT. An open /torznab/api endpoint. Misconfigured Postgres password. Loss/leak of TORZNAB_API_KEY or EVIDENCE_WEBHOOK_SECRET.
Out of scope. Zero-day vulnerabilities in Postgres or the Go runtime. Supply-chain compromise of a base image. Kernel-level exploits or container escape. Compromise of upstream *arr services (BitAgent trusts whatever data they send).
The single load-bearing assumption is that the host running BitAgent is trusted. There is no internal mTLS between BitAgent and Postgres in the bundled stack.
Network attack surface¶
The default examples/docker-compose.public.yml exposes port 3333 directly. All of these paths answer on it:
| Path | Auth (default) | Sensitive? |
|---|---|---|
/graphql |
none | Yes — full mutation surface |
/torznab/api |
none — gates on TORZNAB_API_KEY if set |
Yes if *arr uses it |
/metrics |
none | Information disclosure (DB sizes, infohash counts) |
/import |
none | Yes — accepts NDJSON bulk-import |
/evidence/arr/<instance> |
gates on X-Evidence-Token matching EVIDENCE_WEBHOOK_SECRET |
Yes |
Do not expose port 3333 directly to the internet. Always front it with a reverse proxy that adds auth.
Authentication tiers¶
Three deployment shapes, in increasing order of exposure tolerance.
Tier 1 — open (default)¶
Use only on a tailnet, ZeroTier, LAN, or container-network-only deployment. BitAgent accepts all requests. Network isolation is the entire control.
Tier 2 — API key¶
TORZNAB_API_KEY— gates/torznab/api(constant-time compare; timing-safe). Required when the*arrclients are on a different host or network than BitAgent.EVIDENCE_WEBHOOK_SECRET— gates/evidence/arr/*(compared against theX-Evidence-Tokenheader on each webhook).
Generate keys with at least 256 bits of entropy:
bash
openssl rand -hex 32text
Both keys are read at process startup. Rotation requires a process restart; see Key rotation below.
Tier 3 — reverse-proxy auth¶
For public-internet exposure. Use Caddy, Traefik, NPM, Authelia, or Cloudflare Access in front. The proxy does the auth, BitAgent trusts whatever traffic the proxy forwards.
Defense-in-depth: keep TORZNAB_API_KEY and EVIDENCE_WEBHOOK_SECRET set even when the proxy is doing the heavy lifting. A misconfigured proxy that silently lets traffic through still hits a 401 from BitAgent if the keys aren't there.
DHT and IP exposure¶
BitAgent participates in the public BitTorrent DHT. There is no setting that disables this; it is what BitAgent fundamentally does. Consequence:
- Your IP is published to the global DHT routing table.
- During BEP-9 metainfo fetch, your IP appears in swarm peer lists.
- Peer-tracking services (e.g. "I know what you download") observe and record this.
BitAgent does not download torrent payloads — only metadata. The exposure window is bounded to the metadata phase. Still, for any deployment where IP exposure is a concern, the standard mitigation is:
- Run BitAgent behind a VPN tunnel (Gluetun is what the advanced stack uses).
- Use a VPN provider that supports port forwarding so DHT inbound traffic gets symmetric reach.
CSAM — the one category where post-fetch detection is too late — has its own pre-fetch double-hash defense. See concepts/csam-defense.md.
CSAM defense¶
Briefly: a pre-fetch double-hashed bloom filter rejects known-CSAM infohashes before BitAgent ever opens a TCP connection to a peer. Defaults are safe (enabled, no feeds = NoOp). Layered architecture in concepts/csam-defense.md.
The post-classify regex (keywords.banned) provides a second layer for any infohash that slips past the pre-fetch filter. The self-export pipeline contributes back to community feeds, so each operator's observations harden the pre-fetch defense for the next operator.
Database hardening¶
POSTGRES_PASSWORD is the only thing standing between an attacker on the host and your indexed corpus.
- Generate it with
openssl rand -hex 32. Avoid passwords with shell-special characters. - Never commit
.envfiles to git. Add them to.gitignorefrom day one. - Rotate immediately on any suspected leak.
- The default Postgres user is
bitmagnetwith full database privileges on thebitmagnetdatabase. Bind to localhost or the docker compose internal network — do not expose5432externally.
For the bundled compose stack, Postgres is on the internal docker network only and is not reachable from the host's external interfaces.
Reverse-proxy recipe¶
Caddy template for a public deployment with mixed auth:
```caddyfile bitagent.example.com { # /torznab/api: gated on TORZNAB_API_KEY only — arr clients hit it reverse_proxy /torznab/ bitagent:3333
# /evidence/arr/: gated on EVIDENCE_WEBHOOK_SECRET only reverse_proxy /evidence/ bitagent:3333
# Everything else: forward_auth or basic_auth
@internal {
not path /torznab/ /evidence/
}
basic_auth @internal {
admin ``text
Generate the bcrypt hash withcaddy hash-password`. Use a 12+ character random password.
Webhook auth (X-Evidence-Token)¶
Every *arr Connect → Webhook entry needs the X-Evidence-Token Custom Header set to the same value as EVIDENCE_WEBHOOK_SECRET. The webhook URL pattern is:
text
http://<bitagent-host>:3333/evidence/arr/<instance-label>text
<instance-label> is operator-chosen — it ends up in the label_evidence.source_instance column for traceability (e.g. sonarr-main, sonarr-anime, radarr-4k).
Mismatched or missing token → request rejected, no row written, increments bitagent_evidence_events_rejected_total.
Key rotation¶
TORZNAB_API_KEY¶
- Generate the new key:
openssl rand -hex 32. - Update
TORZNAB_API_KEYin the BitAgent env (.envfile or secrets store), restart the BitAgent container. - Update each
*arrindexer's API Key field to the new value. - Test each indexer connection.
- The old key is now dead — no need to retire it explicitly.
EVIDENCE_WEBHOOK_SECRET¶
Mirroring matters here — webhook signal is dropped during the gap.
- Generate the new value.
- Update each
*arrConnect → Webhook entry'sX-Evidence-Tokento the new value first. - Update
EVIDENCE_WEBHOOK_SECRETin BitAgent's env and restart. - The webhook ingestor will reject any in-flight
*arrretries that still carry the old token —*arrwill retry with the new token, and the data eventually lands.
For zero gap, deploy a forward-compatible BitAgent build that accepts both old and new tokens for the rotation window — not currently shipped; ask in Discussions if you need it.
Logging and audit¶
LOG_LEVEL=info is the production default and captures auth failures, webhook rejection, and CSAM-defense activity. LOG_LEVEL=debug captures every Torznab query — useful for incident response, very loud.
All logs go to stdout. No telemetry, no upstream phone-home — except CSAM_BLOCKLIST_EXPORT_UPSTREAM_URL if you opt in.
Container hardening¶
The bundled Dockerfile already does most of what you'd want:
- Runs as a non-root user. Do not override
USERin your compose. - The image is small (Alpine base, multi-stage build).
- No build tooling in the runtime layer.
If you want to harden further:
read_only: truein compose. BitAgent doesn't need a writable rootfs at runtime.cap_drop: [ALL]. The only capability that might be needed isCAP_NET_BIND_SERVICE, and only if you've setBITAGENT_PEER_PORTbelow1024. Keep it ≥1024and you can drop everything.tmpfson/tmpif you need any writes at all.- Mount Postgres data on a dedicated volume; never bind-mount the database directory from the host filesystem (file-permission drift).
Vulnerability reporting¶
Report security issues via the repo's SECURITY.md — don't open a public issue for unpatched vulnerabilities. The maintainer triages on a best-effort basis; there is no formal SLA or bounty.