Files
light-heart-labs--dreamserver/ods/SECURITY.md
T
wehub-resource-sync 9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:33 +08:00

9.5 KiB

ODS Security Guide

Security best practices for running ODS.


⚠️ Before You Start

  1. Run ./install.sh — generates secure random secrets automatically
  2. Never use default passwords — if you see "changeme", change it
  3. Keep the default localhost binding — opt into LAN exposure only when you understand the firewall and authentication tradeoffs

Secrets Management

Generated Secrets

The installer auto-generates these in .env:

Secret Purpose
WEBUI_SECRET Session signing for Open WebUI
N8N_PASS Admin password for n8n
LITELLM_KEY API key for LiteLLM gateway

Verify no defaults remain:

grep -E "(PASSWORD|SECRET|KEY)" .env | grep -i changeme

Manual Secret Rotation

# Generate new secrets
NEW_WEBUI=$(openssl rand -hex 32)
NEW_N8N=$(openssl rand -base64 16)
NEW_LITELLM="sk-ods-$(openssl rand -hex 16)"

# Update .env
sed -i "s/WEBUI_SECRET=.*/WEBUI_SECRET=$NEW_WEBUI/" .env
sed -i "s/N8N_PASS=.*/N8N_PASS=$NEW_N8N/" .env
sed -i "s/LITELLM_KEY=.*/LITELLM_KEY=$NEW_LITELLM/" .env

# Restart services
docker compose down && docker compose up -d

Network Security

Default: Localhost Only

All services bind to 127.0.0.1 — accessible only from the local machine.

Quick LAN Access

For headless servers accessible from other machines on the same network:

./install.sh --lan

Or change BIND_ADDRESS to 0.0.0.0 in the Dashboard Settings tab, then restart:

ods restart

This binds all services to all network interfaces. Use firewall rules to restrict access to your local subnet:

sudo ufw allow from 192.168.0.0/24 to any port 3000  # WebUI
sudo ufw allow from 192.168.0.0/24 to any port 3001  # Dashboard
sudo ufw allow from 192.168.0.0/24 to any port 8080  # LLM API

Host Agent Network Binding

The host agent (bin/ods-host-agent.py) has its own bind address, separate from the Docker services above. It is controlled by ODS_AGENT_BIND in .env:

Platform Default Behavior
macOS / Windows 127.0.0.1 Docker Desktop routes container traffic via loopback — loopback is sufficient
Linux auto-detected Detects the ods-network gateway IP (e.g. 172.18.0.1) so containers can reach the agent; LAN devices cannot. Falls back to the default Docker bridge gateway (e.g. 172.17.0.1) for partial/older installs, then 127.0.0.1 if detection fails.

To override the default, set ODS_AGENT_BIND in .env:

# Restrict to loopback only (e.g. no-Docker Linux or extra hardening)
ODS_AGENT_BIND=127.0.0.1

# Bind to a Docker network gateway only (explicit Linux default)
ODS_AGENT_BIND=172.17.0.1

# Bind to all interfaces — exposes the host agent API on LAN (not recommended)
ODS_AGENT_BIND=0.0.0.0

Note: If you bind to 0.0.0.0, ensure ODS_AGENT_KEY is set in .env — it protects the extension management endpoints with Bearer token authentication.

If you must expose publicly, use a reverse proxy with TLS:

Caddy (simple):

# /etc/caddy/Caddyfile
yourdomain.com {
    reverse_proxy localhost:3000
}

nginx (with rate limiting):

limit_req_zone $binary_remote_addr zone=ai:10m rate=10r/m;

server {
    listen 443 ssl;
    server_name ai.yourdomain.com;
    
    ssl_certificate /etc/letsencrypt/live/ai.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ai.yourdomain.com/privkey.pem;
    
    auth_basic "AI Server";
    auth_basic_user_file /etc/nginx/.htpasswd;
    
    location / {
        limit_req zone=ai burst=5;
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Consider VPN (Tailscale, WireGuard) instead of public exposure.


Container Security

Resource Limits

Prevent runaway containers:

services:
  llama-server:
    deploy:
      resources:
        limits:
          memory: 32G
        reservations:
          memory: 16G

Principle of Least Privilege

The docker-compose files use:

  • Non-root users where possible
  • Read-only volumes where appropriate
  • GPU access only for services that need it

Data Security

What's Stored

Directory Contents Sensitive?
data/open-webui/ Chat history, user accounts Yes
data/n8n/ Workflows, credentials Yes
data/qdrant/ Vector embeddings Maybe
data/whisper/ Model cache No
models/ Downloaded model weights No

Encryption at Rest

Docker volumes aren't encrypted by default. For sensitive deployments:

  • Use LUKS encrypted filesystem
  • Or encrypted Docker volumes

Backup Security

Backups contain sensitive data — encrypt them:

# Create encrypted backup
tar -cz data/ | gpg -c > ods-backup-$(date +%Y%m%d).tar.gz.gpg

# Restore
gpg -d ods-backup-YYYYMMDD.tar.gz.gpg | tar -xz

Model Download Integrity

The installer verifies GGUF model downloads using SHA256 checksums to prevent:

  • Corrupted downloads from network issues
  • Truncated files from interrupted transfers
  • Potential supply chain attacks

How it works:

  1. Before installation: checks existing model files against known checksums
  2. After download: verifies freshly downloaded models
  3. On mismatch: removes corrupt file and prompts for re-download

Verification happens automatically during installation. If a model fails verification:

# The installer will show:
# ✗ Downloaded file is corrupt (SHA256 mismatch)
#   Expected: 9f1a24700a339b09c06009b729b5c809e0b64c213b8af5b711b3dbdfd0c5ba48
#   Got:      [actual hash]
# Corrupt file removed. Re-run installer to download again.

# Simply re-run the installer:
./install.sh

Manual verification:

# Check a model file manually
sha256sum data/models/Qwen3.5-9B-Q4_K_M.gguf

# Compare against expected hash in installers/lib/tier-map.sh
grep -A 2 "Qwen3.5-9B" installers/lib/tier-map.sh | grep GGUF_SHA256

Note: Some models (like qwen3-coder-next) don't have checksums yet. The installer will skip verification for these but still download them successfully.

Network Timeout Hardening

All network operations (downloads, health checks, API calls) include timeout protection to prevent indefinite hangs.

Important semantic note:

  • curl --max-time is a total wall-clock timeout for the entire request.
  • wget --timeout/--read-timeout are per-connection / idle (no-progress) timeouts.

Because of this difference, large model downloads must not use a low curl --max-time, or they will abort on slow-but-progressing links.

Timeout policy:

  • Health checks / small API calls: use curl --connect-timeout + --max-time (short total timeout)
  • Script downloads / small metadata: use curl --connect-timeout + --max-time (bounded total time)
  • Large model downloads: fail fast on unreachable servers, and fail on stalled transfers, but do not impose a low total wall-clock cap

Why this matters:

  • Prevents installer hangs on slow/unresponsive networks
  • Keeps slow-but-progressing multi-GB downloads running
  • Provides predictable failure modes instead of indefinite blocking

Examples:

# Health check (total timeout is OK)
curl -fsS --connect-timeout 3 --max-time 10 http://localhost:8080/health

# Script download (bounded total timeout is OK)
curl -fsSL --connect-timeout 10 --max-time 300 https://get.docker.com -o script.sh

# Large download (stall detection; no low total max-time)
# - speed-limit/time = "consider it stalled if below 10KiB/s for 30s"
curl -C - -L --progress-bar --connect-timeout 10 \
  --speed-time 30 --speed-limit 10240 \
  -o model.gguf.part https://example.com/model.gguf

On Linux, wget -c with --timeout/--read-timeout provides similar stall protection semantics for large downloads.

All timeout values are tuned for typical network conditions while allowing for slower connections.


API Security

Client → LiteLLM (with API key) → llama-server (localhost only)

llama-server has no authentication by default. Use LiteLLM as your authenticated gateway for remote access.

Service-Specific

Service Auth Notes
Open WebUI Built-in Change admin password, disable signups
n8n Basic auth Use strong password, enable 2FA
llama-server None Keep localhost-only, use LiteLLM for remote
LiteLLM API key Set LITELLM_KEY in .env

Monitoring

# Watch for errors
docker compose logs -f llama-server | grep -i error

# Monitor resource usage
watch -n 5 'nvidia-smi; docker stats --no-stream'

Set up alerts for:

  • High GPU/CPU usage (possible abuse)
  • Failed auth attempts
  • Unusual network traffic

Updates

# Pull latest images
docker compose pull

# Recreate containers
docker compose up -d

Watch for security updates to: llama-server, Open WebUI, n8n, base images.


Pre-Deployment Checklist

  • Ran installer (secrets generated)
  • No default passwords remain
  • Firewall configured
  • TLS enabled (if network-accessible)
  • Rate limiting configured
  • Backups scheduled
  • Credentials documented securely

Reporting Security Issues

Found a vulnerability?

  1. Do NOT open a public issue
  2. Email: security@lightheartlabs.com
  3. Include: description, reproduction steps, potential impact

We'll respond within 48 hours.


Security is a shared responsibility. When in doubt, keep it local.