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 โ€” while others, such as the gateway variables and ADMINA_GUARD_FAIL_MODE, are environment-variable-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

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, /docs, /openapi.json, /redoc, /metrics and the bundled dashboard shell (/, /heimdall.png, /vendor/*) โ€” the dashboard's own /api/* calls still authenticate, via a signed session cookie. Note that /metrics serves Prometheus counters unauthenticated: keep it off the public internet or in front of your own reverse proxy.
  • ADMINA_DASHBOARD_PASSWORD โ€” shared login for the bundled Alpine.js dashboard, Grafana, and ClickHouse.

Both 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"

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.

All variables

Proxy & Upstream
VariableDefaultDescription
UPSTREAM_MCP_URL http://localhost:9000 Default upstream MCP server URL
CORS_ORIGINS http://localhost:3000,http://localhost:8080 Comma-separated allowed CORS origins
LOG_LEVEL INFO Logging verbosity: DEBUG, INFO, WARNING, ERROR
ADMINA_ENGINE auto Governance-engine backend, uniform across proxy / SDK / integrations: auto | python | rust (since v0.10.0; unrecognized value raises). See the Rust engine guide.
ADMINA_PII_ENGINE spacy-regex PII detection engine: spacy-regex (default) | presidio (Microsoft Presidio, behind the [presidio] extra, EN + IT). Also settable as the top-level pii_engine: key in admina.yaml. An unknown value raises โ€” there is no silent fallback. Selecting presidio bypasses Rust acceleration, which only ever applies to spacy-regex. See Data Sovereignty.
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 for POST /v1/chat/completions and GET /v1/models. Must already include the API base path (typically /v1); the default points at a local Ollama. The gateway forwards no credentials, so the upstream must be keyless (Ollama, local vLLM, LM Studio).
ADMINA_GATEWAY_BLOCK_MESSAGE This request was blocked by the Admina governance policy. Assistant message returned in the synthetic HTTP 200 completion โ€” or, on a streaming request, in the synthetic SSE chunk โ€” when the governance pipeline blocks a request (finish_reason: content_filter).
ADMINA_GATEWAY_MODELS_ALLOWLIST โ€” Comma-separated model ids; empty means full passthrough. When non-empty, filters the data[] returned by GET /v1/models. It does not restrict which model POST /v1/chat/completions may forward.

These three are environment-variable-only โ€” there is no admina.yaml equivalent. See API Reference โ†’ OpenAI-compatible gateway.

Authentication
ADMINA_API_KEY โ€” API key for all endpoints. Auto-generated by the vault or bootstrap-secrets.sh
ADMINA_DASHBOARD_PASSWORD โ€” Shared password for dashboard, Grafana, and ClickHouse UIs
ALLOW_UNAUTHENTICATED false Set true only for local development โ€” bypasses the API key check
Storage โ€” Redis
REDIS_URL redis://localhost:6379/0 Redis connection URL โ€” session state, rate limiting, hash chain
Forensic Black Box โ€” backend selection
FORENSIC_BACKEND memory One of memory | filesystem | s3
FORENSIC_BASE_DIR โ€” Required when FORENSIC_BACKEND=filesystem (explicit opt-in, no default)
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_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)
ADMINA_FORENSIC_STATE_KEY โ€” Optional HMAC-SHA256 key that signs the forensic chain-state file _chain_state.json into a _chain_state.json.sig sidecar (a file next to the records for the filesystem backends, a separate object under that key in the same bucket for S3). The signature is the hex digest computed over the exact bytes of the state payload as written โ€” chain_head, record_count, updated_at. On restart a valid signature is trusted as a fast path; a missing or invalid signature is treated as untrusted โ€” a CRITICAL event is logged and the chain state is reconstructed from the stored records instead. Honoured by ForensicBlackBox (both its filesystem and s3 backends) and by the FilesystemForensicStore plugin. Recommended in production.

Leaving ADMINA_FORENSIC_STATE_KEY unset is not a vulnerability: the state file is simply unsigned, and the baseline truncation protection โ€” reconstructing the chain state from the persisted records โ€” is retained.

Programmatic form. Both classes take the key as the keyword argument state_signing_key: str | None = None. The explicit argument wins; the environment variable is only read when the argument is left at None (or empty), so SDK users need no env var at all:

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); since v0.9.5 the admina init templates and the dev docker-compose.yml scaffold with filesystem instead:

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. 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 still works for now โ€” it routes to the s3 backend with a migration warning.
Storage โ€” ClickHouse
CLICKHOUSE_HOST localhost ClickHouse host for analytics
CLICKHOUSE_PORT 8123 ClickHouse HTTP port
CLICKHOUSE_DB admina ClickHouse database name
CLICKHOUSE_PASSWORD โ€” ClickHouse password. Change in production.
Telemetry โ€” OpenTelemetry
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. 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.

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.

Rate Limiting
RATE_LIMIT_MAX_REQUESTS 100 Max requests per session per window
RATE_LIMIT_WINDOW_SECONDS 60 Rate limit window in seconds
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
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.

Where does admina.yaml come from? There is no environment variable for its path, and no --config flag. 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; the CLI is stricter โ€” 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).

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.

Production checklist

  • Run bootstrap-secrets.sh (or let the CLI vault generate them) โ€” never commit .env
  • Leave ALLOW_UNAUTHENTICATED at its false default
  • Use FORENSIC_BACKEND=s3 with TLS and Object Lock for a tamper-evident production audit trail
  • Set ADMINA_FORENSIC_STATE_KEY so the forensic chain-state file is HMAC-signed โ€” an unsigned or tampered state is then detected and the chain rebuilt from the records
  • Decide your guard fail mode deliberately: ADMINA_GUARD_FAIL_MODE=closed makes a guard exception a BLOCK instead of a skipped check
  • Configure CORS_ORIGINS to your actual frontend domains (the proxy warns on wildcard)
  • Set LOG_LEVEL=WARNING to reduce log volume
  • Point OTEL_ENDPOINT to your observability platform