Documentation

Configuration

Every variable the code reads, gathered from the source (os.Getenv and the env(...) loaders in cmd/ and internal/). Compose-only variables follow in a second table.

Gateway

Variable Default Purpose
GUARD_ENV empty dev relaxes the secret checks. Anything else is production
LISTEN_ADDR :8080 API listener
DATABASE_URL empty Postgres URL of the guardspec_app role (password in the URL or PGPASSWORD)
UPSTREAM_LLM_URL empty Base URL of an OpenAI-compatible API, such as http://host.docker.internal:11434/v1. Empty: stub responses
UPSTREAM_LLM_KEY empty Bearer token for the upstream; not sent when empty
UPSTREAM_LLM_TIMEOUT 5m Longest wait for the first byte and for any later silence
UPSTREAM_KEY_* empty Keys of named upstreams: an upstream with api_key_env: UPSTREAM_KEY_MAIN sends the value of that variable as its bearer token. Only names matching ^UPSTREAM_KEY_[A-Z0-9_]{1,64}$ are read. See Routing
UPSTREAM_KEY_ORGS empty (compose: UPSTREAM_KEY_MAIN=*) Which orgs may use which key variable: UPSTREAM_KEY_MAIN=*;UPSTREAM_KEY_ACME=<org-uuid>,<org-uuid>. A variable not listed for the org fails the request closed (502). Empty: no org may use any. Set the same value on the control plane
UPSTREAM_ALLOW_PRIVATE false (compose: true) true lets org-defined upstreams reach loopback, RFC 1918, unique local (fc00::/7) and CGNAT (100.64.0.0/10) addresses. Checked when the gateway connects. UPSTREAM_LLM_URL is never restricted
CLIENT_HALF_CLOSE cancel wait keeps the upstream call running when a client half-closes its socket
AUDIT_STORE_CONTENT false true stores prompt and response content in audit rows for every org
SCAN_ROLES user,tool,tool_descriptions Parts to classify: user, tool, system, tool_calls, tool_descriptions, or all / none
SPOTLIGHT off Mark tool results for the model: off, delimit, datamark
INDIRECT_BLOCK_THRESHOLD 0.85 Block threshold for tool results, documents and tool descriptions, in (0, 0.85]
OUTPUT_GUARD on off passes responses through unchanged
OUTPUT_LINK_ALLOWLIST empty Comma-separated hosts (and their subdomains) whose images and links are kept
OUTPUT_PII_REDACT true Redact e-mail, phone, CCCD and card numbers
OUTPUT_PII_IPV4 false Also redact IPv4 addresses
OUTPUT_CANARY off block or redact injects a canary and acts when it leaks
OUTPUT_REASONING guard drop removes reasoning / reasoning_content instead of guarding them
MODEL_DIR ./models (/models in the image) Root of the model files. MODELS_PATH is the older name
L2_MODEL the manifest's recommended classifier (guardspec-classifier@v3b) Classifier as name@version (a bare name: its recommended version), loaded from $MODEL_DIR/cache/<name>/<version>. Pin or roll back with it; see docs/models.md
L2_MODEL_DIR empty Classifier directory; wins over L2_MODEL. With neither set: the recommended version when installed, else $MODEL_DIR/local/guardspec-classifier-v3b, -v2, -v1, -v0
L3_MODEL the manifest's recommended embedder (multilingual-e5-small@614241f) Embedding model as name@version
L3_MODEL_DIR empty Embedding model directory; wins over L3_MODEL. With neither set: the recommended version when installed, else $MODEL_DIR/cache/multilingual-e5-small
DETECTOR_LAYERS l1,l2,l3 Layers to run; l1,l2 is light mode, and compose's models-init then pulls the classifier only (docs/sizing.md)
DETECTOR_FUSION logreg logreg or max of the calibrated layers
DETECTOR_CASCADE false true skips L2/L3 when L1's calibrated score is below DETECTOR_CASCADE_LOW or above DETECTOR_CASCADE_HIGH
DETECTOR_CASCADE_LOW / DETECTOR_CASCADE_HIGH 0.03 / 0.965 Cascade bounds
DETECTOR_REQUIRE_ML false true refuses to start without the model layers (not with GUARD_ENV=dev)
MODELS_MANIFEST empty (the manifest built into the binary) Manifest file read at start and on every reload: what a reload verifies against, and what /readyz reports as recommended. See Shadow mode and model reload
SHADOW_L2_MODEL / SHADOW_L2_MODEL_DIR empty (off) Candidate classifier compared with the active one on the same traffic, as name@version or a directory. It never changes a decision
SHADOW_TIMEOUT 2s Budget of one request's comparison; a candidate still running after it is recorded as timeout
SHADOW_SAMPLE 1 Share of requests compared, 0 to 1
SHADOW_MAX_INFLIGHT CPUs / 2 (at least 1) Candidate runs at once, on their own ONNX limiter; a text that finds them all busy is not compared (skipped)
GATEWAY_ADMIN_TOKEN empty (off) Bearer token of POST /admin/models/reload and GET /admin/models; at least 32 bytes. Empty: those routes answer 404
ONNXRUNTIME_LIB set in the image Path of libonnxruntime.so / .dylib (-tags onnx builds)
INFERENCE_MAX_CONCURRENCY number of CPUs Concurrent model runs per process
INFERENCE_INTRA_OP_THREADS ORT's choice (Linux: min(4, CPUs); with L2+L3 each gets min(4, CPUs/2)) ONNX Runtime threads per session
METRICS_ADDR :9090 Metrics listener; off disables it
METRICS_TENANT_LABELS false true adds one metric series per org
RATE_LIMIT_RPM / RATE_LIMIT_TPM 0 Per-key requests / estimated tokens per minute for keys without their own limit; 0 = unlimited
OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_TRACES_ENDPOINT empty OTLP/HTTP endpoint; tracing is off when both are empty. Other standard OTEL_EXPORTER_OTLP_* variables apply
OTEL_PROPAGATE_UPSTREAM false true sends traceparent to the upstream

Control plane

Variable Default Purpose
GUARD_ENV empty as above
LISTEN_ADDR :8081 API listener
DATABASE_URL empty as above
JWT_SECRET empty Token signing key, at least 32 bytes
ADMIN_EMAIL / ADMIN_PASSWORD empty First owner of the default organization, created on start when both are set
ALLOWED_ORIGINS empty (compose: http://localhost:3000) CORS origins; empty disables CORS. The UI at /ui/ is same-origin and needs none
UPSTREAM_LLM_URL empty The gateway's value, so that the policy simulator can show the env step; credentials in it are not shown
GATEWAY_URL empty (compose: http://gateway:8080) The gateway's base URL inside the deployment. The control plane reads its /readyz (server side, 3 s timeout) for the Models page; empty: the page says it is not configured
UPSTREAM_KEY_ORGS empty (compose: UPSTREAM_KEY_MAIN=*) As on the gateway. Saving an upstream (or a config file) that names a key variable not allowed for the org is refused
UPSTREAM_KEY_*, UPSTREAM_ALLOW_PRIVATE empty, false (compose: false; dev override: true) Used only by POST .../upstreams/:id/test (owner only), which applies the gateway's key allowlist and address rules. A key is sent with a test only to orgs UPSTREAM_KEY_ORGS lists by id, not through * (except with GUARD_ENV=dev); the answer says key_sent. Give the control plane UPSTREAM_KEY_* variables only if tests should use them
TRUSTED_PROXIES empty Comma-separated CIDRs or addresses of reverse proxies. Their X-Forwarded-For is read from the right, and the first address that is not a trusted proxy is the client, for rate limits, sessions and the activity log. Empty: the header is ignored and the TCP peer is the client
MFA_ENC_KEY empty (dev: a fixed key) 32 random bytes in base64 (openssl rand -base64 32). Encrypts TOTP secrets with AES-256-GCM. Without it 2FA cannot be turned on; when users already have 2FA the control plane refuses to start without it. The development key is refused outside GUARD_ENV=dev. Back it up with the database: a lost key disables everyone's 2FA
PUBLIC_BASE_URL empty (dev: http://localhost:18081) External origin of the control plane, such as https://guard.example.com. SSO callbacks go to <PUBLIC_BASE_URL>/v1/auth/oidc/<provider id>/callback; SSO is unavailable without it
OIDC_SECRET_* empty Client secrets of SSO providers; each provider names its variable in client_secret_env
METRICS_ADDR :9091 Metrics listener; off disables it
AUDIT_RETENTION_DAYS 90 Delete audit rows older than this; 0 keeps them
AUDIT_RETENTION_INTERVAL 1h Time between retention runs, at least 1m
AUDIT_RETENTION_BATCH 5000 Rows per delete, 1 to 100000

Tools: HF_TOKEN (cmd/models, only for gated optional models in the manifest; sent to Hugging Face only), MODELS_MIRROR (cmd/models, a base URL tried before Hugging Face: <mirror>/<name>/<version>/<file>).

Compose and scripts (docker-compose.yml, .env.example, deploy/postgres/set-app-password.sh)

Variable Default Purpose
POSTGRES_PASSWORD dev: ps Superuser ps, used by migrations and db-roles
GUARDSPEC_APP_PASSWORD dev: guardspec_app Password of the guardspec_app role.
POSTGRES_DB guardspec Database created on a fresh volume
COMPOSE_PROJECT_NAME guard-spec Compose project (and volume) name
GATEWAY_PORT, METRICS_PORT 8080, 9090 Host ports of the gateway
CONTROL_PLANE_PORT, CONTROL_PLANE_METRICS_PORT 18081, 9091 Host ports of the control plane
POSTGRES_PORT 5432 Host port of Postgres (dev override only)
GUARDSPEC_VERSION 0.1.1 Tag of the ghcr.io/guard-spec/guard-spec-* images in docker-compose.yml (the dev override builds from source instead)
MODELS_UID, MODELS_GID 0 Owner of files written by models-init and models-pull
QUICKSTART_TIMEOUT 600 scripts/quickstart.sh: seconds to wait for /readyz

Source: README.md#configuration @ ca69dad29f02