🔒 Security Guide
Argus implements multiple layers of security for production deployments. This guide covers security features, configuration best practices, and hardening recommendations.
🛡️ Security Features
🔑 API Key Authentication
Purpose: Protect write endpoints from unauthorized access.
Implementation:
- Optional
API_KEYenvironment variable - 32-character minimum enforced at startup (application exits if too short)
- Timing-safe comparison using
secrets.compare_digest()to prevent timing attacks X-Api-Keyheader required on all protected endpoints
Protected Endpoints:
POST /api/trigger— Manual poll triggerPUT /api/config— Configuration updatesPUT /api/alerts— Alert configurationPOST /api/alerts/test— Test alert notificationsPOST /api/devices— Add devicePUT /api/devices— Replace device listPUT /api/devices/{id}— Update deviceDELETE /api/devices/{id}— Remove device
Public Endpoints: (no authentication required)
GET /api/health— Health checkGET /api/snapshots— Power snapshot historyGET /api/events— Power event historyGET /api/devices— Device listGET /api/energy— Energy totalsGET /api/config— Current runtime configurationGET /api/alerts— Alert configurationGET /api/trigger/status— Poll statusGET /api/diagnostics— Last-poll diagnostics
Generate a Secure Key:
# Recommended
python -c 'import secrets; print(secrets.token_urlsafe(32))'
# Alternative
openssl rand -hex 32
Startup validation failure message:
ERROR: API_KEY must be at least 32 characters long
Suggestion: Generate a secure key with:
python -c 'import secrets; print(secrets.token_urlsafe(32))'
🚦 Rate Limiting
Purpose: Prevent abuse and DoS via request flooding.
Implementation:
- Per-API-key sliding window rate limiting
- Default: 60 requests per 60-second window (configurable via
RATE_LIMIT_PER_MINUTE) - Applies to all protected endpoints
- Returns
429 Too Many RequestswithRetry-Afterheader
🛡️ SSRF Protection on Alert URLs
Purpose: Prevent Server-Side Request Forgery through alert provider URLs.
Implementation:
- All alert provider URLs are validated at configuration time
- Only
https://scheme is accepted —http://is rejected - Validation is enforced at both the Pydantic model layer and runtime
Rejected configuration:
WEBHOOK_URL=http://internal-host/secret # rejected — not HTTPS
GOTIFY_URL=http://gotify.example.com # rejected — not HTTPS
🔐 Security Headers
Every API response includes the following headers:
| Header | Value |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
Referrer-Policy |
strict-origin-when-cross-origin |
Permissions-Policy |
geolocation=(), microphone=(), camera=() |
Content-Security-Policy |
default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; … |
📏 Request Size Limits
All requests are limited to 1 MB. Requests with a Content-Length exceeding this return
413 Request Entity Too Large immediately, before the body is read.
🌐 CORS Restrictions
The API restricts cross-origin requests to explicitly allowed origins configured via
ALLOWED_ORIGINS. The default allows only local development origins.
Production example:
ALLOWED_ORIGINS=https://argus.example.com
✅ Production Hardening Checklist
- Set
API_KEYto a randomly generated 32+ character secret - Set
ALLOWED_ORIGINSto your actual UI domain (not*) - Set
APP_ENV=production - Use HTTPS in front of the API (reverse proxy with TLS)
- Set
API_HOST=127.0.0.1and expose via reverse proxy rather than binding to0.0.0.0 - Restrict Docker port bindings (
127.0.0.1:8000:8000) - Use read-only filesystem mounts where possible
- Rotate
API_KEYregularly - Enable
ALERT_ON_DEVICE_OFFLINEto detect monitoring gaps
🔐 Reverse Proxy with TLS (Nginx Example)
server {
listen 443 ssl http2;
server_name argus.example.com;
ssl_certificate /etc/letsencrypt/live/argus.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/argus.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name argus.example.com;
return 301 https://$host$request_uri;
}
🔑 NUT Credential Security
NUT username and password (NUT_USERNAME, NUT_PASSWORD) are transmitted in plaintext over
the NUT TCP protocol. Mitigations:
- Keep NUT and Argus on the same private network or Docker bridge
- Use a dedicated read-only NUT user (
upsmon) with minimal permissions - Do not expose port 3493 to the public internet
Minimal NUT upsd.users entry:
[argus]
password = <strong-random-password>
upsmon primary
📡 SNMPv3 Security
For SNMP devices, prefer SNMPv3 with authentication and privacy encryption over the default community-string-based v2c:
SNMP_VERSION=3
SNMP_V3_USERNAME=argus-monitor
SNMP_V3_AUTH_PROTOCOL=SHA # stronger than MD5
SNMP_V3_AUTH_KEY=<strong-key>
SNMP_V3_PRIV_PROTOCOL=AES # stronger than DES
SNMP_V3_PRIV_KEY=<strong-key>
Do not store these values in version-controlled files — use Docker secrets or a secrets manager.
📢 Reporting Vulnerabilities
Please report security vulnerabilities privately via the process described in SECURITY.md.