Scared to torrent due to security risks? Worry no more (or at least worry less)! Protectarr sits between your machine and torrent downloads to add another layer of protection against suspicious or malicious releases. Huge shutout to Claude for helping where my coding skills are lacking . After a recent scare in my own *arr stack, where suspicious files were downloaded onto my homelab, I felt the sooner something like this existed, the better.
This project is completely open source, so feel free to make changes, contribute, or create your own version. In a world where everything seems to require a subscription, owning and managing your own media is becoming increasingly difficult. I hope Protectarr helps make homelabbing and self-hosting your own media a little safer and gives people some extra peace of mind.
Protectarr is designed to detect and stop suspicious or malicious torrent releases before they reach your media library. It sits beside qBittorrent and your *arr apps (Sonarr, Radarr, Lidarr, Readarr, with Prowlarr for indexer reports), inspects the downloads they grab, and has the *arr app blocklist bad releases and search for a different one.
It's aimed at the fake releases that turn up on public indexers: a "new episode" that is really Show.S01E01.mkv.exe, a movie that's a 2 MB .wmv asking you to download a codec, or a password-protected archive with a "get the password here" link.
Before anything downloads. As soon as qBittorrent knows a torrent's file list, Protectarr checks names and sizes for:
- program files:
.exe,.scr,.lnk,.bat,.msi,.ps1,.jsand more - disguised names such as
Movie.mkv.exe, and the hidden right-to-left override character that makes a program's name display like a video's - archives and disk images where a video or album should be
- lure files such as
password.txt,codecinstallers and.urllinks - WMV/ASF "codec" bait
- videos far too small for what they claim to be (an episode under 30 MB, a movie under 300 MB)
While it downloads, and before import. Protectarr reads each file's first bytes as soon as they arrive to find its real type, so a Windows program renamed .mkv is caught. It lists archives without extracting them (and flags password protected ones), and sends every non media file to ClamAV.
Each *arr app gets rules that fit what it downloads. Lidarr releases may contain .cue, .log and booklet PDFs, and Readarr releases may contain EPUB, PDF and audiobook files, so neither is mistaken for a fake video.
| Verdict | Examples | Default action |
|---|---|---|
| Malicious | a program, a disguised program, a password-protected archive, a ClamAV detection | Block: stop the torrent, move any files into a read-only quarantine, remove it from the *arr queue with blocklisting on (so it searches for another release) |
| Suspicious | a WMV file, an archive, a video far too small, a file that isn't what its name says | Hold: stop the torrent, lock away any files so nothing imports them, and wait for you to Allow or Deny it on the Review page |
| Clean | a normal release | Nothing. If qBittorrent paused it after metadata, Protectarr starts it again |
You can change each action to block or hold.
qBittorrent and the *arr apps work as usual. Protectarr watches them through their APIs, checks downloads before they can be imported, and steps in only when something looks wrong. Each download goes through three checkpoints (file list, while downloading, finished files) before an *arr app can import it. docs/architecture.md walks through a download step by step.
Protectarr is a single container. You run qBittorrent (4.5 or later, 5.x recommended), your *arr apps and ClamAV however you already do, and connect them in Protectarr's web UI.
-
Start Protectarr with
docker-compose.yml, or:mkdir -p protectarr/config protectarr/quarantine docker run -d --name protectarr -p 9797:9797 --user 1000:1000 \ -v ./protectarr/config:/config -v ./protectarr/quarantine:/quarantine \ -v /path/to/downloads:/downloads \ ghcr.io/ablatnick/protectarr:latestYour login: on first start Protectarr generates a password for the user
adminand prints it once in the container log (docker logs protectarr). Set your own on the Settings page straight away. To choose it up front instead, setPROTECTARR_PASSWORD(and optionallyPROTECTARR_USERNAME) before the first start.Three things matter:
- Create the
configandquarantinefolders yourself first, as above. If Docker creates them, they belong to root and Protectarr can't write to them. --usermust match qBittorrent'sPUID:PGID, so Protectarr can move files into quarantine.- Mount your downloads folder at the same path qBittorrent uses (for example
/downloadsor/data).
- Create the
-
Start ClamAV if you don't run it already (optional, but recommended):
docker run -d --name clamav \ -p 127.0.0.1:3310:3310 \ -v ./clamav:/var/lib/clamav \ clamav/clamav:stableIt needs a few minutes to download its signatures on first start.
clamd has no authentication, so the command above binds its port to localhost only. Don't expose port 3310 beyond your host. Better still, put ClamAV and Protectarr on the same Docker network and don't publish the port at all — Protectarr then reaches it as
clamav:3310, as inconfig.example.yml. -
Open
http://your-server:9797/settings(useradminand the password from the log, or yourPROTECTARR_PASSWORD), set your own login under Login, and enter:- qBittorrent's address, username and password,
- each *arr app's address and API key (Settings > General > Security in that app),
- ClamAV's host and port (3310), and Prowlarr if you want indexer reports.
Addresses can be
your-server-ip:8989or a full URL. Click Save and test: every service shows Connected or tells you what to fix, and the category table lists what's being watched. Changes apply immediately, with no restart. -
In qBittorrent, set Options > Downloads > Torrent stop condition to Metadata received. New torrents then wait until Protectarr has checked their file list, and Protectarr starts them if they pass. That setting applies to every torrent, so Protectarr also starts new torrents in categories it doesn't check; torrents you stopped yourself are never touched.
The Settings page shows a Getting started checklist until these steps are done.
You don't list categories anywhere: Protectarr reads them from each *arr app's qBittorrent download client, and picks up changes within 10 minutes. Torrents in other categories are left alone, and nothing is checked until at least one *arr app is connected.
On its first connection Protectarr leaves torrents that had already finished alone, and only checks new ones.
| Service | What you get |
|---|---|
| qBittorrent | Required. Protectarr watches it, stops bad torrents and restarts clean ones |
| Sonarr, Radarr | Their categories are checked with TV or movie rules; bad releases are blocklisted there and re-searched |
| Lidarr | Music rules for its category (cue sheets, rip logs and booklets are fine) |
| Readarr (or a fork such as Bookshelf) | Book and audiobook rules for its category |
| A second instance (4K, anime…) | Add it as another row and give it a name like "Radarr 4K" |
| Prowlarr | Links and indexer status on the Indexers page, which shows which indexers send bad releases |
| ClamAV | Signature scanning of every non-media file |
The Indexers page works without Prowlarr too: the indexer comes from the *arr app that grabbed the release.
ClamAV can be any clamd reachable over TCP, including one you already run. Protectarr streams files to it (INSTREAM), so ClamAV doesn't need to see your downloads folder. To scan bigger files, raise clamd's StreamMaxLength and set the size limit on the Settings page to match.
Addresses: use something Protectarr can reach from inside its container: the server's LAN IP and published port, or the container name if they share a Docker network (http://sonarr:8989). localhost means Protectarr's own container.
Different paths in qBittorrent and Protectarr: if you can't mount the downloads folder at the same path, set PATH_MAPPINGS=/path/in/qbittorrent:/path/in/protectarr on the container.
-
Options > Downloads > Keep incomplete torrents in: a separate folder, so half-finished files are never mistaken for finished ones.
-
Options > Downloads > Run external program, so Protectarr reacts instantly instead of within a few seconds. The Settings page shows the exact commands for your setup:
- on torrent added:
curl -fsS -H "Authorization: Bearer HOOK_TOKEN" "http://your-server:9797/api/hook/added?hash=%I" - on torrent finished:
curl -fsS -H "Authorization: Bearer HOOK_TOKEN" "http://your-server:9797/api/hook/finished?hash=%I"
HOOK_TOKENis the hook token from the Settings page. It can only trigger these hooks, so if it leaks from qBittorrent's settings or logs, nobody can use it to open Protectarr or change its settings. It's sent as a header rather than in the URL so it stays out of access logs. (?key=HOOK_TOKENalso works, if a header isn't possible.)(The linuxserver.io qBittorrent image includes
curl.) - on torrent added:
Protectarr works alongside these. None are required except qBittorrent and at least one *arr app, but together they make a safer setup.
Security
| Container | Why | GitHub |
|---|---|---|
| ClamAV | Scans the files Protectarr sends it (subtitles, extras, archives) for known malware. Recommended; Protectarr only needs its TCP port (3310). Image: clamav/clamav |
Cisco-Talos/clamav · docker |
| Gluetun | Runs qBittorrent through a VPN with a kill switch, so torrent traffic never leaves without it. Image: qmcgaw/gluetun |
passteque/gluetun |
Download client
| Container | Why | GitHub |
|---|---|---|
| qBittorrent | The torrent client Protectarr watches (4.x and 5.x). The LinuxServer.io image includes curl for the instant hooks |
qbittorrent/qBittorrent · linuxserver/docker-qbittorrent |
*The arr stack
| Container | Why | GitHub |
|---|---|---|
| Sonarr | TV shows | Sonarr/Sonarr |
| Radarr | Movies | Radarr/Radarr |
| Lidarr | Music | Lidarr/Lidarr |
| Bookshelf | Books and audiobooks: a maintained fork of Readarr, which has been retired. Add it in Protectarr as type Readarr | pennydreadful/bookshelf · Readarr (archived) |
| Prowlarr | Manages indexers for all of the above; connect it to Protectarr to see which indexers send bad releases | Prowlarr/Prowlarr |
http://your-server:9797. Log in with your username and password (see Logins and tokens below). Repeated wrong passwords make an address wait a few minutes.
-
Activity: every check, with the reasons and the indexer. The cards along the top open a panel with what's behind them, such as recent blocks or the watched categories.

The Blocked recently and Watched categories panels


-
Review: held downloads waiting for Allow or Deny.

-
Quarantine: blocked files, which you can restore or delete.

-
Indexers: which indexers sent bad releases.

-
Settings: the address and key of every service, their live status with setup hints, and the categories being watched.

The rest of the Settings page: allowed file types, history, categories, hooks and tokens


Protectarr has three separate credentials, so a leaked one only opens what it's for:
| Credential | Where it comes from | What it opens |
|---|---|---|
| Web UI login (username + password) | Generated on first start and printed once in the log, or PROTECTARR_USERNAME/PROTECTARR_PASSWORD. Change it under Settings > Login |
Everything. Stored only as a salted PBKDF2 hash |
| API token | PROTECTARR_API_KEY, or generated and shown on the Settings page |
The JSON API under /api/ (including changing settings). Header only: Authorization: Bearer <token> or X-Api-Key: <token>. Never the web pages |
| Hook token | Generated and shown on the Settings page | Only the two qBittorrent hooks under /api/hook/ |
Generated tokens can be regenerated on the Settings page. Locked out? Set PROTECTARR_RESET_LOGIN=true and restart: the saved login is removed and a new password is printed in the log (or PROTECTARR_PASSWORD is used). Then remove the variable, or it happens on every start.
Upgrading from an earlier build: a login you already set keeps working, and so do hooks that pass the old key or PROTECTARR_API_KEY as ?key= (Protectarr logs a warning; switch them to the hook token). PROTECTARR_API_KEY no longer opens the web pages.
There's a JSON API too: /api/status, /api/settings (GET, and POST to change connections from a script), /api/events, /api/review, /api/quarantine, /api/indexers, and /health.
Service connections are easiest to set on the Settings page. Everything, including the rules, can also be set with environment variables on the container, which is handy for infrastructure-as-code. Anything saved on the Settings page takes precedence over these for the service connections.
If you'd rather use a file, copy config.example.yml to /config/config.yml: when that file exists it's used instead of the environment variables, and it can still pull secrets from the environment with ${VAR}.
| Variable | Default | |
|---|---|---|
QBIT_URL |
empty | qBittorrent Web UI, e.g. http://qbittorrent:8080. Nothing contacts qBittorrent until this (or the Settings page) is set |
QBIT_USERNAME / QBIT_PASSWORD |
admin / empty |
|
QBIT_RESUME_AFTER_CHECK |
true |
Start torrents that qBittorrent's stop condition held once their file list passes |
QBIT_RESUME_OTHER_CATEGORIES |
true |
Start new torrents in other categories that qBittorrent's stop condition held (only ones added in the last 2 minutes, or while Protectarr was down) |
QBIT_CATEGORIES |
empty | Check only these categories instead of the ones read from the *arr apps |
QBIT_WATCH_UNCATEGORIZED |
true |
Also check torrents with no category, such as a magnet link you paste into qBittorrent yourself. Only torrents added after it's turned on. Also on the Settings page |
<APP>_URL, <APP>_API_KEY |
<APP> is SONARR, RADARR, LIDARR or READARR, optionally with a suffix (RADARR_4K_URL) |
|
<APP>_CATEGORIES |
empty | Categories for that app, if it can't be read from its settings |
PROWLARR_URL, PROWLARR_API_KEY |
empty | |
CLAMAV_ENABLED |
true |
|
CLAMAV_HOST / CLAMAV_PORT |
empty / 3310 |
clamd to scan with, e.g. clamav or your-server-ip |
CLAMAV_TIMEOUT |
120 |
Seconds to wait for ClamAV to scan one file |
CLAMAV_STREAM_MAX_MB |
25 |
Largest file sent to ClamAV; keep it at or below clamd's StreamMaxLength |
CLAMAV_SCAN_MEDIA |
false |
Also send verified real video/audio files to ClamAV (their real type is always checked). Slow for albums and season packs |
ACTION_MALICIOUS / ACTION_SUSPICIOUS |
block / hold |
block or hold |
MIN_EPISODE_MB / MIN_MOVIE_MB |
30 / 300 |
Smallest believable episode and movie |
CATEGORY_MIN_VIDEO_MB |
empty | Per-category override, e.g. tv-anime=15 |
CATEGORY_PROFILES |
empty | What a category holds when no *arr app says so, e.g. audiobooks=book,concerts=movie (tv, movie, music, book) |
ALLOW_ARCHIVES |
false |
Set true if you use Unpackerr for scene RAR releases. Archives are still checked for passwords and programs |
EXTRA_BLOCKED_EXTENSIONS |
empty | e.g. .iso,.torrent |
ALLOWED_EXTENSIONS |
empty | File types not to flag, e.g. .iso,.mka. Their contents are still checked (real type, disguised names, lures, password archives, ClamAV). Also editable under Settings > Allowed file types, which takes precedence once saved |
PATH_MAPPINGS |
empty | qbit-path:protectarr-path, comma-separated |
PROTECTARR_USERNAME / PROTECTARR_PASSWORD |
admin / generated |
The first web UI login, used only while none is saved. Without a password, one is generated and printed once in the log |
PROTECTARR_API_KEY |
generated | API token for scripts and the JSON API (as a header). Never opens the web pages |
PROTECTARR_RESET_LOGIN |
false |
Forget the saved login and make a new first one (when you're locked out) |
PUBLIC_URL |
empty | How you open the UI, if that's through a reverse proxy |
POLL_SECONDS |
5 |
How often qBittorrent is checked |
EARLY_CHECKS |
true |
Check files while they download (real type from the first piece, full scan as each file finishes) |
HISTORY_DAYS |
90 |
Days to keep clean results on the Activity page; 0 keeps everything. Blocked, held and denied results are always kept. Also editable under Settings > Activity history, which takes precedence once saved |
QUARANTINE_DIR / DATA_DIR |
/quarantine / /config |
|
PORT |
9797 |
Port the web UI listens on inside the container |
LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING or ERROR |
PROTECTARR_CONFIG |
/config/config.yml |
Where to look for the optional config file |
- It protects a media pipeline; it is not an antivirus. It is built to catch fake and bait releases. It cannot make cracked software safe, and a brand-new trojan that ClamAV doesn't know yet will pass the signature check. (It will still be caught if it's a program pretending to be a video.)
- Import race. Sonarr/Radarr import a download as soon as it finishes, so a check that runs after completion can lose that race. Protectarr closes it from both sides: while a torrent downloads it asks qBittorrent for each file's first and last pieces early, checks each file's real type as soon as its first piece arrives, and fully scans every file the moment it finishes, so there's almost nothing left to check at completion. And if an *arr app still imports something bad first (for example while Protectarr was down), Protectarr deletes the imported file through that app, marks the grab as failed so the release is blocklisted and searched again, and removes the torrent. Lidarr and Readarr are handled the same way.
- Large files and ClamAV. Only files up to
CLAMAV_STREAM_MAX_MBgo to ClamAV, and files that are verified real videos or audio are not sent unlessCLAMAV_SCAN_MEDIA=true; they're rarely the carrier. - Nothing unchecked passes as clean. If the downloaded files can't be found (wrong
PATH_MAPPINGSor mounts), can't be read, or ClamAV is down, the download is treated as suspicious and held (by default) instead of passed. Downloads held only because ClamAV was down are scanned again and released or blocked automatically once it's back. The Settings page warns when qBittorrent's download folder isn't visible to Protectarr. - Held means held. A held torrent that something else starts (you in qBittorrent, qbit_manage) is stopped again; use Allow on the Review page. When files can't be moved into the quarantine folder, they're renamed in place with
.protectarr-heldso no *arr app imports them. - Torrents keep being checked after their category changes, for example by an *arr app's "category after import".
- Notifications: coming soon. Protectarr doesn't send alerts (Discord, Telegram, ntfy, email) yet. For now, check the Activity and Review pages (Review shows a badge when something is waiting for you), or watch the container log, which records every block and hold.
- qBittorrent only, for now. Transmission and Deluge support is planned.
- Keep the UI on your LAN (or behind a VPN or reverse proxy with its own login), and replace the generated password with your own. Wrong passwords lock out an address for a few minutes; behind a reverse proxy that address is the proxy's, so repeated failures there make everyone wait. Behind a reverse proxy, set
PUBLIC_URLto the address you open it on. - Failed qBittorrent logins back off (1 minute, doubling up to 15), because qBittorrent bans an address after 5 failures. Saving the Settings page retries straight away.
- It can fail, and false positives happen. Protectarr is designed to reduce the risk of malicious torrents, not to eliminate it. Even though Protectarr has undergone extensive testing, failures and false positives are still possible. It is not an antivirus solution — treat it as one more layer of protection, not your only one.
Your antivirus may flag Protectarr's files. Protectarr is not malicious; it's built to catch malware, so some of its files have to look like malware on purpose:
- The tests and e2e scripts (
tests/,e2e/, not in the Docker image) create the EICAR test file and fake program headers to check that bad downloads are caught. EICAR is a harmless, industry-standard string that every antivirus reports as a "virus" so detection can be tested safely. The source only builds it at runtime, but Windows Defender may still flag files the tests write, or the repo zip. It's safe to allow them, or skip downloadingtests/ande2e/if you only want to run the container. - The quarantine folder holds the real files Protectarr blocked, so it may intentionally contain actual malware. If your antivirus flags or deletes something in it, that's expected — it's doing its job. Don't add the quarantine folder to your antivirus exclusions unless you fully understand the implications.
- Your downloads folder. On Windows (Docker Desktop), Defender may scan or remove a bad download before Protectarr gets to it. That's fine: Protectarr then holds the torrent because the files are missing.
If you're unsure, the full source is here to read, and the image is built from it by the release workflow.
pip install -e '.[test]'
pytest
python3 e2e/run.py starts a throwaway stack (qBittorrent, Sonarr, Lidarr, Prowlarr, ClamAV and Protectarr built from your checkout), connects everything from scratch and runs real bait scenarios against it. It needs Docker, and ClamAV takes a few minutes to get ready on the first run. Add --down to remove the stack afterwards.
docs/design.md explains how it hooks into qBittorrent and the *arr apps, and why.
If Protectarr is useful to you, you can support it and other projects on Venmo (@ablatnick).

