Homarr is a feature-rich self-hosted dashboard combining service links, integrated status monitoring, Docker container visibility, and user account management in a single application. Its security profile is more complex than simpler dashboards like Homer: Homarr stores API keys for all integrated services (Sonarr, Radarr, qBittorrent, Transmission, Jellyfin, and others) in its SQLite database — these credentials are accessible to any Homarr admin and may be exposed through the configuration API; Homarr's Docker integration can mount the host Docker socket, providing visibility into all running containers, container names, and in some configurations the ability to view container logs; Homarr has built-in user management with admin and regular user roles — the admin account setup on first run determines the initial credential; and Homarr's ping/status checking for services makes outbound HTTP requests to configured service URLs, creating an SSRF vector if those URLs can be controlled by a non-admin user or configured by an admin pointing to internal targets. This guide covers systematic Homarr security assessment.
# Homarr — authentication and admin account assessment
HOMARR_URL="https://homarr.example.com"
# Check if Homarr requires authentication (redirects to /auth/login if protected)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -L "${HOMARR_URL}/" 2>/dev/null)
echo "Homepage status (following redirects): ${STATUS}"
# Attempt default credentials
for CREDS in "admin:admin" "admin:password" "admin:homarr"; do
USER=$(echo "$CREDS" | cut -d: -f1)
PASS=$(echo "$CREDS" | cut -d: -f2)
RESPONSE=$(curl -s -X POST "${HOMARR_URL}/api/auth/callback/credentials" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=${USER}&password=${PASS}&redirect=false&callbackUrl=%2F&json=true" 2>/dev/null)
echo "${USER}:${PASS} -> ${RESPONSE:0:100}"
done
# Get Homarr configuration via API (requires auth session cookie)
# If you have a valid session cookie:
SESSION_COOKIE="next-auth.session-token=your-session-token"
curl -s "${HOMARR_URL}/api/configs" \
-H "Cookie: ${SESSION_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f'Config boards: {len(d) if isinstance(d,list) else \"see response\"}')
if isinstance(d,list):
for board in d:
print(f' Board: {board.get(\"configProperties\",{}).get(\"name\",\"?\")}')
except: pass
" 2>/dev/null
# Homarr service integrations — stores API keys for all configured services
HOMARR_URL="https://homarr.example.com"
SESSION_COOKIE="next-auth.session-token=your-session-token"
# Get integration configuration — contains API keys for all integrated services
curl -s "${HOMARR_URL}/api/config/default" \
-H "Cookie: ${SESSION_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
try:
config = json.load(sys.stdin)
apps = config.get('apps',[])
print(f'Apps: {len(apps)}')
for app in apps:
name = app.get('name','?')
behavior = app.get('behaviour',{})
integration = app.get('integration',{})
props = integration.get('properties',[])
if props:
print(f' {name} (has integration config):')
for prop in props:
pname = prop.get('field','')
pval = prop.get('value','')
if pval and any(k.lower() in pname.lower() for k in ['apikey','token','password','key']):
print(f' CREDENTIAL {pname}: {pval}')
else:
url = behavior.get('externalUrl','') or behavior.get('defaultUrl','')
print(f' {name} url={url}')
except Exception as e:
print(f'Error: {e}')
" 2>/dev/null
# Homarr Docker integration — /var/run/docker.sock mounted in container
# Docker socket access provides visibility into all container infrastructure
HOMARR_URL="https://homarr.example.com"
SESSION_COOKIE="next-auth.session-token=your-session-token"
# Check if Docker integration is enabled and what it exposes
curl -s "${HOMARR_URL}/api/docker/containers" \
-H "Cookie: ${SESSION_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
try:
containers = json.load(sys.stdin)
if isinstance(containers, list):
print(f'Containers visible via Homarr: {len(containers)}')
for c in containers[:15]:
name = c.get('name','?')
state = c.get('state','?')
image = c.get('image','?')
print(f' {name} state={state} image={image}')
else:
print(f'Response: {str(containers)[:200]}')
except Exception as e:
print(f'Error or Docker not configured: {e}')
" 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| Admin credential brute force or default credentials | POST /api/auth/callback/credentials — test admin/admin, admin/password, admin/homarr; successful auth returns session cookie granting full admin access to all board configurations and service integrations | Critical |
| Service integration API key extraction | GET /api/config/default with admin session — returns all board configuration including integration properties containing API keys for Sonarr, Radarr, qBittorrent, Jellyfin, and other integrated services | High |
| Docker socket container enumeration | GET /api/docker/containers with admin session — when Docker socket mounted, returns all containers on host Docker daemon including names, images, state, and network configuration | High |
| Public board API key exposure | Access Homarr as unauthenticated user — boards set to public allow viewing widget configurations; integration widgets may reveal API keys used for status polling to any visitor | High |
| homarr.db SQLite credential access | Read /data/homarr.db — SQLite database contains all service integration credentials; accessible via path traversal or direct filesystem access to Homarr data directory | High |
Ironimo tests Homarr deployments for admin credential brute force, service integration API key extraction from board configurations, Docker socket container enumeration via the Docker API, public board access revealing service API keys, homarr.db credential exposure, guest access permission misconfiguration, and service integration credential scope analysis for Sonarr, Radarr, and qBittorrent integrations.
Start free scan