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
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).
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=trueandADMINA_API_DOCS_REQUIRE_AUTH=trueput/metricsand the docs behind the key too; otherwise/metricsserves Prometheus counters unauthenticated, so keep it off the public internet.ADMINA_DASHBOARD_PASSWORDโ the HTTP Basic Auth password (useradmin) of the Docker Compose dashboard container.bootstrap-secrets.shand the CLI vault write the same value toCLICKHOUSE_PASSWORDandGRAFANA_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.
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.
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.
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) 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.
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. 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_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.
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. 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.
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. 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 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.
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.
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_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 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 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
8080and keeps its forensic records on the named volumeforensic-data(FORENSIC_BACKEND=filesystem,FORENSIC_BASE_DIR=/app/.admina/forensic), so they survivedocker compose down/up. - The dashboard container is published on
127.0.0.1:3000only (Grafana on3001). WithADMINA_API_KEYset, it refuses to start withoutADMINA_DASHBOARD_PASSWORD(HTTP Basic Auth, useradmin) 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);/mcpand 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_KEYorADMINA_API_KEY_FILEis 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*_FILEforms for secrets - Leave
ALLOW_UNAUTHENTICATEDat itsfalsedefault - Pin the configuration:
ADMINA_CONFIG=/etc/admina/admina.yamlandADMINA_CONFIG_STRICT=true, so a missing file, a typo'd key or a strayADMINA_*variable stops the proxy instead of being ignored - Serve only what you use with
ADMINA_ENABLED_SURFACES; setADMINA_DASHBOARD_ENABLED=falseandADMINA_API_DOCS_ENABLED=falseif nobody needs them - Set
ADMINA_METRICS_REQUIRE_AUTH=true, or keep/metricsreachable only from your monitoring network - Behind TLS, set
DASHBOARD_COOKIE_SECURE=true(orauto) so the dashboard session cookie is never sent over plain HTTP - Use
FORENSIC_BACKEND=s3with TLS and Object Lock for a tamper-evident production audit trail; setADMINA_FORENSIC_FAIL_MODE=closedif 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_writablenotfalse,forensic_chainok, andenginenaming the engines you expect - On upgrade from 0.12, check
heuristic_thresholdin youradmina.yaml(see above) and recompute any pinned ruleset hash (it changes with every release) - Decide your guard fail mode deliberately:
ADMINA_GUARD_FAIL_MODE=closedmakes a guard exception aBLOCKinstead of a skipped check - On the gateway, bound the upstream with
ADMINA_GATEWAY_MODELS_ALLOWLIST, theADMINA_GATEWAY_TIMEOUT_*settings andADMINA_GATEWAY_PIPELINE_TIMEOUT; leaveADMINA_GATEWAY_SCAN_POLICY_ENABLEDoff 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 setADMINA_EGRESS_MODE=enforce - If agents share write destinations, list them under
coordination_declaredbefore enforcing โ and rememberX-Agent-Idis unauthenticated, so forged ids can arm a coordination quarantine - Configure
CORS_ORIGINSto your actual frontend domains (the proxy warns on wildcard) - Set
LOG_LEVEL=WARNINGto reduce log volume, andADMINA_LOG_FORMAT=jsonif a log pipeline parses the output - Point
OTEL_ENDPOINTto your observability platform, or setOTEL_ENABLED=false