Skip to content

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:

Terminal window
IMAGE=registry.tinyfactory.ai/tinyblox/guard:0.3.2
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" -w /work \
"$IMAGE" generate-config --output config.toml
docker run --rm -v "$PWD:/work:ro" -w /work \
"$IMAGE" validate-config --config config.toml --secrets secrets.toml

Rules 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 _FILE form 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 the GUARD_ prefix is not read.
  • idp-server config check loads the configuration exactly as serve would, environment included, validates it without touching the network and exits non-zero with the reason; --print shows the effective values with secrets redacted. Run it before a rollout.
  • GUARD_VAULT_CONFIG=true loads the whole configuration from a vault-style source (vault.toml with address/token/mount/path, or GUARD_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 at data.data.config. Malformed vault.toml fails the boot.
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.

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.

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.
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.
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.
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.

Key Default Meaning
grace_window_seconds 30 After start, both subsystems report healthy for this window.
Key Default Meaning
format plain One of plain, json.
level info One of trace, debug, info, warn, error.

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.
Key Default Meaning
enabled false Serve the browser-facing web-identity discovery document at the host root.
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.

Key Default Meaning
node_name — Optional multi-node declaration; inert on a single node.

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.

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 = 7
level = "notice"
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 = 7
block_secs = 60

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 = 587
smtp_username = "relay-user"
smtp_password = "relay-password"
from_address = "idp@example.com"
recipient_address = "ops@example.com" # fixed recipient; while absent the channel stays inert
theme = "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 instead
password = "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 = false
allowed_private_networks = ["10.20.0.0/16"] # only for an upstream identity provider on your own network

Outbound 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.

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).
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.
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.

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.

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.

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.

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.

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.