TLS 1.3 · 1.2 min
HTTP → redirect
Certificates Let's Encrypt / corporate CA
client_max_body_size,
client_*_timeout,
limit_conn,
large_client_header_buffers. Earlier and coarser than
limits ③⑤: on the raw connection, not on an identified client.
max-age=1y; includeSubDomains
CSP
default-src 'self'; object-src 'none'; base-uri 'self';
form-action 'self'
X-Content-Type-Options nosniff
X-Frame-Options DENY
Cross-Origin-Opener-Policy same-origin
Cross-Origin-Resource-Policy same-origin
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy
Cache-Control no-store on
sensitive responses
Clear-Site-Data on logout
Token routes no-referrer
Server · X-Powered-By off
true
Allow-Methods GET/POST/PUT/DELETE/PATCH
Allow-Headers
Authorization, Content-Type, X-Request-Id ·
X-CSRF-Token
v2
Expose-Headers Retry-After, X-RateLimit-Remaining, X-Request-Id
Preflight Max-Age 3600
API rate limiting per-user, by role · Redis
>
/refresh).brute:ip ·
brute:account
429 + Retry-After rather than holding the request open:
otherwise the hanging connections themselves become a load vector.
argon2 verify
(constant-time): the response takes as long as it would for a real
account.
chat_id:
5 failed entries burn the attempt — get a new code in the web app.
The bot's public webhook is under
rate-limit
by IP/source; an expired and an invalid code are indistinguishable
(a single 410 LINK_EXPIRED). The bot is open to everyone,
so the line is stricter than for the workspace-closed Slack.
argon2id
slow
memory 19 MB
iterations 2
parallelism 1
SHA-256
fast
AES-256-GCM
by key
smtp_settings.password_encsources.credential_encsources.webhook_secret_encai_providers.api_key_enctools.credential_encnotification_channels.url_encnotification_channels.secret_encmfa_secret v2secrets.token_urlsafe()
≥32B
Constant-time hmac.compare_digest()
Token hash in the DB SHA-256
JWT signing HMAC-SHA256
mfa_secret — AES-256-GCM (the secret is
needed reversibly: the server computes TOTP from it). Key from
env/vault, key rotation across several versions (key ID in the
data).
Recovery codes are hashed, not encrypted (argon2id),
like passwords: one-time, the server only verifies them.
audit_log(actor, action, target, result, ip, ua),
records the actor's intent, not a row diff (off-the-shelf
row-versioners handle that). Application default: OWASP ASVS V7,
NIST SP 800-92.old→new only for the non-confidential: role,
status). The log is undeletable and permanent — PII that lands here
can no longer be scrubbed out.
result=failure isn't lost if the main operation rolls
back.
audit_retention_days config, defaulting to
“forever.” The cleanup job itself and the export to cold storage are
designed together with hash-chaining — deletion must play nicely with
immutability, and the term is set by compliance (SOC 2 / ISO 27001).
pgaudit v2: SQL operations
are logged in the database itself, which the application audit can't
see. Forensics for compliance (SOC 2 / ISO 27001).