Security¶
Network Security¶
Bind Address¶
lumen-argus binds to 127.0.0.1 by default — both the proxy (port 8080) and dashboard (port 8081). Non-loopback binds log a warning.
For Docker containers, use the --host CLI flag:
This overrides the bind address for both proxy and dashboard. Docker's port mapping (-p) then controls what's exposed to the host network.
Plain HTTP In, HTTPS Out¶
The proxy accepts plain HTTP on localhost (no TLS interception needed) and forwards to upstream AI providers over HTTPS. This means:
- No need to install custom CA certificates on the client
- No MITM — the proxy reads the request body, not the TLS stream
- Upstream TLS is validated against system CA bundles
Corporate Proxy / Custom CA¶
If behind a corporate proxy with custom certificate authority:
For development/testing only:
Warning
verify_ssl: false logs a warning on startup and should never be used in production.
Dashboard Security¶
Authentication¶
When dashboard.password is set (or LUMEN_ARGUS_DASHBOARD_PASSWORD env var), the dashboard requires authentication:
- Sessions: 8-hour timeout, stored server-side with
secrets.token_hex(32) - Cookies:
argus_session(HttpOnly, SameSite=Strict),csrf_token(SameSite=Strict) - CSRF: Double-submit cookie pattern with
secrets.compare_digest()on all mutations (POST/PUT/DELETE). GET requests are exempt. - Login redirect: Validates
nextparameter against open redirect (rejects//,\/) and CRLF header injection (strips\r,\n). Output URL-encoded viaurllib.parse.quote.
License Key Storage¶
License keys submitted via POST /api/v1/license are validated before writing:
- Maximum 4KB, no newline characters
- Saved to
~/.lumen-argus/license.keywith0o600permissions - Read by Pro plugin on startup and SIGHUP reload
Plugin Trust Model¶
Dashboard extensions are loaded from pip-installed entry points only:
jsfield: Injected as raw<script>blocks server-side. Treated as trusted code — same trust level as any pip-installed package.htmlfield: Sanitized client-side via_safeInjectHTML()which strips<script>tags and allon*event handlers before DOM insertion.- API handler: Plugin API handler runs server-side with full access to the analytics store and audit reader.
Only install plugins from trusted sources.
Thread Safety¶
- AnalyticsStore: Thread-local SQLite connections, WAL mode, write serialization via
threading.Lock - AuditReader: Cache protected by
threading.Lockfor concurrent dashboard requests - SSEBroadcaster: Async queue-based;
broadcast()usesput_nowait()(thread-safe, callable fromasyncio.to_threadcontext); defensive list copy before iteration - AsyncDashboardServer:
aiohttp.webin proxy's event loop; session storage accessed from single event loop (no lock needed); community API handlers offloaded viaasyncio.to_thread()
Data Security¶
Matched Values Never Persisted¶
The actual secret/PII value (Finding.matched_value) is kept in memory only:
- Audit log: excluded from
AuditEntry.to_dict()serialization - Application log: only finding type and count are logged, never values
- Metrics: only aggregated counters by type
- Baseline files: SHA-256 hash of the value, not the value itself
This prevents the proxy from becoming a secondary exfiltration vector. If logs are shared with support, no secrets leak.
File Permissions¶
| Resource | Permissions | Method |
|---|---|---|
| Application log | 0o600 |
Atomic via os.open() with O_CREAT |
| Rotated log files | 0o600 |
Secured on rotation via doRollover() |
| Log directory | 0o700 |
Created with os.makedirs(mode=0o700) |
| Audit log | 0o600 |
Atomic via os.open() with O_CREAT |
| Analytics DB | 0o600 |
os.chmod() on every startup |
| License key | 0o600 |
os.chmod() after write |
Connection Isolation¶
Connection pooling is scoped per-host — connections to api.anthropic.com are never reused for api.openai.com. Authorization headers are forwarded only to the intended provider.
Backpressure¶
The proxy.max_connections setting (default: 10) limits concurrent upstream connections using a semaphore. This prevents:
- File descriptor exhaustion under heavy parallel sub-agent usage
- Overwhelming upstream providers with too many connections
- Thread contention on shared resources
When the limit is reached, requests are queued and a WARNING is logged.
Graceful Shutdown¶
On SIGINT/SIGTERM, lumen-argus:
- Stops accepting new connections
- Waits up to
proxy.drain_timeoutseconds for in-flight requests - Force-closes remaining connections if timeout reached
- Logs shutdown summary