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