Configuration reference
The server is configured by a TOML file (--config, default ./config.toml),
an optional secrets file (--secrets, default ./secrets.toml), and
per-value environment overrides. Generate a fully documented template with:
IMAGE=registry.tinyfactory.ai/tinyblox/guard:0.3.2docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" -w /work \ "$IMAGE" generate-config --output config.tomldocker run --rm -v "$PWD:/work:ro" -w /work \ "$IMAGE" validate-config --config config.toml --secrets secrets.tomlRules that apply to every section:
- Unknown keys in any section abort the boot — a misspelled value is never silently ignored.
- String values may reference the secrets file with
$SECRETS.<name>; an unreferenced name aborts the boot. - Every key has an environment-variable override,
GUARD_<SECTION>_<KEY>(server.http_port→GUARD_SERVER_HTTP_PORT), and a_FILEform that reads the value from a file. The environment wins over the file. The environment reference lists every name. There are no other spellings: a variable without theGUARD_prefix is not read. idp-server config checkloads the configuration exactly asservewould, environment included, validates it without touching the network and exits non-zero with the reason;--printshows the effective values with secrets redacted. Run it before a rollout.GUARD_VAULT_CONFIG=trueloads the whole configuration from a vault-style source (vault.tomlwithaddress/token/mount/path, orGUARD_VAULT_ADDR,GUARD_VAULT_TOKEN,GUARD_VAULT_MOUNT,GUARD_VAULT_CONFIG_PATH). An unreachable source is fatal. Remote Vault addresses require verified HTTPS using system trust roots; plain HTTP is accepted only for a loopback sidecar. The source reads KV v2 at/v1/<mount>/data/<path>and expects a string atdata.data.config. Malformedvault.tomlfails the boot.
[server]
Section titled “[server]”| Key | Default | Meaning |
|---|---|---|
public_url |
localhost:8080 |
Scheme-less public URL; the issuer and callback URLs derive from it. Include non-standard ports. |
listen_scheme |
http |
One of http, https, http+https, unix-http, unix-https (unix variants require unix_socket_path). |
listen_host |
0.0.0.0 |
Bind address. |
http_port |
8080 |
HTTP listen port. |
https_port |
8443 |
HTTPS listen port. |
unix_socket_path |
— | Unix socket path, required for the unix-* schemes. |
http_workers |
0 |
0 = automatic (cores minus the concurrent-hash worker bound and the database reserve). |
graceful_shutdown_seconds |
10 |
Grace period on shutdown. |
proxy_enabled |
false |
Derive HTTPS URLs and trust forwarded client-address headers from the listed proxies. Off: forwarded headers are ignored. |
trusted_proxies |
— | The proxies to trust, as addresses or networks (for example ["10.0.0.0/8"]). At least one is required when proxy mode is enabled. |
whoami_headers |
false |
Echo all request headers (cookie and CSRF values masked) from the whoami diagnostic. |
console_static_dir |
— | Directory holding the built admin-console bundle; the versioned root serves the console SPA. |
The derived issuer is <scheme>://<public_url>/auth/v1/ and the provider
callback URL is <issuer>oidc/callback.
[server.tls]
Section titled “[server.tls]”| Key | Default | Meaning |
|---|---|---|
cert_path |
— | PEM certificate file; a .der key filename selects DER parsing. |
key_path |
— | Private-key file. |
self_signed |
false |
Ignore the configured paths and always serve generated self-signed material from fixed files. |
Missing files fall back to generated self-signed material.
[server.metrics]
Section titled “[server.metrics]”| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Scrape endpoint on its own dedicated single-worker listener. |
listen_host |
0.0.0.0 |
Must be IPv4. |
port |
9090 |
Never on the main application port. |
[server.docs]
Section titled “[server.docs]”| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Publish the machine-readable API document and documentation UI under the versioned prefix. |
public |
false |
When false, requires an authenticated session. |
[security]
Section titled “[security]”| Key | Default | Meaning |
|---|---|---|
scan_target_paths |
— | Paths that blacklist the requester instead of being served (for example ["/wp-admin", "/.env"]). |
blacklist_minutes |
5 |
Minutes a scanner-path requester stays blacklisted (0 disables). |
blacklist |
[] |
IP addresses blocked from the first request on. |
[storage]
Section titled “[storage]”| Key | Default | Meaning |
|---|---|---|
mode |
embedded |
One of embedded, postgres. |
data_directory |
./data |
Embedded storage directory. |
host |
— | PostgreSQL host (may use $SECRETS.<name>). |
user |
— | PostgreSQL user. |
password |
— | PostgreSQL password (may use $SECRETS.pg_password). |
port |
5432 |
PostgreSQL port. |
db_name |
— | PostgreSQL database name. |
schema |
tinyguard |
The schema the service owns inside the database; it never uses public. Lowercase letters, digits and underscores. |
tls_mode |
verify-full |
One of disable, prefer, require, verify-ca, verify-full. Only verify-full authenticates the server; a development database without TLS must say disable. An unrecognised value is an error. |
ca_file |
— | PEM trust anchors for verify-ca and verify-full; the system store when absent. |
pool_size |
10 |
Connections one instance holds open (1–500). |
connect_timeout_seconds |
10 |
How long a connection attempt may take. |
statement_timeout_seconds |
30 |
Server-side limit for one statement; 0 sends none. |
auto_migrate |
true |
true: boot applies pending schema steps. false: boot only checks the schema is current; run idp-server migrate to apply them. |
The PostgreSQL settings are explained, with how to size and secure a shared server, in Deployment.
[health]
Section titled “[health]”| Key | Default | Meaning |
|---|---|---|
grace_window_seconds |
30 |
After start, both subsystems report healthy for this window. |
[logging]
Section titled “[logging]”| Key | Default | Meaning |
|---|---|---|
format |
plain |
One of plain, json. |
level |
info |
One of trace, debug, info, warn, error. |
[observability]
Section titled “[observability]”OpenTelemetry (OTLP traces and logs with sensitive-data masking). Off by
default — with enabled = false nothing OpenTelemetry runs and the stdout
logs are unchanged. See Observability for the masking
guarantees and what is exported.
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Master switch: OTLP span and log export when true. |
endpoint |
http://localhost:4318 |
Base collector endpoint; /v1/traces and /v1/logs are appended. https:// everywhere except loopback. |
protocol |
http/protobuf |
The one supported transport (OTLP over HTTP). |
traces |
true |
Export spans. |
logs |
true |
Export log records through the masked bridge. |
sample_ratio |
1.0 |
Head sampling ratio between 0 and 1. |
parent_based |
true |
Wrap the ratio sampler in the parent-based choice (an incoming traceparent decides). |
service_name |
tinyguard |
The service.name resource attribute. |
resource_attributes |
[] |
Extra resource attributes, each key=value. |
allow_insecure_endpoint |
false |
Accept a plaintext http:// endpoint to a non-loopback collector. |
batch_queue_size |
2048 |
Bounded queue shared by the span and log batch processors; overflow is dropped, never back-pressured. |
batch_export_size |
512 |
Maximum records per export (at most the queue size). |
batch_delay_ms |
5000 |
Delay between two consecutive exports. |
export_timeout_ms |
10000 |
Per-export timeout. |
[fedcm]
Section titled “[fedcm]”| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Serve the browser-facing web-identity discovery document at the host root. |
[tenants]
Section titled “[tenants]”| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Master switch of the path-bound tenant machinery (/auth/v1/{tenant}/...). The default tenant always exists and needs no configuration. |
The deprecated [realms] spelling of this section, and the REALMS_ENABLED
environment variable, still apply with a boot warning (validation applies
them only when [tenants] is absent; when both are set, the new one wins).
Env: TENANTS_ENABLED. See Tenants & multitenancy.
[cluster]
Section titled “[cluster]”| Key | Default | Meaning |
|---|---|---|
node_name |
— | Optional multi-node declaration; inert on a single node. |
[encryption]
Section titled “[encryption]”Optional. When present, the deployment switches from the plaintext
secrets-table encryption key to the envelope model: the 32-byte
value-encryption key (the DEK) is stored locally only wrapped, under a
root KEK kept in the secret manager. When the section is absent (the
default), nothing changes — the secrets file’s encryption_key remains
the deployment key.
| Key | Default | Meaning |
|---|---|---|
wrapped_dek |
./wrapped_dek.toml |
Path of the local wrapped-DEK artifact (owner-only, atomic replacement). |
kek_manager |
vault |
The KEK manager kind; the vault-style KV v2 manager is the one implemented. |
kek_address |
— (required) | Manager address; https://…, or loopback http://… for a local sidecar. |
kek_mount |
secret |
KV mount holding the KEK. |
kek_path |
tinyguard/platform/kek |
The platform KEK path; recorded in the artifact as kek_id, and the two must agree. |
kek_namespace |
— | Optional manager namespace header. |
kek_token_env |
GUARD_KEK_TOKEN |
Environment variable carrying the manager token; the token never lives in the configuration file. |
The wrapped-DEK artifact and boot behavior
Section titled “The wrapped-DEK artifact and boot behavior”The artifact is a small TOML file naming kek_id, kek_version, the wrap
algorithm (AES-256-GCM), and the sealed DEK; during a DEK rotation’s
overlap window it additionally keeps the previous wrapped DEK for
dual-read. At boot the deployment fetches the recorded KEK version from
the manager, unwraps the DEK, and uses it as the in-memory key — the
cleartext DEK is never written anywhere. A cold start with the manager
unavailable (or a tampered artifact, or a mismatched KEK version) refuses
to boot; there is no fail-open and no cleartext disk cache. A leaked
local artifact alone is useless without the manager; a compromised
manager alone can re-wrap but cannot read sealed records.
Adopt the model with rotate-kek (it wraps the secrets-table key under a
freshly minted KEK version, keeping every existing sealed record valid),
then remove encryption_key from the secrets file. Rotate either key at
any time — see Security: envelope key rotation.
[events]
Section titled “[events]”| Key | Default | Meaning |
|---|---|---|
level |
info |
Persistence floor: info, notice, warning, critical (at-or-above). Records below it stream live but are never stored. |
retain_days |
31 |
Age-based retention window (at least 1). |
keep_alive_seconds |
30 |
Live-stream keep-alive comment cadence. |
retry_seconds |
10 |
Live-stream reconnect delay advertised on every data frame. |
generate_token_issued |
true |
Whether token-issuing flows record a token-issued event. |
disable_release_check |
false |
Disables the new-release check watcher when true. |
level_jwks_rotate |
notice |
Severity of the key-rotation event. |
origin_levels |
— | Per-origin severity overrides keyed by exact catalog type label (for example JwksRotated); unknown labels abort the boot. |
invalid_login_bands |
see below | From each failure count upward, the given severity applies. Counts positive and strictly ascending; defaults start at 7, 10, 15, 20, 25. |
[events]level = "info"retain_days = 31
[events.origin_levels]JwksRotated = "notice"
[[events.invalid_login_bands]]count = 7level = "notice"[events.login_protection]
Section titled “[events.login_protection]”| Key | Default | Meaning |
|---|---|---|
block_schedule |
see below | Per-address schedule: from each cumulative failure count upward, further failures answer too-many-requests blocked for block_secs. |
stuffing_window_secs |
10800 |
Credential-stuffing rolling window. |
stuffing_threshold |
15 |
Distinct failed credential-pair threshold inside the window. |
stuffing_blacklist_secs |
86400 |
How long a crossed source stays blacklisted. |
Default block schedule: 7 → 60 s, 10 → 600 s, 15 → 900 s, 20 → 3600 s, 25 → 86400 s. A schedule must carry at least one entry with positive, strictly ascending counts and non-zero durations; the stuffing window, threshold, and blacklist duration must each be at least one — otherwise the boot aborts with a field-scoped error.
[[events.login_protection.block_schedule]]count = 7block_secs = 60[events.notify]
Section titled “[events.notify]”Every channel is off until enabled, and an enabled channel must be fully configured — any gap aborts the boot with a field-scoped error.
| Key | Default | Meaning |
|---|---|---|
notify_level_email |
warning |
Per-channel severity gates (info/notice/warning/critical); records below the gate never reach the channel. |
notify_level_matrix |
notice |
Chat-channel gate. |
notify_level_slack |
notice |
Chat-channel gate. |
notify_subject_prefix |
— | Deployment name woven into every notification headline (for example [idp]). |
notify_email_enabled |
false |
Email channel switch. |
notify_matrix_enabled |
false |
Matrix channel switch. |
notify_slack_enabled |
false |
Slack channel switch. |
[events.notify.email]smtp_host = "relay.example"smtp_port = 587smtp_username = "relay-user"smtp_password = "relay-password"from_address = "idp@example.com"recipient_address = "ops@example.com" # fixed recipient; while absent the channel stays inerttheme = "light" # or "dark"
[events.notify.matrix]homeserver_url = "https://matrix.example"room_id = "!room:example.org"username = "svc-user" # with password, or an access_token insteadpassword = "svc-password"access_token = "syt-..."
[events.notify.slack]webhook_url = "https://hooks.example/services/..."
[events.notify.outbound]custom_ca_path = "/etc/ca.pem"danger_accept_invalid_certs = falseallowed_private_networks = ["10.20.0.0/16"] # only for an upstream identity provider on your own networkOutbound HTTPS uses system trust roots by default; custom_ca_path adds a
private CA. Upstream identity providers (federation and their keys) must
resolve to public addresses; when your company’s identity provider runs on
your own network, list that network in allowed_private_networks (CIDR
ranges or single addresses). HTTPS is still required. Avoid the dangerous validation switch; timeouts, the TLS 1.2 floor, and the user agent are fixed
constants. A chat-room channel needs an access token or a username and
password; every channel needs its endpoint.
[events.email_jobs]
Section titled “[events.email_jobs]”The bulk email job queue: execution pacing and the orphan-recovery duty. A batch size below one or a zero staleness, recovery, or rate-limit window aborts the boot.
| Key | Default | Meaning |
|---|---|---|
batch_size |
3 |
Recipients processed per batch. |
batch_delay_ms |
2000 |
Pause between batches, in milliseconds. |
user_delay_ms |
10 |
Pause between individual recipients, in milliseconds. |
send_retry_base_ms |
10 |
Base of the tripling per-recipient send-retry backoff, in milliseconds. |
staleness_seconds |
300 |
How long an open job must sit untouched before recovery considers it orphaned. |
recovery_interval_seconds |
300 |
How often the recovery duty runs. |
info_rate_limit_seconds |
3600 |
Per-address window for informational notices (for example the duplicate-registration owner notice). |
[lifetimes]
Section titled “[lifetimes]”| Key | Default | Meaning |
|---|---|---|
jwk_autorotate_cron |
0 30 3 1 * * * |
Signing-key auto-rotation schedule, seven-field cron form (second minute hour day-of-month month weekday year), server-local time. An unparseable value disables future automatic rotation; manual rotation keeps working. |
[hashing]
Section titled “[hashing]”| Key | Default | Meaning |
|---|---|---|
argon2_m_cost |
19456 |
Argon2id memory cost per hash, in KiB (documented floor 19456). |
argon2_t_cost |
2 |
Iteration cost. |
argon2_p_cost |
1 |
Parallelism cost. |
max_hash_threads |
2 |
Maximum simultaneous hashes; also feeds the automatic HTTP worker derivation. |
The defaults are the OWASP Password Storage parameters for Argon2id. Memory is the parameter that dominates both the cost of guessing a password and the memory a sign-in transiently needs, so raising it is the trade to make deliberately; see Sizing.
A memory cost below the 19456 KiB floor, a zero iteration or parallelism cost, or a zero hash-thread bound aborts the boot with a field-scoped error. Existing password hashes record their own parameters, so changing these costs affects new hashes only — an already-stored credential keeps verifying.
[device_grant]
Section titled “[device_grant]”The RFC 8628 device authorization grant.
| Key | Default | Meaning |
|---|---|---|
code_lifetime_secs |
300 |
Lifetime of a pending device code. |
user_code_length |
8 |
Length of the user code; always a prefix of the device code and must stay below its fixed 64 characters. |
rate_limit_secs |
— | Optional per-address rate-limit window for flow starts; unset (default) disables limiting entirely. |
poll_interval_secs |
5 |
Advertised poll interval. |
refresh_lifetime_hours |
72 |
Device-bound refresh-token lifetime. |
[webauthn]
Section titled “[webauthn]”Passkey (WebAuthn) ceremony timing and policy.
| Key | Default | Meaning |
|---|---|---|
challenge_expiry_secs |
60 |
Lifetime of one ceremony challenge (the authenticator timeout communicates the same value in milliseconds). |
ceremony_lifetime_secs |
90 |
Lifetime of the server-side pending ceremony state. |
mfa_cookie_hours |
2160 |
Lifetime of the remembered second-factor trust cookie. |
force_user_verification |
false |
Demand user verification in every ceremony, regardless of account kind. |
no_password_expiry_with_passkey |
false |
Clear the password expiry of an account with a password when it registers its first passkey. |
renew_mfa_on_session_renew |
false |
Re-require the second-factor ceremony on a silent re-authentication of an already second-factor-marked session. |
Every key of this section has a GUARD_… environment name; see the environment reference.
[users]
Section titled “[users]”The account area: administrative listing, account pictures, self-service.
| Key | Default | Meaning |
|---|---|---|
list_mode_threshold |
1000 |
Account count at or above which the administrative list switches to the paged response. |
picture_storage |
disabled |
disabled (uploads refused), database, or file. |
picture_size_limit_bytes |
524288 |
Largest accepted picture upload. |
force_admin_mfa |
false |
Demand an active second factor on administrative and delegated administrative sessions. |
self_deletion_enabled |
false |
Allow accounts to delete themselves. |
registration_enabled |
false |
Serve the self-service registration page and accept submissions. |
allow_open_redirects |
false |
Honor a submitted registration or reset redirect target matching no registered client redirect URI. |
action_link_lifetime_minutes |
30 |
Lifetime of single-use email action links (reset, confirmation, welcome), in whole minutes. |
relax_reset_binding |
false |
Accept a reset link open or submission without the binding cookie. |
preferred_username_required |
false |
Refuse clearing the preferred username. |
preferred_username_immutable |
false |
Refuse overwriting a set preferred username without the administrator force flag. |
preferred_username_blacklist |
— | Preferred usernames this deployment refuses (for example ["admin", "root"]). |
Every key of this section has a GUARD_… environment name; see the environment reference.
[clients]
Section titled “[clients]”Relying-party client registry policy.
| Key | Default | Meaning |
|---|---|---|
dynamic_registration_enabled |
false |
Master switch; while off, every dynamic registration endpoint answers not-found and discovery omits the registration endpoint. |
dynamic_registration_token |
— | When set, registration requires this bearer token and the per-address rate limit does not apply. |
dynamic_registration_rate_limit_seconds |
60 |
Per-address registration window when no static token is configured. |
dynamic_auto_rotate_registration_token |
true |
Self-updates re-issue the per-client registration token. |
dynamic_allowed_scopes |
openid, profile, email, offline_access, groups |
Scope names a dynamically registered or unmanaged client may request (the built-in scope catalog). |
dynamic_default_scopes |
openid, profile |
Scope names granted when a request omits them. |
dynamic_access_token_lifetime_seconds |
1800 |
Access-token lifetime for dynamically registered clients. |
ephemeral_clients_enabled |
false |
URL-identified unmanaged clients, derived at request time from the document served at their identifier. |
ephemeral_grant_types |
["authorization_code"] |
Flow set granted to unmanaged clients regardless of the advertised document. |
ephemeral_force_mfa |
false |
Force-MFA flag granted to unmanaged clients. |
ephemeral_cache_ttl_seconds |
3600 |
In-memory cache lifetime for derived unmanaged clients. |
danger_strip_unsupported_ephemeral_grants |
false |
Strip unsupported grant names from an advertised document instead of rejecting it. |
danger_allow_ephemeral_resources_without_list |
false |
Let an unmanaged client without a resource allow-list pass any resource request. |
danger_allow_loopback_port_redirects |
false |
Let dynamic and unmanaged clients match a loopback redirect URI that differs only in port. |
cleanup_interval_seconds |
3600 |
Unused-dynamic-client cleanup duty cadence. |
cleanup_never_used_grace_seconds |
3600 |
Creation-age grace period before a never-used client becomes removable. |
cleanup_unused_days |
0 |
Days of inactivity before a used client becomes removable; zero disables that path. |
allowed_custom_origin_schemes |
[] |
Non-http(s) Origin schemes accepted at client-bound browser surfaces. |
Every key of this section has a GUARD_… environment name; see the environment reference.
Secrets file
Section titled “Secrets file”The secrets file (generate-secrets --output secrets.toml) carries the
32-byte encryption key plus free-form named values that configuration
entries reference through the $SECRETS.<name> marker (for example
password = "$SECRETS.pg_password"). Secrets are operator-supplied and never
baked into container images; mount the file read-only.