Security notes
Password hashing (Argon2id)
Section titled “Password hashing (Argon2id)”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.
Proof-of-work login gate
Section titled “Proof-of-work login gate”Credential submissions at the login page are gated by a proof-of-work challenge:
POST /auth/v1/powissues 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.
Login protection and blacklisting
Section titled “Login protection and blacklisting”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, forblacklist_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.
CSRF pairing
Section titled “CSRF pairing”- 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.
Session cookies
Section titled “Session cookies”| 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.
Secrets file handling
Section titled “Secrets file handling”generate-secrets --output secrets.tomlwrites 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$SECRETSspelling, 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-keyprints a fresh 32-byte base64 key;bootstrap get/purgeprints 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].
Envelope key rotation
Section titled “Envelope key rotation”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 removeencryption_keyfrom 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.
TLS and proxying
Section titled “TLS and proxying”- Listen schemes
http,https,http+https,unix-http,unix-https; TLS material is PEM (a.derkey filename selects DER parsing) and missing files fall back to generated self-signed material. proxy_enabled = truederives HTTPS URLs and trusts forwarded client-IP headers only from the listedtrusted_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_pathadds a private CA. Avoiddanger_accept_invalid_certs; timeouts, the TLS 1.2 floor, and the user agent are fixed constants.
Key rotation and events
Section titled “Key rotation and events”- Signing keys rotate on the
jwk_autorotate_cronschedule or manually viaPOST /auth/v1/oidc/rotate_jwk; rotation emits aJwksRotatedevent at the configuredlevel_jwks_rotateseverity. - 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.
Account-area switches
Section titled “Account-area switches”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.