Security Guide¶
Security considerations for self-hosted deployments. This system is designed for local/trusted network use. Additional hardening is required for exposed deployments.
Security Model¶
Home Security Intelligence is designed as a single-user, local deployment:
- No authentication by default - Assumes trusted network
- No cloud connectivity - All processing is local
- No internet exposure - Designed for LAN access only
Default Security Posture¶
| Feature | Default | Production Recommendation |
|---|---|---|
| Authentication | Disabled | Enable for exposed deployments |
| HTTPS/TLS | Disabled | Enable for production |
| Rate Limiting | Enabled | Keep enabled |
| Admin Endpoints | Disabled | Keep disabled unless needed |
| Debug Mode | Disabled | Keep disabled |
| CORS | Localhost only | Restrict to your domains |
Database Credentials (REQUIRED)¶
SECURITY: Database password is REQUIRED. No default passwords exist.
As of the latest security update, all default passwords have been removed from docker-compose files. The system will fail to start if POSTGRES_PASSWORD is not set, preventing accidental deployment with insecure credentials.
# docker-compose.prod.yml - password is REQUIRED
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
Setting Up Database Credentials¶
Option 1: Interactive Setup (Recommended)
The setup script generates secure 32-character passwords automatically:
./setup.sh # Quick mode - generates secure password
./setup.sh --guided # Guided mode - explains each step
Option 2: Manual .env File
- Generate a secure password:
- Create .env file:
# .env (never commit this file)
POSTGRES_USER=security
POSTGRES_PASSWORD=your-secure-generated-password-here
POSTGRES_DB=security
DATABASE_URL=postgresql+asyncpg://security:your-secure-generated-password-here@postgres:5432/security # pragma: allowlist secret
- Set secure file permissions:
Option 3: Docker Secrets (Enhanced Security)
For production deployments, Docker secrets provide better security than environment variables:
# Create secrets directory
mkdir -p secrets
# Generate and store password
openssl rand -base64 32 > secrets/postgres_password.txt
# Set secure permissions
chmod 600 secrets/postgres_password.txt
Then uncomment the secrets sections in docker-compose.prod.yml:
services:
postgres:
secrets:
- postgres_password
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
secrets:
postgres_password:
file: ./secrets/postgres_password.txt
Password Requirements¶
The setup script enforces these security guidelines:
| Requirement | Value | Reason |
|---|---|---|
| Minimum length | 16 characters | Prevents brute force attacks |
| Recommended length | 32 characters | Industry best practice |
| Character set | URL-safe base64 | Avoids shell escaping issues |
Weak passwords that trigger warnings:
security_dev_password(old default - removed)password,admin,root,secret- Any password under 16 characters
Authentication¶
API Key Authentication¶
Enable API key authentication for protected access:
Making Authenticated Requests¶
# Include X-API-Key header
curl -H "X-API-Key: your-secure-api-key-here" \
http://localhost:8000/api/events
Key Requirements¶
- Use cryptographically secure random strings (32+ characters)
- Store keys securely (don't commit to git)
- Rotate keys periodically
- Keys are hashed on startup
- Always use header-based authentication (
X-API-Keyheader) instead of query parameters
Security Warning: Avoid passing API keys in URL query parameters (
?api_key=...). Query parameters are logged in server access logs, stored in browser history, and exposed in HTTP Referer headers.
Generate Secure Keys¶
# Python
python -c "import secrets; print(secrets.token_urlsafe(32))"
# OpenSSL
openssl rand -base64 32
Admin Endpoint Security¶
Admin endpoints require both conditions:
DEBUG=trueADMIN_ENABLED=true
# Enable admin endpoints (development only)
DEBUG=true
ADMIN_ENABLED=true
# Optional: Require API key for admin endpoints
ADMIN_API_KEY=your-admin-api-key
Warning: Never enable admin endpoints in production without the ADMIN_API_KEY protection.
TLS/HTTPS¶
Why TLS Matters¶
Without TLS:
- Credentials sent in plaintext
- Camera images can be intercepted
- API responses can be modified (MITM attacks)
TLS Configuration Modes¶
| Mode | Use Case | Certificate Source |
|---|---|---|
disabled | Development only | None |
self_signed | LAN/internal use | Auto-generated |
provided | Production | Your certificates |
Self-Signed Certificates (LAN)¶
# .env
TLS_MODE=self_signed
TLS_CERT_DIR=data/certs
# Certificates will be auto-generated on first start
Note: Browsers will show security warnings for self-signed certificates. Add exceptions for LAN access.
Production Certificates¶
# .env
TLS_MODE=provided
TLS_CERT_PATH=/etc/ssl/certs/server.crt
TLS_KEY_PATH=/etc/ssl/private/server.key
TLS_MIN_VERSION=TLSv1.2
Certificate Sources¶
- Let's Encrypt - Free, automated (requires public domain)
- Internal CA - For enterprise deployments
- Purchased - From certificate authorities
mTLS (Mutual TLS)¶
For high-security deployments, require client certificates:
Generating Self-Signed Certificates¶
For development or internal LAN deployments:
# Create certificate directory
mkdir -p data/certs
cd data/certs
# Generate private key
openssl genrsa -out server.key 2048
# Generate self-signed certificate (valid for 365 days)
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=home-security-intelligence/O=Local Development"
# Set proper permissions
chmod 600 server.key
chmod 644 server.crt
Let's Encrypt Certificates¶
For internet-facing deployments with a domain name:
# Install certbot
sudo apt install certbot # Debian/Ubuntu
sudo dnf install certbot # Fedora/RHEL
# Generate certificate (requires port 80 accessible)
sudo certbot certonly --standalone -d yourdomain.com
# Certificates are stored in:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/yourdomain.com/privkey.pem
Configure in .env:
TLS_MODE=provided
TLS_CERT_PATH=/etc/letsencrypt/live/yourdomain.com/fullchain.pem
TLS_KEY_PATH=/etc/letsencrypt/live/yourdomain.com/privkey.pem
TLS Configuration Reference¶
| Variable | Default | Description |
|---|---|---|
TLS_MODE | disabled | disabled, self_signed, or provided |
TLS_CERT_PATH | none | Path to TLS certificate file |
TLS_KEY_PATH | none | Path to TLS private key file |
TLS_CA_PATH | none | Path to CA certificate (for mTLS) |
TLS_VERIFY_CLIENT | false | Enable client certificate verification |
TLS_MIN_VERSION | TLSv1.2 | Minimum TLS version (TLSv1.2, TLSv1.3) |
TLS_CERT_DIR | data/certs | Directory for auto-generated certs |
AI Service Security¶
HTTPS for AI Services¶
Critical: AI service URLs use HTTP by default, which is vulnerable to MITM attacks.
| Environment | Protocol | Acceptable |
|---|---|---|
| Localhost dev | HTTP | Yes |
| Docker network | HTTP | Yes (internal) |
| Cross-machine LAN | HTTPS | Required |
| Remote AI services | HTTPS | Required |
# Production AI service URLs
YOLO26_URL=https://your-yolo26-host:8095
NEMOTRON_URL=https://your-nemotron-host:8091
Network Security¶
Firewall Configuration¶
Only expose necessary ports:
| Port | Service | Exposure |
|---|---|---|
| 80/443 | Frontend | User access |
| 8000 | Backend API | User access |
| 5432 | PostgreSQL | Internal only |
| 6379 | Redis | Internal only |
| 8095 | YOLO26 | Internal only |
| 8091 | Nemotron | Internal only |
# UFW example (Linux)
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 8000/tcp
ufw deny 5432/tcp
ufw deny 6379/tcp
ufw enable
Docker Network Security¶
Use internal networks for sensitive services:
# docker-compose.yml
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external access
services:
frontend:
networks:
- frontend
backend:
networks:
- frontend
- backend
postgres:
networks:
- backend # Internal only
redis:
networks:
- backend # Internal only
Rate Limiting¶
Rate limiting is enabled by default to prevent abuse:
# .env (defaults shown)
RATE_LIMIT_ENABLED=true
RATE_LIMIT_REQUESTS_PER_MINUTE=60
RATE_LIMIT_BURST=10
RATE_LIMIT_MEDIA_REQUESTS_PER_MINUTE=120
RATE_LIMIT_WEBSOCKET_CONNECTIONS_PER_MINUTE=10
RATE_LIMIT_SEARCH_REQUESTS_PER_MINUTE=30
Rate Limit Tiers¶
| Endpoint Type | Limit/min | Burst | Purpose |
|---|---|---|---|
| General API | 60 | 10 | Normal operations |
| Media (images/thumbnails) | 120 | - | Higher for dashboards |
| Search | 30 | - | Lower (expensive operation) |
| WebSocket | 10 connections | - | Prevent connection storms |
CORS Configuration¶
Restrict Cross-Origin Resource Sharing to trusted domains:
# Development
CORS_ORIGINS=["http://localhost:3000", "http://localhost:5173"]
# Production
CORS_ORIGINS=["https://your-domain.com"]
Warning: Never use ["*"] in production - allows any origin to make requests.
Metrics Endpoint Security¶
The /api/metrics endpoint exposes Prometheus-format metrics and is intentionally unauthenticated to allow Prometheus scraping. This is a security consideration.
Information Disclosed¶
| Metric Type | Exposed Information | Risk Level |
|---|---|---|
| Queue depths | System load patterns | Low |
| Error counts | Failure patterns, potential vulnerabilities | Medium |
| AI request durations | Service performance characteristics | Low |
| Detection counts by class | Activity patterns (when people detected) | Medium |
| Events by camera | Camera names, activity patterns | Medium |
Hardening Recommendations¶
- Network-Level IP Allowlisting (Recommended)
# nginx.conf
location /api/metrics {
allow 10.0.0.50; # Prometheus server IP
allow 127.0.0.1; # Localhost
allow 172.16.0.0/12; # Docker internal network
deny all;
proxy_pass http://backend:8000;
}
- Basic Auth via Reverse Proxy
location /api/metrics {
auth_basic "Metrics";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend:8000;
}
- Rate Limiting for Metrics
Be careful not to set limits too low - Prometheus typically scrapes every 10-30 seconds.
See Prometheus Alerting for full monitoring configuration.
Path Traversal Protection¶
Media endpoints include path traversal protection:
# Blocks attempts like:
# /api/media/cameras/../../../etc/passwd
# /api/media/thumbnails/../../sensitive.txt
The backend validates all file paths are within expected directories.
Secrets Management¶
Sensitive Variables¶
| Variable | Contains | Storage Recommendation |
|---|---|---|
POSTGRES_PASSWORD | DB password | .env file or Docker secrets (REQUIRED) |
DATABASE_URL | DB connection | .env file (includes password) |
API_KEYS | API credentials | .env file or secrets manager |
ADMIN_API_KEY | Admin credential | .env file or secrets manager |
SMTP_PASSWORD | Email credentials | .env file or secrets manager |
Docker Secrets (Recommended for Production)¶
This project supports Docker secrets for enhanced credential security:
# Create secrets with the setup script
./setup.sh --create-secrets
# Or manually:
mkdir -p secrets
openssl rand -base64 32 > secrets/postgres_password.txt
chmod 600 secrets/postgres_password.txt
Docker secrets advantages over environment variables:
- Not visible in
docker inspectoutput - Not exposed in container process listings
- Stored in RAM-backed tmpfs at
/run/secrets/ - Automatically cleaned up when container stops
Best Practices¶
- Never commit secrets to git
-
Use different credentials per environment
-
Development: auto-generated passwords from
./setup.sh -
Production: Docker secrets or external secrets manager
-
Rotate credentials periodically
-
API keys: quarterly
- Database passwords: annually
-
After any suspected compromise: immediately
-
Use secrets managers for production
-
Docker secrets (built-in support)
- HashiCorp Vault
-
Cloud provider secrets (AWS SSM, GCP Secret Manager, etc.)
-
Verify file permissions
# .env should be owner-only
ls -la .env
# Expected: -rw------- (600)
# secrets/ files should be owner-only
ls -la secrets/
# Expected: -rw------- (600) for each file
Logging Security¶
Sensitive Data in Logs¶
The logging system sanitizes sensitive data:
- Passwords are masked
- API keys are truncated
- Personal data is redacted
Log Retention¶
Log Access¶
Restrict access to log files:
Security Checklist¶
Development¶
- [ ] Debug mode can be enabled
- [ ] Self-signed certs acceptable
- [ ] Default passwords acceptable
- [ ] CORS allows localhost
Staging/Testing¶
- [ ] TLS enabled (self-signed OK)
- [ ] API keys enabled
- [ ] Strong passwords used
- [ ] Rate limiting enabled
- [ ] CORS restricted
Production¶
- [ ]
DEBUG=false - [ ]
ADMIN_ENABLED=false - [ ] TLS with valid certificates
- [ ] API keys required
- [ ] POSTGRES_PASSWORD set (required)
- [ ] Strong, unique passwords for all services (32+ characters recommended)
- [ ]
.envfile permissions are600(owner read/write only) - [ ] Docker secrets used for enhanced security (optional)
- [ ] Firewall configured
- [ ] Database not exposed externally
- [ ] Redis not exposed externally
- [ ] AI services not exposed externally
- [ ] CORS restricted to your domain
- [ ] Log retention configured
- [ ] Backups encrypted
Security Updates¶
Keeping Current¶
- Monitor dependencies:
- Update regularly:
# Python dependencies (using uv)
uv sync --extra dev --upgrade
# Node dependencies
cd frontend && npm update
- Subscribe to security advisories:
- FastAPI: GitHub security advisories
- React: npm security advisories
- PostgreSQL: postgresql.org/support/security
Incident Response¶
If Compromised¶
- Isolate: Disconnect from network
- Preserve: Save logs before rotation
- Investigate: Check access logs and audit trail
- Rotate: Change all credentials
- Patch: Apply security updates
- Monitor: Watch for continued attacks
Audit Trail¶
Review logs for suspicious activity:
# Failed authentication attempts
grep "authentication failed" data/logs/security.log
# Unusual API access patterns
grep "rate limit exceeded" data/logs/security.log
# Admin endpoint access
grep "admin" data/logs/security.log
See Also¶
- Environment Variable Reference - All configuration options
- Secrets Management - Detailed secrets guide
- API Keys - API key management
- Troubleshooting - Common issues
- Monitoring - Security monitoring
- Prometheus Alerting - Alert configuration