Skip to content

Security notes

Recorded defaults, enforced at boot:

Key Default Constraint
argon2_m_cost 19456 Memory cost per hash in KiB (the OWASP parameter); below 19456 aborts the boot.
argon2_t_cost 4 Zero aborts the boot.
argon2_p_cost 8 Zero aborts the boot.
max_hash_threads 2 Zero aborts the boot; also reserves capacity in the automatic HTTP worker derivation.

hash-password on the CLI hashes from stdin with the same defaults.

Credential submissions at the login page are gated by a proof-of-work challenge:

  • POST /auth/v1/pow issues a plain-text challenge (idp1.<payload>.<signature>) keyed from the active encryption-key material.
  • Difficulty: 12 leading zero bits in the solution digest.
  • Challenges live 300 seconds and work exactly once (single-use consumption registry).
  • Validation happens before any account or database work.

The gate applies to credential POSTs; navigations (such as the authorization entry point) are not gated.

Configured under [events.login_protection]:

  • Per-address block schedule (defaults: 7 failures → 60 s, 10 → 600 s, 15 → 900 s, 20 → 3600 s, 25 → 86400 s): from each cumulative failure count upward, further failures answer too-many-requests blocked.
  • Credential-stuffing detection: rolling window (default 10800 s), distinct failed credential-pair threshold (default 15), blacklist duration (default 86400 s).
  • Scanner-path blacklisting ([security]): configured paths (for example /wp-admin, /.env) blacklist the requester instead of being served, for blacklist_minutes (default 5). Bans only ever extend; permanence wins.
  • The blacklist middleware is the outermost layer of the request pipeline; default security headers are applied innermost on every response.
  • Every browser write carries the session-established anti-CSRF header; reads never carry a body.
  • The token is fetched with the session (session document) and can be re-issued via GET /auth/v1/oidc/sessioninfo/xsrf.
  • Administrative handlers apply the shared gate — caller resolution with the forced-second-factor, CSRF, and origin guards — before any work.
Cookie Purpose
TinyguardSession The session cookie; the value is the encrypted session id and nothing else.
TinyguardMfa Remembered second-factor trust (lifetime webauthn.mfa_cookie_hours, default 2160 h).
TinyguardSessionFedCM Experimental FedCM mode only.

Secure deployments issue the __Host-TinyguardSession form with Path=/, HttpOnly, Secure, SameSite=Lax. The tenant is bound inside the encrypted cookie value, so a cookie presented to another tenant simply fails to resolve.

  • generate-secrets --output secrets.toml writes the secrets file: the 32-byte encryption key plus free-form named values.
  • Configuration strings reference values with the exact form $SECRETS.<name>; an unreferenced name, or any other $SECRETS spelling, aborts the boot.
  • Client secrets and signing-key private halves are sealed with the encryption key; responses never echo secrets.
  • In containers, secrets are operator-supplied and never baked in — mount the file read-only (-v "$PWD/secrets.toml:/app/secrets.toml:ro"). On macOS, mount from a non-symlinked path.
  • generate-enc-key prints a fresh 32-byte base64 key; bootstrap get/purge prints or purges generated one-time secrets.
  • The Vault configuration source verifies HTTPS against system trust roots; remote plain HTTP is refused. A local loopback sidecar may use HTTP. Per-tenant secret-manager selection supports a built-in write-only managed store, Vault KV v2, operator-mounted files, and operator-provisioned cloud credential sidecars for AWS, Google, and Azure. Cloud self-service workload identity onboarding is not yet shipped.
  • Optionally, the [encryption] configuration section moves the encryption key into the envelope model — root KEK in the manager, wrapped DEK in a local artifact. See Envelope key rotation and Configuration: [encryption].

With the [encryption] section opted in, two independent rotations exist, both operator CLI subcommands and both following the signing-key rotation contract — a failed rotation leaves the previous key fully in effect:

  • rotate-kek — writes a fresh KEK version to the manager, re-wraps the unchanged DEK under it, and atomically replaces the wrapped-DEK artifact (temp file + rename). No sealed record is touched, so it is cheap and safe at any cadence; the previous KEK version remains in the manager for rollback. With no artifact present yet, the same command adopts the model: it wraps the secrets file’s existing key under a first KEK version, keeping every sealed record valid — then remove encryption_key from the secrets file.
  • rotate-dek — mints a new DEK and writes it (wrapped) as the new active version while keeping the previous wrapped DEK in the artifact for dual-read; then a one-shot re-seal pass re-encrypts every sealed record (client secrets, signing-key private halves, API-key digests, MFA seeds, tenant secret-manager credentials, upstream provider secrets) through the store port, a completion-marker key records the finished migration, and the retained previous DEK is dropped from the artifact. During the window every record still opens — values sealed under either DEK decrypt (new sealing always uses the active key).

Failure shapes: if the re-seal pass fails, every record it changed is restored and the pre-rotation artifact is put back, so the previous DEK is fully in effect and a single-key boot works. If the rollback itself cannot complete, the dual-version artifact is kept — boots dual-read every record — and re-running rotate-dek resumes the same rotation (already re-sealed records are skipped) instead of stacking another one. Run both commands with the nodes stopped or quiesced, like every rewrite pass. Outstanding browser session cookies do not survive a DEK rotation (their values are sealed under the old key); users simply sign in again.

  • Listen schemes http, https, http+https, unix-http, unix-https; TLS material is PEM (a .der key filename selects DER parsing) and missing files fall back to generated self-signed material.
  • proxy_enabled = true derives HTTPS URLs and trusts forwarded client-IP headers only from the listed trusted_proxies (addresses or networks; at least one required). Without proxy mode, forwarded headers are ignored.
  • Outbound HTTPS uses system trust roots by default; custom_ca_path adds a private CA. Avoid danger_accept_invalid_certs; timeouts, the TLS 1.2 floor, and the user agent are fixed constants.
  • Signing keys rotate on the jwk_autorotate_cron schedule or manually via POST /auth/v1/oidc/rotate_jwk; rotation emits a JwksRotated event at the configured level_jwks_rotate severity.
  • The event pipeline enforces a persistence floor ([events] level) and retention (retain_days); invalid-login severity bands and per-origin overrides are validated at boot.

force_admin_mfa demands an active second factor on administrative and delegated sessions; self_deletion_enabled (default off) governs the anti-lockout self-deletion refusal; allow_open_redirects (default off) governs unregistered redirect targets on registration and reset.