Configuration

All Admina settings are configured via admina.yaml or environment variables. A few settings accept both forms โ€” the PII engine, for instance, is the top-level pii_engine: key in admina.yaml (a sibling of forensic_store: and auth_provider:, not nested under domains) or the ADMINA_PII_ENGINE environment variable; where both are set, the environment wins. Others, such as ADMINA_GUARD_FAIL_MODE, the egress mode and most gateway limits, are environment-variable-only, and the firewall's pattern rules are admina.yaml-only. Secrets are auto-generated on first launch and stored in an encrypted vault (.admina/secrets.json). The defaults are tuned so that the full Docker Compose stack lands in the OISG adequate band out of the box, and a bare pip install lands at 75, in the Good coverage band; turning any capability off immediately lowers the score. There are four supported bootstrap paths:

# 1. CLI (recommended) โ€” local mode, no Docker
pip install admina-framework
admina init my-project
cd my-project && admina dev

# 2. Generate admina.yaml interactively (or non-interactively for CI)
admina configure                  # interactive wizard
admina configure --non-interactive # scaffolds defaults to ./admina.yaml

# 3. Docker Compose without the CLI
./scripts/bootstrap-secrets.sh   # writes random creds to .env
docker compose up --build

# 4. Manual
cp .env.example .env                # then fill in values
Upgrading from 0.12? v0.13.0 turns several lenient or silent behaviours into startup errors โ€” a wrong-typed value in admina.yaml, ADMINA_ENGINE=rust without admina-core, a filesystem / s3 forensic backend that cannot be opened โ€” and starts applying heuristic_threshold from files generated by older admina init releases. Read the upgrade guide (docs/guides/upgrade-0.13.md) before upgrading; the rows below flag each change with since v0.13.0.

Where admina.yaml comes from

Without ADMINA_CONFIG, the library loader looks for admina.yaml in the current working directory, then in the installed package root, and falls back to environment variables / .env when neither exists. Since v0.13.0, ADMINA_CONFIG=/etc/admina/admina.yaml names the file instead: the proxy, the SDK, the firewall overrides, the egress policy and the PII engine selection then read exactly that file, and a missing, unreadable or invalid file is a ConfigFileError โ€” the proxy does not start, there is no fallback to the defaults. The CLI is stricter than the library โ€” admina dev reads admina.yaml from the current directory only and exits if it is missing. To write one somewhere else, use admina configure -o /path/to/admina.yaml (the --output flag defaults to ./admina.yaml).

Schema check (since v0.13.0). The file is checked against its schema (schema_version: 1). A value of the wrong type โ€” a string where a list is expected, say โ€” is a ConfigSchemaError naming the key (admina.yaml ./admina.yaml: domains.agent_security.loop_breaker.window_size: must be an integer; for a file named by ADMINA_CONFIG it surfaces as a ConfigFileError carrying the same message) and the proxy does not start; the engine factories the SDK uses raise the same error. An empty value (key: with nothing after it) is not checked, nor are the free-form blocks (plugin_config, integrations, agent_security.domains, the entries of custom_patterns). An unknown key, typically a typo, is logged as a warning at startup (unknown keys, not read: โ€ฆ). The proxy also warns about ADMINA_* variables in its environment or .env that nothing reads (names only, never values).

Configuration loading (since v0.13.0)
VariableDefaultDescription
ADMINA_CONFIG โ€” Path of the admina.yaml to load. Unset or empty: the search above. Set: that file or a startup error. Explicit load_config(yaml_path=โ€ฆ) / search_paths= arguments still take precedence. admina redteam --config defaults to it.
ADMINA_CONFIG_STRICT false true turns the two startup warnings into errors: unknown admina.yaml keys (ConfigSchemaError) and unknown ADMINA_* variables (UnknownVariablesError) stop the proxy. Wrong-typed values stop it either way.
ADMINA_ENV_ALLOW_PREFIXES โ€” Comma-separated prefixes of ADMINA_* variables that belong to other components sharing the environment (e.g. ADMINA_MYAPP_); they are not reported. Each entry point of the admina.plugins, admina.pii_engines and admina.pattern_packs groups already allows ADMINA_<NAME>_.

Authentication

Admina ships with dashboard authentication enabled by default. Two credentials drive the platform:

  • ADMINA_API_KEY โ€” protects every governance endpoint. The only exempt paths are /health, /metrics, the OpenAPI docs (/docs, /redoc, /openapi.json) and the bundled dashboard shell (/, /heimdall.png, /vendor/*), which holds no credential. Since v0.13.0, ADMINA_METRICS_REQUIRE_AUTH=true and ADMINA_API_DOCS_REQUIRE_AUTH=true put /metrics and the docs behind the key too; otherwise /metrics serves Prometheus counters unauthenticated, so keep it off the public internet.
  • ADMINA_DASHBOARD_PASSWORD โ€” the HTTP Basic Auth password (user admin) of the Docker Compose dashboard container. bootstrap-secrets.sh and the CLI vault write the same value to CLICKHOUSE_PASSWORD and GRAFANA_ADMIN_PASSWORD, so one password opens every UI of the stack. The proxy itself does not read it.

Dashboard sign-in (since v0.12.1). The dashboard the proxy serves itself (at / on the proxy's port โ€” 3000 under admina dev, 8080 in Docker) asks for the API key once and exchanges it at POST /api/dashboard/session for a signed, HttpOnly, SameSite=Strict session cookie scoped to /api/. The session is accepted only for read-only requests to the dashboard API (/api/dashboard/*, /api/stats, the live feed); /mcp, the gateway and the integration and compliance APIs always need the key. Rotating the API key invalidates every session. The built-in apikey auth provider reads X-API-Key / Authorization: Bearer only, never cookies.

Both credentials can be managed via the CLI:

admina password show      # display current credentials
admina password reset     # regenerate all credentials
admina password set       # set a custom password

Include the API key in every request to a protected endpoint:

# Via header
curl http://localhost:8080/api/stats \
  -H "X-API-Key: $ADMINA_API_KEY"

# Via Bearer token
curl http://localhost:8080/api/stats \
  -H "Authorization: Bearer $ADMINA_API_KEY"

The key in the query string (?api_key=) is accepted only on the dashboard live-feed WebSocket, where browsers cannot set headers, and is deprecated since v0.13.0: the first use logs a warning, and a later release will refuse it.

For local development only, set ALLOW_UNAUTHENTICATED=true to disable auth. A warning is logged at startup. The docker-compose default is false.

Fail-closed since v0.10.0. A proxy started with no ADMINA_API_KEY and no auth providers now rejects protected requests (with a loud startup warning) instead of authenticating every caller as admin. To run without a key you must explicitly set ALLOW_UNAUTHENTICATED=true.
Guard fail mode (new in v0.11.0). This is a different fail-closed axis from the auth one above. ADMINA_GUARD_FAIL_MODE controls what happens when a pluggable governance guard raises an exception. The default open skips the guard and records it as an ERROR entry in the decision's checks; setting closed turns the exception into a BLOCK (also recorded as an ERROR check). It is enforced on the request side of three governed surfaces โ€” the SDK (GovernedModel.ask() and .stream()), the MCP proxy (POST /mcp) and the OpenAI-compatible gateway (POST /v1/chat/completions). Response-side inspection fail-closed is MCP-proxy-only, where a fail-closed block additionally writes an explicit forensic ERROR record. Two caveats worth stating plainly: only ValueError, RuntimeError, OSError and TypeError raised inside a guard are caught โ€” any other exception propagates; and a request-side fail-closed BLOCK is still downgraded to ALLOW under ADMINA_GOVERNANCE_MODE=observe or dry-run, where it is recorded as a shadow decision instead. An unrecognised value is rejected at startup (the proxy fails fast), and the SDK reads the variable once, at GovernedModel construction. On the gateway, a pipeline exception that is not a guard contract error blocks the request in every mode since v0.13.0.

Secrets from files

Since v0.13.0 each of these secrets can be given directly or as <NAME>_FILE, the path of a file holding it (a Docker or Kubernetes secret): ADMINA_API_KEY, ADMINA_FORENSIC_STATE_KEY, ADMINA_AUDIT_APPEND_KEY, ADMINA_GATEWAY_UPSTREAM_API_KEY and the per-route ADMINA_GATEWAY_UPSTREAM_<NAME>_API_KEY. The file is read once at startup with one trailing newline removed. A missing, unreadable, non-UTF-8 or empty file, or a secret set both directly and as a file, stops the proxy with an error that names the setting and the path, never the secret. ADMINA_FORENSIC_STATE_KEY_FILE inside the forensic directory is refused (SecretFileError): keep the key outside the store it signs.

All variables

The proxy reads its own settings from the process environment and from .env. A handful of variables are read from the process environment only โ€” a .env line takes effect for them only where something exports it, as Docker Compose's env_file does: ADMINA_CONFIG, ADMINA_ENGINE, ADMINA_OFFLINE, ADMINA_PII_ENGINE, ADMINA_PII_MASK_STYLE, ADMINA_PRESIDIO_NLP_MODELS, ADMINA_PATTERN_PACK_DIRS, ADMINA_FORENSIC_STATE_KEY (and _FILE), ADMINA_EGRESS_MODE, ADMINA_EGRESS_FINGERPRINT_KEY, ADMINA_SPACY_MODEL and ROUTING_CONFIG_PATH.

Proxy & Upstream
VariableDefaultDescription
UPSTREAM_MCP_URL http://localhost:9000 Default upstream MCP server URL
ADMINA_ENABLED_SURFACES โ€” (all) Comma-separated surfaces the proxy serves: gateway (/v1/*), mcp (/mcp), integration (/api/v1/*), compliance (/api/compliance/*), dashboard (/api/dashboard/*, /api/stats, /api/events, the dashboard shell and its sign-in). A disabled surface's routes are not mounted and answer 404 before authentication; /health and /metrics are always served. An unknown name stops the proxy. The loop breaker is built only with mcp or integration, the coordination detector only with mcp. With gateway alone, the proxy-minimal extra suffices (set REDIS_URL and CLICKHOUSE_HOST empty). Since v0.13.0.
ADMINA_MAX_REQUEST_BYTES 10485760 Largest request body on any route, in bytes (10 MiB; 0 = no limit). A larger body gets 413 before it is parsed. Since v0.13.0.
MAX_REQUEST_TOKENS 100000 Longest request content on /mcp only, estimated in characters (0 = no limit); longer requests get 413. The gateway's equivalent is ADMINA_GATEWAY_MAX_PROMPT_CHARS.
CORS_ORIGINS http://localhost:3000,http://localhost:8080 Comma-separated allowed CORS origins (also checked on the dashboard live-feed WebSocket)
LOG_LEVEL INFO Logging verbosity: DEBUG, INFO, WARNING, ERROR
ADMINA_LOG_FORMAT text text | json โ€” one JSON object per line (timestamp, level, logger, message, exception), uvicorn's lines included. Another value stops the proxy. Since v0.13.0.
ADMINA_ENGINE auto Governance-engine backend, uniform across proxy / SDK / integrations: auto | python | rust (since v0.10.0; unrecognized value raises). auto runs the Rust firewall and loop breaker when admina-core is installed, but the Python firewall whenever admina.yaml sets a Python-only firewall key; PII stays Python. Since v0.13.0, rust without admina-core, or with a Python-only firewall key set, is an EngineSelectionError and the proxy does not start (0.12 fell back to Python with a warning). See the Rust engine guide.
ADMINA_OFFLINE false true sets HF_HUB_OFFLINE, TRANSFORMERS_OFFLINE and HF_DATASETS_OFFLINE to 1 before a PII engine is built, and starts the proxy without the OpenTelemetry exporter. Accepts true/false, 1/0, yes/no, on/off; anything else raises. PII engines never download models in either mode. Since v0.13.0.
ROUTING_CONFIG_PATH โ€” Path to multi-upstream routing config (OpenClaw mode)
OpenAI-compatible gateway
VariableDefaultDescription
ADMINA_GATEWAY_UPSTREAM http://localhost:11434/v1 Upstream base URL when no named routes are configured (one route, default). Must include the API base path (typically /v1); the default points at a local Ollama.
ADMINA_GATEWAY_UPSTREAMS โ€” Named routes, name=url[,name=urlโ€ฆ] (names: 1โ€“64 lowercase letters, digits, _). When set, replaces gateway.upstreams of admina.yaml. A request picks a route with X-Admina-Upstream. Since v0.13.0.
ADMINA_GATEWAY_UPSTREAM_API_KEY โ€” Key sent upstream as Authorization: Bearer on every route without its own; or โ€ฆ_API_KEY_FILE. Per route: ADMINA_GATEWAY_UPSTREAM_<NAME>_API_KEY[_FILE], then the route's api_key_file, then this default. No key: no Authorization header. The caller's own credentials are never forwarded. Since v0.13.0.
ADMINA_GATEWAY_MODELS_ALLOWLIST โ€” Comma-separated model ids; empty lets every model through. Filters GET /v1/models and, since v0.13.0, also refuses a POST /v1/chat/completions for any other model (or none) with 403 before it is governed or forwarded.
ADMINA_GATEWAY_BLOCK_STATUS 200 200: a block is returned as a completion carrying the block message (finish_reason: content_filter). 403: an OpenAI-style error (governance_blocked). Any other value stops the proxy. Since v0.13.0.
ADMINA_GATEWAY_BLOCK_MESSAGE This request was blocked by the Admina governance policy. Text of a blocked request's synthetic completion (or SSE chunk), or of the 403 error message.
ADMINA_GATEWAY_MAX_PROMPT_CHARS 0 Longest message text, in characters (0 = no limit); longer gets 413 before governance. Since v0.13.0.
ADMINA_GATEWAY_FORWARD_FIELDS โ€” (all) Top-level body fields forwarded upstream; model, messages, stream always are, and so are n / max_tokens / max_completion_tokens while their limit below is set. The firewall still scans the body as received. Since v0.13.0.
ADMINA_GATEWAY_MAX_N 0 Largest n forwarded (0 = no limit); larger values are lowered. Since v0.13.0.
ADMINA_GATEWAY_MAX_COMPLETION_TOKENS 0 Largest max_tokens / max_completion_tokens forwarded (0 = no limit); a request with neither gets max_tokens set to it. Since v0.13.0.
ADMINA_GATEWAY_STREAM_MODE passthrough passthrough forwards upstream SSE bytes unchanged while PII redaction is off; governed parses and re-serialises each chunk. Wins over gateway.stream_mode. Since v0.13.0.
ADMINA_GATEWAY_TIMEOUT_CONNECT 30 Seconds to open an upstream connection (or wait for one from the pool); 0 = no limit. A timeout before the response starts gets 504. Since v0.13.0.
ADMINA_GATEWAY_TIMEOUT_READ 30 Seconds to wait for the next upstream bytes; 0 = no limit. Since v0.13.0.
ADMINA_GATEWAY_TIMEOUT_TOTAL 0 Seconds for the whole upstream exchange; 0 = no limit. Since v0.13.0.
ADMINA_GATEWAY_MAX_CONNECTIONS 100 Connection pool size of the gateway's upstream client (all routes). Since v0.13.0.
ADMINA_GATEWAY_MAX_KEEPALIVE_CONNECTIONS 20 Idle keep-alive connections kept in that pool. Since v0.13.0.
ADMINA_GATEWAY_PIPELINE_WORKERS 0 Worker threads running the governance pipeline โ€” the most requests governed at once (0 = number of CPUs). Since v0.13.0.
ADMINA_GATEWAY_PIPELINE_TIMEOUT 0 Seconds a request waits for its governance decision, queueing included (0 = no limit). Past it the request is blocked in every governance mode. Since v0.13.0.
ADMINA_GATEWAY_SCAN_RESPONSE false true also runs the firewall on completion text (needs the firewall on). Non-streaming completions flagged under enforce are replaced by the block message; streamed ones are only recorded. Since v0.13.0.
ADMINA_GATEWAY_SCAN_ROLES system,user,assistant,tool Message roles the firewall scans (empty = all four); messages with any other role are always scanned. Other body fields are always scanned. Since v0.13.0.
ADMINA_GATEWAY_SCAN_POLICY_ENABLED false Honour the X-Admina-Scan-Policy request header, which lets any caller holding the API key narrow the scan of its own requests. Turn on only when every such caller is trusted. Since v0.13.0.
ADMINA_GATEWAY_REQUEST_ID_HEADER โ€” Request header recorded as request_id in the forensic record (e.g. X-Request-Id). Since v0.13.0.
ADMINA_GATEWAY_RECORD_HEADERS โ€” Comma-separated request headers recorded in the record's context. Credential headers cannot be listed. Since v0.13.0.
ADMINA_GATEWAY_FORWARD_HEADERS โ€” Comma-separated request headers forwarded upstream (e.g. traceparent,tracestate); nothing else from the client is. Credentials, connection/body headers and X-Admina-* cannot be listed. Since v0.13.0.

Routes, the default route, the stream mode and the scan-policy allow-lists can also live in the top-level gateway section of admina.yaml (since v0.13.0). A malformed section โ€” a route without a url, a default_upstream that names no route, an unreadable key file โ€” stops the proxy at startup:

gateway:
  upstreams:
    main:
      url: "http://main.upstream.test/v1"
      api_key_file: /run/secrets/upstream_api_key
    util:
      url: "http://util.upstream.test/v1"
  default_upstream: main      # default: the first route
  stream_mode: passthrough    # ADMINA_GATEWAY_STREAM_MODE wins
  # tags a request may declare as already scanned (X-Admina-Scan-Policy)
  prescan_tags: []
  # rulesets (SHA-256) accepted in that header besides the proxy's own
  prescan_rulesets: []

Response codes, the X-Admina-* response headers, the scan-policy header syntax and the forensic records the gateway writes are documented under API Reference โ†’ OpenAI-compatible gateway. The ruleset hash covers the Admina version, so it changes with every upgrade (and its whole form changed in v0.13.0): after each release, recompute any value pinned in prescan_rulesets from GET /v1/admina/ruleset.

Authentication & dashboard
VariableDefaultDescription
ADMINA_API_KEY โ€” API key for all protected endpoints. Auto-generated by the vault or bootstrap-secrets.sh. Or ADMINA_API_KEY_FILE (since v0.13.0, see above).
ALLOW_UNAUTHENTICATED false Set true only for local development โ€” bypasses the API key check
ADMINA_AUDIT_APPEND_KEY โ€” A second key accepted by POST /api/v1/audit only (appending audit records); every other route refuses it. Unset: that route needs the API key. Or โ€ฆ_FILE. Since v0.13.0.
ADMINA_METRICS_REQUIRE_AUTH false true puts GET /metrics behind the API key. Since v0.13.0.
ADMINA_API_DOCS_ENABLED true false stops serving /docs, /redoc and /openapi.json (404). Since v0.12.1.
ADMINA_API_DOCS_REQUIRE_AUTH false true puts the OpenAPI docs behind the API key. Since v0.13.0.
ADMINA_DASHBOARD_ENABLED true false stops serving the bundled dashboard (/, /vendor/*, /heimdall.png) and its sign-in endpoint; dashboard.enabled: false in admina.yaml does the same. The /api/dashboard/* data API stays available with the API key. Since v0.12.1.
ADMINA_DASHBOARD_SESSION_TTL 3600 Lifetime of a dashboard browser session, 60 to 43200 seconds. A live-feed connection opened with a session is closed when it expires. Since v0.12.1.
DASHBOARD_COOKIE_SECURE false Secure flag of the session cookie over plain HTTP (always set over HTTPS). true: always โ€” e.g. TLS terminated by a reverse proxy that does not forward the scheme. auto (since v0.13.0): set unless the dashboard is addressed as localhost, *.localhost or a loopback address. Any other value stops the proxy.
ADMINA_DASHBOARD_PASSWORD โ€” Basic Auth password of the Docker Compose dashboard container (user ADMINA_DASHBOARD_USER, default admin); the bootstrap writes the same value for ClickHouse and Grafana.
Storage โ€” Redis
REDIS_URL redis://localhost:6379/0 Redis connection URL โ€” session state, rate limiting, hash chain, and (since v0.12.0) the coordination detector's fan-in counts, content sketches and quarantine set. Without Redis the detector cannot detect anything: every payload-bearing MCP call to an undeclared destination reports degraded and writes an extra forensic record. Empty: no Redis, and since v0.13.0 no connection attempt (the redis package is not even imported). admina dev local mode sets it empty by default. The egress allowlist itself needs no Redis.
Forensic Black Box โ€” backend selection
FORENSIC_BACKEND memory One of memory | filesystem | s3. When unset (environment and .env), the proxy reads domains.compliance.forensic.backend (or its older name storage) from admina.yaml (since v0.13.0); the environment wins and a conflict is logged. Since v0.13.0 a filesystem or s3 backend that cannot be opened no longer falls back to memory: see ADMINA_FORENSIC_FAIL_MODE. admina egress suggest-allowlist reads local forensic records, so an observation window meant to produce an allowlist needs filesystem.
FORENSIC_BASE_DIR โ€” Required when the backend is filesystem (explicit opt-in, no default); falls back to domains.compliance.forensic.base_dir in admina.yaml (since v0.13.0).
ADMINA_FORENSIC_FAIL_MODE open What happens when a forensic record cannot be written. open: logged, request served without its record; a backend that cannot be opened starts as a store that records nothing (/health: forensic_writable: false, status: degraded). closed: the request is answered 503 and not forwarded, and a backend that cannot be opened stops the proxy at startup. Since v0.13.0.
FORENSIC_S3_ENDPOINT โ€” S3 endpoint URL when FORENSIC_BACKEND=s3 โ€” boto3-based, S3-compatible (leave empty for AWS S3). Standard AWS_* credentials are honoured if the FORENSIC_S3_* equivalents are empty.
FORENSIC_S3_ACCESS_KEY, FORENSIC_S3_SECRET_KEY โ€” S3 credentials; FORENSIC_S3_REGION defaults to us-east-1.
FORENSIC_S3_BUCKET forensic-blackbox Bucket that holds the forensic hash chain
FORENSIC_S3_LOCK false Enable S3 Object Lock (WORM COMPLIANCE mode); retention set by FORENSIC_S3_LOCK_DAYS (default 7 years). The bucket must have been created with Object Lock enabled โ€” FORENSIC_S3_LOCK_AUTO_BUCKET=true creates it so when it does not exist.
FORENSIC_S3_MAX_RETRIES 5 Retries of transient S3 errors (backoff from FORENSIC_S3_BASE_DELAY_S, default 0.2 s); since v0.13.0 they apply to reads as well as writes.
ADMINA_FORENSIC_STATE_KEY โ€” HMAC-SHA256 key, or โ€ฆ_FILE (since v0.13.0, kept outside the forensic directory). It signs the chain-state file (_chain_state.json.sig sidecar) and, since v0.13.0, every record (record_sig, under a key derived from this one). At startup a chain state that is missing or does not verify is rebuilt only from records that all verify with the key; otherwise the chain is marked invalid and no record is written. A rebuild stays visible as forensic_chain: "rebuilt" on /health until admina forensic acknowledge-rebuild. Honoured by ForensicBlackBox (filesystem and S3) and the FilesystemForensicStore plugin. Recommended in production โ€” keep a copy of it.

Without the key (since v0.13.0). The state file and records are unsigned and an intact chain state is used as it is, but a chain state that is missing while records exist can no longer be rebuilt โ€” the records cannot be verified without the key, so the chain is reported invalid and nothing is recorded until an operator restores or moves the store aside. Records written before a key was set are reported as unsigned. Verification, rebuild and admina forensic verify are described under Governance Domains โ†’ Compliance.

Programmatic form. Both classes take the key as the keyword argument state_signing_key: str | None = None. The explicit argument wins; the environment variables (ADMINA_FORENSIC_STATE_KEY, then its _FILE) are only read when the argument is left at None (or empty), so SDK users need no env var at all. ForensicBlackBox also takes fail_mode="open" | "closed" (since v0.13.0):

from admina.domains.compliance.forensic import ForensicBlackBox
from admina.plugins.builtin.forensic.filesystem import FilesystemForensicStore

# key material comes from your own secret manager โ€” never hardcode it
box = ForensicBlackBox(
    filesystem_dir="/var/lib/admina/forensic",
    state_signing_key=signing_key,
)
store = FilesystemForensicStore(
    base_dir="/var/lib/admina/forensic",
    state_signing_key=signing_key,
)

Choose your forensic backend deliberately. The hash chain that makes Admina's audit trail tamper-evident depends on persistent storage. The no-config fallback is memory (records lost on restart). The repository's docker-compose.yml and the one admina init generates set FORENSIC_BACKEND=filesystem with FORENSIC_BASE_DIR=/app/.admina/forensic on the named volume forensic-data, so records and chain state outlive the container (since v0.13.0). The admina init admina.yaml leaves the backend lines commented out, and admina dev local mode exports FORENSIC_BACKEND=memory unless you set it โ€” which, being an environment value, overrides the YAML.

Backend comparison
BackendLicenseWhen to use
memory default Local dev, tests, demos. Records LOST on restart (loud warning at startup).
filesystem โ€” Single-host on-prem / air-gapped. Each record and the chain state are written atomically and fsynced. Persistence depends on the host filesystem; not ideal for HA. Requires FORENSIC_BASE_DIR.
s3 Apache 2.0 (boto3) Recommended for production. Works against any S3-compatible service โ€” AWS S3, Cloudflare R2, Backblaze B2, SeaweedFS (Apache 2.0), Garage (AGPLv3, as a backend), MinIO, Ceph RGW. Object Lock supported.
Using MinIO? Since v0.9.5 the dedicated MinIO backend has been removed โ€” the archived MinIO Python SDK is no longer a dependency. MinIO servers remain fully supported through the s3 backend: point FORENSIC_S3_ENDPOINT at your MinIO server (boto3 speaks the S3 API). One thing to keep in mind that is not an Admina obligation but MinIO's own: MinIO Server is AGPLv3 โ€” deploying it as part of a network-accessible service can be read to require publishing the source code of the combined application (the commercial license removes this obligation but is paid). If you want a fully permissive stack, SeaweedFS (Apache 2.0, lightweight, single-binary S3 gateway) is a FOSS-friendly drop-in. FORENSIC_BACKEND=minio (or backend: minio in admina.yaml) still works for now โ€” it routes to the s3 backend with a migration warning.
Storage โ€” ClickHouse
CLICKHOUSE_HOST localhost ClickHouse host for analytics. Empty: no ClickHouse and no connection attempt (since v0.13.0); since v0.12.2 the dashboard feed, trend and suggestions then read the forensic store's recent records instead.
CLICKHOUSE_PORT 8123 ClickHouse HTTP port
CLICKHOUSE_DB admina ClickHouse database name
CLICKHOUSE_PASSWORD โ€” ClickHouse password. Change in production.
Telemetry โ€” OpenTelemetry
OTEL_ENABLED true With the telemetry extra installed and ADMINA_OFFLINE off, the proxy exports a span per governance decision. false builds no exporter and makes no telemetry connection. Since v0.13.0.
OTEL_ENDPOINT http://localhost:4317 OTLP gRPC collector endpoint
Governance Domains
ADMINA_GOVERNANCE_MODE enforce How the firewall reacts to flagged traffic: enforce (default, blocks), observe (never blocks, logs "would have blocked"), dry-run (like observe + tags the response). Use observe for the first 1โ€“2 weeks of a new deployment. Also the ceiling for egress control on the proxy surfaces: under observe or dry-run the egress stage never refuses, whatever ADMINA_EGRESS_MODE says (the SDK uses GovernedModel(mode=โ€ฆ) instead). Honoured under this name since v0.11.1; on v0.11.0 it was silently ignored and the unprefixed GOVERNANCE_MODE was read instead.
ADMINA_GUARD_FAIL_MODE open What happens when a pluggable governance guard raises: open (default) skips the guard and records an ERROR check; closed turns the exception into a BLOCK. Shared by the SDK, the MCP proxy and the OpenAI-compatible gateway (new in v0.11.0). An unrecognised value is rejected at startup.
INJECTION_FAST_PATH_ENABLED true Firewall on the gateway and /mcp; false switches it off there.
INJECTION_DEEP_PATH_ENABLED true false turns off the firewall's deep path (heuristic scoring) on either engine, leaving the patterns only. Since v0.13.0.
PII_REDACTION_ENABLED true PII redaction on the gateway and /mcp. While it is on, the gateway's passthrough stream mode behaves as governed.

There is no environment variable that selects the active domains โ€” each one is toggled individually in admina.yaml under domains.<name>.enabled (data_sovereignty, ai_infra, agent_security, compliance). Three of them ship enabled; ai_infra is opt-in and defaults to false.

PII engine
VariableDefaultDescription
ADMINA_PII_ENGINE spacy-regex PII detection engine: spacy-regex (default) | presidio (Microsoft Presidio, behind the [presidio] extra, EN + IT) | since v0.13.0, the name of an engine registered by another package under the admina.pii_engines entry-point group (it receives its plugin_config block). Also settable as the top-level pii_engine: key. An unknown name raises, listing the available engines โ€” there is no silent fallback. Rust acceleration applies only to spacy-regex, and only under ADMINA_ENGINE=rust (under auto PII stays Python for full recall). See Data Sovereignty.
ADMINA_PII_MASK_STYLE typed typed ([EMAIL], [PERSON], โ€ฆ) | omissis (every span becomes [OMISSIS], in every engine). Also pii_mask_style: in admina.yaml; another value raises. Since v0.13.0.
ADMINA_PRESIDIO_NLP_MODELS โ€” spaCy pipeline per language of the presidio engine, e.g. it:blank,en:en_core_web_sm (blank = tokenizer only, no NER). Also presidio.nlp_models in admina.yaml. Unset: en_core_web_sm and it_core_news_sm where installed. A configured model that is not installed stops the engine โ€” models are never downloaded. Since v0.13.0.

Firewall rules in admina.yaml

The injection firewall's rules live under domains.agent_security.firewall; there is no environment-variable form apart from ADMINA_PATTERN_PACK_DIRS (separated by : โ€” os.pathsep), which replaces pattern_pack_dirs when set. Shown with defaults:

domains:
  agent_security:
    firewall:
      enabled: true
      # deep-path score from which a text is flagged (Python engine)
      heuristic_threshold: 0.5
      # tags the deep path does not count as context switches (Python engine)
      allowed_tags: []
      # โ”€โ”€ Python-only keys: any non-empty one selects the Python firewall โ”€โ”€
      custom_patterns: []       # entries: regex, category, risk_level
      disabled_categories: []
      disabled_patterns: []     # pattern ids, e.g. instruction_override.en.1
      pattern_packs: []         # names of versioned pattern packs
      # โ”€โ”€ pack lookup; these do not select an engine โ”€โ”€
      pattern_pack_dirs: []
      strict_pack_timing: false # true: a pack pattern over 50 ms stops the proxy

disabled_patterns, allowed_tags, pattern_packs, pattern_pack_dirs and strict_pack_timing are new in v0.13.0. A pack that is missing, found twice or invalid stops the proxy (PatternPackError); a malformed custom_patterns entry is skipped with a warning. Under ADMINA_ENGINE=auto, any of the four Python-only keys makes the firewall Python (warning logged); under ADMINA_ENGINE=rust it is an EngineSelectionError. Pattern ids, packs and the categories are documented under Governance Domains โ†’ Agent Security.

heuristic_threshold is applied since v0.13.0. Older releases ignored the key and always used 0.5. Files generated by admina init up to 0.12 (and copies of that era's admina.yaml.example) set heuristic_threshold: 0.7, which the Python firewall now honours โ€” fewer texts flagged by the deep path. Set 0.5 to keep the previous behaviour. When the Python firewall runs, a value that is not a finite number greater than 0 stops the proxy. The Rust engine scores with a threshold of its own and ignores this key โ€” so under auto with admina-core installed (and no Python-only key set) a bad value goes unnoticed.
Egress control & coordination detector (v0.12.0)
VariableDefaultDescription
ADMINA_EGRESS_MODE observe observe records the destinations of every tool call and refuses nothing; enforce is default-deny โ€” a destination not on the allowlist is refused, and so is a call whose destination cannot be determined. The same gate decides whether a coordination quarantine refuses writes. Capped by the governance mode โ€” ADMINA_GOVERNANCE_MODE on the proxy, the mode= argument on GovernedModel โ€” so refusal needs both set to enforce. An unrecognised value falls back to observe with a warning.
ADMINA_EGRESS_FINGERPRINT_KEY โ€” Secret key for the coordination detector's keyed content shingles. Unset, echo confirmation is disabled outright and the detector never escalates past suspected, so nothing is ever quarantined. A key shorter than 16 bytes is accepted but logged as too short to resist a dictionary attack.

Both are environment-variable-only and read from the process environment, not from .env (see the note above the tables). Everything else lives under domains.agent_security.egress in admina.yaml, shown here with its defaults (an empty allow list under enforce refuses every destination):

domains:
  agent_security:
    egress:
      # false skips the stage entirely
      enabled: true
      # exact hosts, "*.suffix" wildcards, CIDR ranges
      allow: []
      # tool names exempt from the payload-bearing classification
      read_only_tools: []
      # surfaces the stage runs on: gateway, mcp, integration, sdk
      # (unset: all of them; []: none) โ€” since v0.13.0
      # surfaces: [mcp]
      # shared write destinations: exact lowercase hostnames only
      coordination_declared: []
      fanin:
        window_seconds: 3600   # effective window is 1โ€“2ร—
        min_agents: 5          # raises "suspected" (floor 2)
      # fleet-wide write quarantine after "confirmed"
      quarantine_ttl_seconds: 86400

Since v0.13.0 a wrong-typed value anywhere in admina.yaml is a ConfigSchemaError that stops the proxy (and raises in the SDK) โ€” it no longer degrades to an empty allowlist โ€” and an unknown name in surfaces stops the proxy too. What each key does, the per-surface coverage and the admina egress commands are documented under Governance Domains โ†’ Egress control.

Rate Limiting
RATE_LIMIT_MAX_REQUESTS 100 Max requests per session per window (requires Redis)
RATE_LIMIT_WINDOW_SECONDS 60 Rate limit window in seconds
RATE_LIMIT_IP_MULTIPLIER 5 Per-IP limit = the per-session limit ร— this
Governance Thresholds โ€” Loop Breaker
LOOP_WINDOW_SIZE 10 Number of past requests to compare for loop detection
LOOP_SIMILARITY_THRESHOLD 0.85 Cosine similarity threshold (0.0โ€“1.0) to trigger loop detection
LOOP_MAX_CONSECUTIVE 3 Consecutive similar requests that trip the breaker
Storage โ€” Grafana
GRAFANA_ADMIN_PASSWORD โ€” Grafana admin password โ€” docker-compose.yml passes it straight through to GF_SECURITY_ADMIN_PASSWORD and falls back to an empty value when unset. The admin username is fixed at admin. Set it in production.

Docker Compose environment

When using the included docker-compose.yml, run ./scripts/bootstrap-secrets.sh to generate all secrets at once. The script writes random values to .env and is idempotent โ€” rerun with --force to regenerate.

./scripts/bootstrap-secrets.sh
docker compose up --build

# An API key and one password shared by every UI โ€” the two values the
# script prints. Both are written to .env:
ADMINA_API_KEY=<random 32-byte hex>
ADMINA_DASHBOARD_PASSWORD=<random 20-char password>
CLICKHOUSE_PASSWORD=<same value>
GRAFANA_ADMIN_PASSWORD=<same value>
MINIO_SECRET_KEY=<same value>      # unused residue of the removed MinIO backend (v0.9.5)

# .env also gets a separate, unprinted WEBUI_SECRET_KEY (another random
# 32-byte hex), used only by the Open WebUI service that `admina init`
# adds to its compose file when the ai_infra domain is on.
  • The proxy listens on 8080 and keeps its forensic records on the named volume forensic-data (FORENSIC_BACKEND=filesystem, FORENSIC_BASE_DIR=/app/.admina/forensic), so they survive docker compose down / up.
  • The dashboard container is published on 127.0.0.1:3000 only (Grafana on 3001). With ADMINA_API_KEY set, it refuses to start without ADMINA_DASHBOARD_PASSWORD (HTTP Basic Auth, user admin) and accepts only a key made of letters, digits and . _ ~ + / = - (a hex key is fine). Its nginx adds the key only to the dashboard's read-only routes (/api/dashboard/*, the live feed, /api/stats); /mcp and the other /api/ routes are forwarded with the caller's own key. Without the key, the page signs in with the API key itself. Since v0.13.0.
  • The proxy image's entrypoint exits unless ADMINA_API_KEY or ADMINA_API_KEY_FILE is set, and prints only whether a key is set, never any of its characters.

Production checklist

  • Run bootstrap-secrets.sh (or let the CLI vault generate them) โ€” never commit .env. In containers, prefer the *_FILE forms for secrets
  • Leave ALLOW_UNAUTHENTICATED at its false default
  • Pin the configuration: ADMINA_CONFIG=/etc/admina/admina.yaml and ADMINA_CONFIG_STRICT=true, so a missing file, a typo'd key or a stray ADMINA_* variable stops the proxy instead of being ignored
  • Serve only what you use with ADMINA_ENABLED_SURFACES; set ADMINA_DASHBOARD_ENABLED=false and ADMINA_API_DOCS_ENABLED=false if nobody needs them
  • Set ADMINA_METRICS_REQUIRE_AUTH=true, or keep /metrics reachable only from your monitoring network
  • Behind TLS, set DASHBOARD_COOKIE_SECURE=true (or auto) so the dashboard session cookie is never sent over plain HTTP
  • Use FORENSIC_BACKEND=s3 with TLS and Object Lock for a tamper-evident production audit trail; set ADMINA_FORENSIC_FAIL_MODE=closed if no request may be served without its record
  • Set ADMINA_FORENSIC_STATE_KEY (or _FILE, outside the forensic directory) so the chain state and every record are signed, and back the key up โ€” without it a lost chain state cannot be rebuilt
  • After deploying, check GET /health: forensic_writable not false, forensic_chain ok, and engine naming the engines you expect
  • On upgrade from 0.12, check heuristic_threshold in your admina.yaml (see above) and recompute any pinned ruleset hash (it changes with every release)
  • Decide your guard fail mode deliberately: ADMINA_GUARD_FAIL_MODE=closed makes a guard exception a BLOCK instead of a skipped check
  • On the gateway, bound the upstream with ADMINA_GATEWAY_MODELS_ALLOWLIST, the ADMINA_GATEWAY_TIMEOUT_* settings and ADMINA_GATEWAY_PIPELINE_TIMEOUT; leave ADMINA_GATEWAY_SCAN_POLICY_ENABLED off unless every key holder is trusted
  • Roll egress control out observe-first: collect destinations with a persistent forensic backend, build the allowlist with admina egress suggest-allowlist, review it, and only then set ADMINA_EGRESS_MODE=enforce
  • If agents share write destinations, list them under coordination_declared before enforcing โ€” and remember X-Agent-Id is unauthenticated, so forged ids can arm a coordination quarantine
  • Configure CORS_ORIGINS to your actual frontend domains (the proxy warns on wildcard)
  • Set LOG_LEVEL=WARNING to reduce log volume, and ADMINA_LOG_FORMAT=json if a log pipeline parses the output
  • Point OTEL_ENDPOINT to your observability platform, or set OTEL_ENABLED=false