Troubleshooting¶
Organized by area. Each entry: what you see → why → what to do.
Installation and startup¶
Backend exits: ENCRYPTION_KEY must be explicitly set when
APP_ENV='production'
Expected fail-fast. Set a valid Fernet key in .env (generation
command is in the error message).
Backend exits: invalid format (not a valid Fernet key)
Typo in ENCRYPTION_KEY. Regenerate; if data already exists under an
old key, restore it, don't overwrite blindly.
password authentication failed for user "umbryx" on startup
DB_PASSWORD changed after the PostgreSQL volume was initialized.
Either restore the old password or reset the volume
(docker compose down -v — destroys data).
App unreachable on :3000
docker compose ps for service states; check the published PORT
and reverse-proxy forwarding.
Configuration¶
Sessions drop after every restart
JWT_SECRET unset — auto-generated per start when persistence
fails. Set it explicitly.
CORS errors after moving the UI to another origin
Set CORS_ORIGINS to the UI origin (never *; credentials are in
use).
WS behind a reverse proxy doesn't update live
The /ws/ location needs HTTP/1.1 upgrade headers and long read
timeouts (the shipped nginx configs show the working pattern). The
frontend falls back to polling meanwhile.
Database and migrations¶
Migration error on startup
Code/database version mismatch (e.g. DB restored from a newer
backup). Restore matching versions or consciously
alembic stamp <revision>.
Slow lists on huge projects
Raise DATABASE_STATEMENT_TIMEOUT cautiously; prefer attaching a
read replica (DATABASE_REPLICA_URL).
Authentication and authorization¶
401 everywhere after logout elsewhere Refresh tokens rotate on use and are blacklisted on logout (with Redis). Re-login.
403 on actions a role "should" be able to do
Check the actual matrix — e.g. findings.approve belongs to
reviewer only; credentials.read_secret to owner/admin/editor/
operator. See Access control.
First-run setup screen appears on an instance with users
You are hitting a different database than you think (check
DATABASE_URL / volume mounts) — setup only renders when the users
table is empty.
Scans and execution¶
Scan fails: no attacker host / 404 on attacker_ssh No executor configured. Infra → Connections → add an attacker SSH target (or enable the executor flag on a pool workstation). The attacker_ssh module must be enabled.
No usable SSH credential found for attacker host
The credential exists but isn't linked to that attacker host, or the
host isn't marked attacker-side. Fix the linkage in Connections.
Job stuck in queued
Worker pool exhausted or WORKER_BACKEND=arq without the worker
service. Check System → Health and the worker badge in Jobs.
Distinct from awaiting ROE approval, which needs an owner/admin to
approve/reject.
Job fails: Blocked by ROE/scope guard
Target outside scope or a forbidden activity/time window. Adjust ROE
or scope; approval re-checks on approve.
Data and encryption¶
Cannot read encrypted value / decryption errors
ENCRYPTION_KEY differs from the one that wrote the data. Restore
the original key or re-encrypt via backend/scripts/rekey.py
(dry-run first). Without the old key the data is unrecoverable.
Encrypted ZIP import asks for a password Exports containing credential secrets are AES-encrypted with a one-time password shown at export time. Lost password = re-export from the source instance.
Loot shows "No inline preview" Office/database formats are download-only by design. File decryption for preview is platform-admin gated.
Reports and limits¶
PDF button returns an HTML file WeasyPrint missing in that backend image — the server falls back to HTML. Install/enable the PDF-capable image.
429 Too Many Requests Built-in limits: report exports 10/min, login 5/min (plus account lockout), credential reveal 30/min, webhooks 120/min.
Licensing (EE)¶
402 on writes, banner about wind-down License expired past grace. Upload a renewed token (System → License); data and teardown remain available meanwhile.
Heartbeat logs License Server unreachable
Network path to LICENSE_SERVER_URL blocked. Harmless until
contract expiry; air-gapped installs should switch the channel to
offline.
Token rejected on upload The token is bound to another installation's hardware/instance key — re-activation on a new host requires a new C2V round-trip.