.env.example is the canonical list; this page is its documented form. Settings are grouped by what they govern, and each required variable states what happens if it is absent.

Required

Four variables must be present in a deployed environment. Two of them are also required in development, and two are generated for you there. Generate both secrets with openssl rand -hex 32. JWT_SECRET_PREVIOUS (optional) is set only while rotating JWT_SECRET: the old secret, which still verifies the tokens it signed — each token names its key in a kid header — but never signs a new one. Set it to the old value when you install the new JWT_SECRET, keep it for REFRESH_EXPIRY days (default 7), then remove it. Rotating without it logs every user out. It must be at least 32 characters and must differ from JWT_SECRET.

What happens when a secret is missing

It depends on ENV, and that is the point. In development or test, the backend generates 32 random bytes per process and logs loudly that it did so. There is no setup step, and sessions do not survive a restart. A previously hardcoded default for JWT_SECRET — the same published literal the config layer defaulted to — meant anyone who read the repository could forge a token for any user. That was a real security finding, and generating an ephemeral key is what replaced it: a key that does not exist cannot leak. In any other environment, a missing or weak secret is a fatal startup error. Values equal to or prefixed by a known placeholder are refused at any length, in any environment, because a placeholder that once shipped in a repository is public by definition. TOKEN_ENCRYPTION_KEY is a master key from which the cursor-signing key, the webhook signing secret and the secret-finding salt are all derived. Setting one value sets all four, so a deployment cannot forget one and silently fall back to an empty secret. Rotating it makes every stored provider token unreadable and forces users to reconnect their accounts; there is no key-rotation mechanism yet.

Server

Frontend origins

DASHBOARD_URL is also the whole CORS allow-list, and CORS permits credentials, so it matters more than a display URL would. WEBSITE_URL is not on the allow-list: the website never calls the API, and because the website and the API are usually same-site, listing it would let any script on the website read authenticated API responses. There is deliberately no wildcard option. A previous AllowOrigins: "*" let any origin call the API with a stolen bearer token, which combined with a token readable from localStorage turned any XSS into a full account takeover. Sessions are HttpOnly cookies with SameSite=Lax, which imposes a deployment constraint: the API and the dashboard must share a registrable domain. api.example.com and app.example.com do; example.com and example.net do not. The default arrangement proxies the API under the dashboard’s own origin, which is same-site trivially.

Sessions and tokens

Data

Redis is an optimisation, not the system of record. Job state lives in PostgreSQL, so losing Redis costs latency rather than work — see Scaling.

Scanning

See Scanning for how these interact.

Registry policy

The private-address rule is checked on the target when a scan is requested, and again on every connection the scan makes, after the name is resolved. See Scanning. These are about network reachability, not authentication. Credentials for registries that require a login are stored per registry host through /api/v1/registry-credentials and are not configured by environment variable — a secret in a process environment is a secret in docker inspect, in a crash dump and in every child process.

Vulnerability database

See Vulnerability database, which is where the reasoning behind two thresholds belongs.

Threat intelligence: KEV and EPSS

The two feeds the risk score reads. The worker fetches each on a schedule, stores it, and re-applies it to every stored finding. Nothing is fetched at startup, and a failed fetch never stops the process: the previous data stays in force, the error is recorded, and the data is flagged stale as it ages. GET /api/v1/system/status reports each feed’s source, version, age, staleness and last error. A URL source goes through the same SSRF-guarded transport as webhook delivery, so it cannot reach a private or link-local address. An in-perimeter mirror is served with the file source. See Risk scoring.

Rate limiting and webhooks

The rate limit is protection, not billing — nothing here meters usage or prices a plan. It is generous on purpose: a pipeline polling a scan every two seconds for ten minutes makes three hundred requests, and a limiter that interrupts legitimate work teaches customers to retry harder. WEBHOOK_ALLOW_PRIVATE_DESTINATIONS logs a prominent warning when enabled, and the server refuses to start with it set when ENV is not development. Even when set, the link-local range 169.254.0.0/16 — which hosts cloud instance metadata — stays blocked.

Observability

Metrics are cross-tenant aggregates, so binding this to 0.0.0.0 publishes facts about your business to anything that can route to the host. See Health and metrics.

Identity providers

Optional. Configure only the providers you intend to offer. Redirect URIs default to http://localhost:8080/api/v1/auth/<provider>/callback and must match what is registered with the provider.

Outbound mail

Optional. With no relay, address verification, password reset, invitation mail and email alerts answer that mail is not configured (503, mail_unavailable: true); invitations still work, and the administrator is handed the link to send. See Send mail through your SMTP relay. A half-configured relay — any SMTP_ variable without SMTP_HOST, or a host without SMTP_FROM — stops the process at startup. Links in mail point at DASHBOARD_URL.

Settings that no longer exist

S3_* were declared but never read, and were removed. If you find them in an old .env, they do nothing. (SMTP_* were removed the same way and have since returned with real meaning; see above.) The MinIO service they configured was removed along with them — it is AGPL-3.0, the one copyleft licence whose network clause reaches a service, and an unused dependency is cheap to delete and expensive to explain in a compliance review.