Full Configuration Reference
Every field validated by Pydantic at startup, with its default. See Configuration for a faster path to a working setup, or Environment Variables for the override list.
Full reference
Section titled “Full reference”storage: backend: "mongodb" # mongodb | memory mongodb: uri: "mongodb://user:pass@host:27017/db?authSource=db" database: "mcp_analyzer" # collection names below all have defaults — override only if you need to
cors: allow_origins: ["https://your-app.example.com"] # ["*"] for dev allow_credentials: true allow_methods: ["GET", "POST", "PUT", "PATCH", "DELETE"] allow_headers: ["*"]
auth: api_key: "change-me-to-a-strong-secret" # null → disabled (anonymous default-org owner)
jwt: # secret: "set-me-in-production" # random per-process if omitted refresh_ttl_days: 14 # reserved; refresh-token rotation not yet implemented
# session.access_ttl_minutes isn't set here — it's platform-managed, see below
two_factor: # secret_encryption_key: "set-me-in-production" # random per-process if omitted
rate_limit: enabled: true # the thresholds are NOT set here — they're platform-managed, see below
llm: # enabled is NOT set here — it's platform-managed, see below # credential_encryption_key: "set-me-in-production" # random per-process if omitted
deployment: mode: "self_hosted" # self_hosted | cloud — see belowThat’s the entire file — logging, registration, server, session, mcp, telemetry,
retention, jobs, analysis, org_offboarding, and github are deliberately absent, and
rate_limit appears with only its on/off switch; see below.
The example configs shipped in each repo (OpenTremor-core/configs/opentremor-core*.yaml,
OpenTremor-platform/configs/opentremor-platform*.yaml) point here rather than restating any of
it — this page is the single source of truth for which sections a config file can set.
Platform settings — admin-editable subset
Section titled “Platform settings — admin-editable subset”logging, registration, server, session, mcp, telemetry, retention, jobs,
analysis, org_offboarding, and github are not read from this file or from any
environment variable. Their only possible baseline is a fixed, hardcoded default
(the Pydantic field default shown in each section below) — create_app() resets these sections
to that default the moment the config is loaded, discarding whatever the file/env contained for
them, before anything else in the process reads them. The only way to change one is
GET/PATCH /admin/settings (superadmin only — the dashboard’s Platform Admin → Settings
page).
This is deliberate, not a limitation: previously, an admin-editable field with no override yet simply reflected whatever the local YAML happened to contain — which meant two instances that had never been touched via the admin UI could show different “effective” values purely because they shipped with different config files, with no way to tell a deliberate choice from incidental yaml drift. Resetting these sections to one fixed, documented default removes that ambiguity by construction: there is exactly one baseline, and exactly one way to change it.
Covered — hardcoded default + live database override, no restart, ever: logging,
registration, server, session, rate_limit (thresholds only), retention, jobs,
analysis, org_offboarding, github. A PATCH
takes effect for the very next request (for session.access_ttl_minutes, the very next
login/session-switch — it doesn’t retroactively extend a cookie a user is already holding; for
retention.telemetry_days/retention.audit_days, immediately — pushed live onto the running
MongoDB TTL indexes via collMod, not just re-read on the next request). See
Data Retention for retention’s own field-by-field detail,
Platform Admin — Running a stuck-job sweep manually
for jobs’s, and Deleting an organization for
org_offboarding’s — all three are covered here only at the same summary depth as every other
section.
Excluded — genuinely file/env-driven (unaffected by any of the above):
| Section | Why |
|---|---|
storage | The MongoDB connection string can’t be stored in the database it points to. A live cutover to a different target is possible, just through a dedicated mechanism instead: POST /admin/storage/switch — see Platform Admin — Switching the storage backend live. |
cors | Security-critical — changing it live from an authenticated session is exactly what shouldn’t be possible. |
auth | api_key is a bootstrap credential that must work independently of any stateful system, including this API; default_admin_email/default_admin_password only take effect on first boot anyway. Ongoing auth management happens through POST /admin/users/{id}/superadmin, org invites/roles, SSO connections, and service accounts instead — see Authentication. |
jwt | Rotating the session-signing secret invalidates every existing session across every replica. (Session lifetime is a separate, covered section — see session below; only the signing secret itself is excluded here.) |
two_factor | Rotating the secret encryption key makes every already-enrolled user’s TOTP secret unreadable without a re-encryption step. |
rate_limit.enabled | One field, not a section — the rest of rate_limit is editable. Whether brute-force protection runs at all is the single change a stolen superadmin session would most want to make, and the one an operator couldn’t undo from inside a compromised deployment. |
deployment | Identifies which deployment this process is (the operator’s cloud instance vs. a customer’s self-hosted one), not a business setting an authenticated admin of either kind should be able to flip — see deployment below. |
Excluded — fixed at the hardcoded default, not configurable by any means (not file, not env, not the admin API either):
| Section | Why |
|---|---|
mcp | Read once, synchronously, inside create_app() itself — the MCP ASGI app is mounted (or not) before the FastAPI app object even exists for a lifespan to attach to, let alone run an async database read. There is no point in the process’s life at which a database override could reach this section before it’s already acted on. |
telemetry | Same reason as mcp — TelemetryMiddleware’s enabled flag is constructed from it before an override could ever apply. (The TTL/retention duration for the spans it produces is a separate, covered section — see retention below and Data Retention; only the on/off switch itself is excluded here.) |
See Platform Admin — Settings for the full endpoint reference,
Platform Admin — Preparing MongoDB for the
POST /admin/storage/prepare-mongodb action that replaces tools/init_mongo.py with a
dashboard button, and Platform Admin — Switching the storage backend live
for actually cutting the running process over to a prepared target.
Fields
Section titled “Fields”logging
Section titled “logging”| Key | Default | Description |
|---|---|---|
level | info | Log verbosity: debug, info, warning, error |
Not set in this file — level above is the fixed baseline; change it live via
PATCH /admin/settings (logging_level) — see Platform settings.
storage
Section titled “storage”| Key | Default | Description |
|---|---|---|
backend | memory | mongodb or memory |
mongodb.uri | — | Full MongoDB connection URI (required when backend is mongodb) |
mongodb.database | — | Target database |
| Key | Default | Description |
|---|---|---|
allow_origins | ["*"] | Allowed origins — restrict to known callers in production |
allow_credentials | true | Allow cookies / auth headers |
allow_methods | ["*"] | Allowed HTTP methods |
allow_headers | ["*"] | Allowed request headers |
| Key | Default | Description |
|---|---|---|
api_key | null | Bootstrap admin key. null disables authentication entirely |
default_admin_email | null | Human superadmin account, created once at startup if set |
default_admin_password | null | Password for default_admin_email (min. 8 characters — shorter values are silently skipped, not rejected) |
auth.api_key is the bootstrap key — it always resolves to owner of the default org (and platform superadmin — see Platform Admin) and is never stored in the database. Additional keys and human accounts are created at runtime (POST /auth/keys, POST /auth/register) and scoped to whichever org created them — see Authentication.
auth.default_admin_email/default_admin_password create a ready-to-use human login the first time the server starts with them set — idempotent (only acts if no user with that email exists yet, so it’s safe to leave in a config that’s applied on every restart). This is the declarative counterpart to grant_superadmin.py: convenient for a fresh install where running a separate script against a live database isn’t the point.
When auth is enabled, every endpoint except GET /health requires either:
X-API-Key: <key>or a session cookie / bearer token from POST /auth/session.
registration
Section titled “registration”| Key | Default | Description |
|---|---|---|
allow_public_org_creation | true | Whether anyone can create a new organization unilaterally |
When set to false, two things 403 for everyone except a superadmin: POST /auth/register with
org_name set (and no invite_token), and POST /orgs (an already-logged-in user creating an
additional org). Two things are unaffected either way, since neither creates a new
organization: registering with neither org_name nor invite_token (an org-less account — see
Invitations), and registering with invite_token (joining an
existing org an invite grants access to). Use this to force invite-only growth on a
publicly-reachable instance without touching who can register an account at all.
Not set in this file — change it live via PATCH /admin/settings
(registration_allow_public_org_creation), no restart — see
Platform settings above.
server
Section titled “server”Not set in this file at all — every field below is a fixed baseline, changed only via
PATCH /admin/settings (server_* fields), no restart. On a fresh deployment that needs
dashboard_base_url/public_base_url set from day one (see the reverse-proxy note below), that
means one PATCH /admin/settings call right after first boot instead of a config-file line.
| Key | Default | Description |
|---|---|---|
max_input_size_mb | 10 | Maximum ingest/analyze body size (1–500 MB) |
sync_analysis_max_units | 5 | POST /{analyzer}/{namespace}/analyze runs inline (sync, 200) at or below this many ingested units; above it, a background job is created instead (202) |
public_base_url | null | Public URL used for external links in reports (e.g. the HTML report link in markdown_light), and to build the GitHub App manifest flow’s webhook/redirect URLs (see GitHub App Integration). When null, report links fall back to X-Forwarded-Proto/X-Forwarded-Host headers, then request.base_url — but the manifest flow has no such fallback (there’s no in-flight request to derive it from at redirect time) and returns 501 until this is set. |
dashboard_base_url | null | Where the dashboard is reachable — used only to build the redirect target after the GitHub App manifest flow’s callback. Falls back to public_base_url when unset, which is correct for the default same-origin, path-prefixed deployment; only set this explicitly if the dashboard is on a separate Service/ingress path. |
telemetry
Section titled “telemetry”Controls the built-in metrics and tracing system.
| Key | Default | Description |
|---|---|---|
enabled | true | Collect and expose telemetry spans |
When enabled, every HTTP request is recorded as a span, and enriched domain spans are recorded on ingest and analysis submission. Spans are queryable via GET /metrics/* (org-admin only, org-scoped).
retention
Section titled “retention”TTL/purge policy for telemetry spans, the audit log, findings, and raw analyzed resource content.
Covered by PATCH /admin/settings — thin summary table only; see
Data Retention for the full policy this backs (why each field has the
lifecycle it does, how TTL-based vs. sweep-based fields differ in when they take effect, and the
severity/status tiering for findings).
| Key | Default | Bounds |
|---|---|---|
telemetry_days | 30 | 1–365 |
audit_days | 730 | 30–3650 |
finding_low_medium_days | 180 | 1–3650 |
finding_high_critical_days | 730 | 1–3650 |
resource_content_days | 90 | 1–3650 |
sweep_enabled | true | — |
sweep_interval_hours | 24 | 1–168 |
telemetry_days/audit_days are pushed live onto the running MongoDB backend’s TTL indexes via
collMod the moment they’re PATCHed — no restart, no re-prepare. The other fields govern the
retention sweep (POST /admin/retention/run), normally triggered by
OpenTremor-task-scheduler’s heartbeat rather than this endpoint’s
value alone. Ignored by the in-memory backend the same way every other Mongo-specific setting is.
Timeout/cadence policy for the stuck-job sweep. Platform-owned: job tracking is generic
infrastructure, not analysis-specific. There is no durable job queue (an async .../analyze
job is a plain in-process background task), so a process crash or restart mid-run otherwise
leaves a job frozen at queued/running forever. Covered by PATCH /admin/settings;
see Platform Admin — Running a stuck-job sweep manually
for the full endpoint reference.
| Key | Default | Bounds |
|---|---|---|
stuck_timeout_minutes | 60 | 5–1440 |
sweep_enabled | true | — |
sweep_interval_hours | 1 | 1–168 |
stuck_timeout_minutes is how long a job can go without a progress update (updated_at, stamped
on every status transition and progress report) before the sweep (POST /admin/jobs/sweep)
presumes it orphaned and marks it failed — resumable afterward via POST /jobs/{job_id}/retry. sweep_enabled/sweep_interval_hours govern the sweep itself, normally
triggered by OpenTremor-task-scheduler’s heartbeat rather than this
endpoint’s value alone, same shape as retention above.
analysis
Section titled “analysis”How the server-side analysis loop drives the LLM — the only section owned outright by the
analysis engine rather than the platform. (llm, below, is about access: whether the feature
is on and what key encrypts per-org credentials. That’s generic; this isn’t.) Not set in the
config file — every field is a fixed baseline changed via PATCH /admin/settings
(analysis_* fields), no restart, which matters because these are exactly the knobs you reach
for while a provider is misbehaving. See
Architecture — the drain loop for what each one bounds.
| Key | Default | Bounds | Description |
|---|---|---|---|
max_concurrent_units | 4 | 1–32 | Units analyzed in parallel within one run |
llm_timeout_seconds | 120.0 | 5–600 | Per-call deadline handed to the provider SDK |
llm_max_attempts | 3 | 1–6 | Total attempts per unit, not retries on top of one — 1 disables retrying |
llm_retry_backoff_seconds | 1.0 | 0.1–30 | Base delay for exponential backoff with full jitter |
max_concurrent_units is a per-run bound: N concurrent analyze jobs can have N × this many
calls in flight. That’s deliberate — the backstop against runaway spend is the organization’s
monthly LLM budget, which the loop honours per unit. Raising it
past what your provider’s rate limit allows makes 429s (retried, but not free in wall clock) more
likely before it makes anything faster.
llm_timeout_seconds replaces the provider SDK’s own default, which is far longer than a job’s
useful deadline — Anthropic’s is 10 minutes. A timed-out call is treated as transient and
retried, not fatal.
Only transient failures are retried: 408/409/429 and 5xx, plus timeouts and dropped connections. An invalid API key, a rejected schema or an unparseable response fails the run on the first attempt, because asking again reproduces it exactly.
org_offboarding
Section titled “org_offboarding”The grace period between DELETE /admin/organizations/{org_id} scheduling an organization’s
deletion and it becoming permanent. Covered by PATCH /admin/settings; see
Platform Admin — Deleting an organization for the
full endpoint reference and exactly what does and doesn’t survive a purge.
| Key | Default | Bounds |
|---|---|---|
grace_period_days | 30 | 1–365 |
Read fresh per request, same as every other covered section — a change applies to any
still-pending organization’s computed purge_after immediately, and to
POST /admin/organizations/purge-pending’s next run.
Controls the FastMCP endpoint mounted alongside the REST API.
| Key | Default | Description |
|---|---|---|
enabled | true | Mount the MCP server at mount_path |
base_url | http://localhost:8000 | URL FastMCP uses for internal HTTP calls back to FastAPI |
mount_path | /mcp | Path prefix for MCP endpoints (/mcp/sse, POST /mcp/) |
base_url is always http://localhost:8000 in every environment (bare-metal, Docker,
Kubernetes) — FastMCP calls back to the same process via loopback, no service-level routing
needed, so this never needs to be anything else.
Signs human-user session tokens (POST /auth/session, POST /auth/register).
| Key | Default | Description |
|---|---|---|
secret | random per-process | HS256 signing secret. Must be set explicitly (and shared across every replica) in any real deployment via JWT_SECRET — otherwise sessions won’t validate across pods, and every restart invalidates all logged-in sessions |
refresh_ttl_days | 14 | Reserved — refresh-token rotation isn’t implemented yet |
session
Section titled “session”Session token lifetime — kept separate from jwt so it can be platform-managed (live-editable via
PATCH /admin/settings) without also exposing jwt.secret, whose rotation invalidates every
existing session across every replica.
Not set in this file or via env vars — change it live via PATCH /admin/settings
(session_access_ttl_minutes), no restart — see
Platform settings above. Takes effect for newly
issued sessions only (login, register, session-switch); doesn’t retroactively extend a session a
user is already holding.
| Key | Default | Description |
|---|---|---|
access_ttl_minutes | 480 | Session token lifetime (1–1440) |
A platform section despite the name — see Platform architecture — LLM. It carries the base only: whether the deployment offers LLM-backed features, and the key their secrets are encrypted under. The provider clients belong to the product.
| Key | Default | Description |
|---|---|---|
enabled | true | Admin-editable — Platform Admin → Settings → General → LLM Service, or PATCH /admin/settings {"llm_enabled": false}. Whether this deployment offers LLM-backed features at all. Like every admin-editable field, a value in the config file is discarded at boot; the API is the only way to change it |
credential_encryption_key | random per-process | File/env only, and never returned by the API. Encrypts secrets at rest (Fernet): SSO connection client secrets, per-org LLM provider API keys, and the GitHub App private key. Same must-be-set-in-production rule as jwt.secret via LLM_CREDENTIAL_KEY — otherwise stored secrets become unreadable across restarts/replicas |
llm is the one section split across the two regimes: enabled is admin-editable, the key is
not. That is a per-field distinction, not a per-section one — the key is registered nowhere, so
it is absent from the PATCH body model (sending llm_credential_encryption_key is a 422),
absent from effective in GET /admin/settings, and left at its file/env value rather than
being reset to a fresh per-process default at boot.
The key encrypts storage, not any particular provider — it’s shared across whichever of anthropic/openai/mistral an org stores a credential for. See Server-Side Analysis.
With enabled: false, these return 501: POST /{analyzer}/{namespace}/analyze,
POST /{namespace}/analyze/auto, and POST /orgs/{org_id}/llm-credentials. Listing and
deleting stored credentials deliberately keep working, so an operator who turns the feature off
can still see and clean up what organizations left behind. Ingest, analyzer listing and the
client-led analysis workflow are unaffected — none of them call a provider.
two_factor
Section titled “two_factor”| Key | Default | Description |
|---|---|---|
secret_encryption_key | random per-process | Encrypts enrolled users’ TOTP secrets at rest (Fernet). Same must-be-set-in-production rule as jwt.secret and llm.credential_encryption_key via TWO_FACTOR_SECRET_KEY — otherwise every already-enrolled secret becomes unreadable across restarts/replicas |
File/env only, like the other two encryption keys — see the exclusion table in
Platform settings above. Note this is only the
encryption key: whether an org requires 2FA is per-org data
(PATCH /admin/organizations/{org_id}/2fa-policy), not config. See
Two-Factor Authentication.
rate_limit
Section titled “rate_limit”Attempt budgets for the endpoints that verify a credential.
This section is split: the five thresholds are admin-editable, enabled is file/env only.
Change the thresholds at Platform Admin → Settings → Security (or PATCH /admin/settings) —
they apply to the very next request, no restart, and anything a config file says about them is
discarded at boot like every other admin-editable section. enabled is the opposite: it is read
from the file, is never returned by GET /admin/settings, and PATCH rejects it with a 422.
The asymmetry is the point. Tightening or loosening a threshold is an ordinary operational
decision, and the bounds below cap how loose “loose” can get. Turning the protection off
altogether is the single change a stolen superadmin session would most want to make, and the one
an operator couldn’t undo from inside a compromised deployment — so it requires reaching the
deployment’s config, alongside the ingress rules that are the other half of the same protection.
(The llm section is split the same way and in the opposite direction, for the mirror-image
reason — there the toggle is the safe live decision and the secret is the dangerous one.)
| Key | Default | Editable | Description |
|---|---|---|---|
enabled | true | file/env only | Whether the application layer runs at all. Only sensible to turn off when something in front already does this (an API gateway, WAF, Cloudflare) — off means unlimited failed attempts |
login_max_attempts | 10 | ✅ | Failed logins per account (1–1000) before POST /auth/session stops checking the password at all. Cleared by any successful login |
login_window_minutes | 15 | ✅ | The window that budget is measured over (1–1440) |
mfa_max_attempts | 5 | ✅ | Second-factor attempts per account (1–100), over a fixed 15-minute window. Counts correct codes too — a challenge ends either way. Cleared by a successful second factor |
ip_max_attempts | 60 | ✅ | Failed credential presentations per source IP (1–10000), across every credential type at once: login, API key, SCIM bearer token, invite and public-report link tokens |
ip_window_minutes | 15 | ✅ | The window for the per-IP budget (1–1440) |
Exceeding a budget answers 429 with a Retry-After header. The response to the attempt that
crosses a threshold is unchanged (a 401 stays a 401), so nothing in the API reveals where the
line sits — only the next attempt is turned away.
Two budgets, keyed differently, on purpose. The per-account ones are the half a distributed password-spray cannot dodge by rotating source addresses; the per-IP one is the catch-all for the guessing surfaces that have no account to key a budget to. A success clears an account’s budget but never the IP’s — that one is keyed by something many unrelated users share (a NAT, an office egress), so letting a success reset it would hand an attacker a free reset by authenticating to their own account.
github
Section titled “github”Configures the platform’s built-in GitHub App integration — the fallback identity used by orgs that install the shared App rather than registering their own via the manifest flow (POST /orgs/{org_id}/integrations/github/manifest). Orgs using the platform App “install” it and register their own installation_id separately (POST /orgs/{org_id}/integrations/github); orgs using their own App need none of this section set at all.
Not set in this file or via GITHUB_* env vars — change it live via PATCH /admin/settings
(github_app_id/github_private_key/github_webhook_secret), no restart — see
Platform settings above.
| Key | Default | Description |
|---|---|---|
app_id | null | GitHub App ID |
private_key | null | App’s PEM private key (RS256), used to sign App-level JWTs |
webhook_secret | null | HMAC-SHA256 secret configured on the App’s webhook — verifies X-Hub-Signature-256 |
deployment
Section titled “deployment”| Key | Default | Description |
|---|---|---|
mode | self_hosted | self_hosted or cloud |
File/env only (DEPLOYMENT_MODE) — never admin-API-editable, even though nothing
structurally prevents it the way mcp/telemetry are prevented; it identifies which
deployment this process is, not a business setting an authenticated admin should
ever flip live.
self_hosted(the default — every OSS/quickstart/Helm config leaves this unset) — every org is unconditionally entitled to run every analyzer, regardless ofplan_tier/demo_analyses_used.cloud(set only on the operator’s own SaaS config) — an org needsplan_tier == "paid", or is still within its one-time, lifetime 100-analysis free demo. See Billing / quota enforcement for the full mechanism (controllers/entitlements.py).