Security
QuickZTNA's cryptographic commitments, threat model, authentication flows, and audit guarantees. This is the doc to hand to your security team or auditor — honest about what we defend against and what we don't.
Post-quantum cryptography
Each tunnel's PSK is derived from a combination of X25519 ECDH and ML-KEM-768 KEM shared secrets:
// ztna/client/pkg/crypto/pqc.go
PSK = HKDF-SHA256(
secret: X25519_shared || ML-KEM_shared,
salt: nil,
info: "quickztna-pqc-wg-psk-v1",
length: 32,
) Implementation files:
ztna/client/pkg/crypto/pqc.go— keygen + encap/decap + PSK derivation (Go stdlibcrypto/mlkem, no external library)ztna/client/pkg/agent/pqc.go— per-peer state cacheztna/client/pkg/wireguard/engine.go— PSK injection into WireGuard IPCbackend/src/handlers/key-exchange.ts— server-side relaybackend/migrations/043_pqc_key_exchange.sql— 4 nullable columns onkey_exchanges
Control plane — TLS + JWT
- TLS: Caddy auto-TLS via Let's Encrypt. HSTS preload, TLS 1.2+, modern cipher suite only.
- JWT: ES256 (ECDSA P-256 + SHA-256). Issuer
quickztna, audiencequickztna-api. 1-hour access token, 30-day refresh token. - Refresh cookie:
__Host-refresh_token— HttpOnly, Secure, SameSite=Strict, Path=/. The__Host-prefix pins the cookie to exact origin with no Domain attribute. - WebSocket auth: token via
Sec-WebSocket-Protocolsubprotocol header — never in URL. Prevents token leak to proxy logs, CDN caches, browser history. - DERP relays: TLS 1.3 minimum with X25519Kyber768 hybrid KEM enabled at the TLS layer.
At rest
- PostgreSQL: disk encryption (Docker volume on encrypted host)
- Secrets vault: AES-256-GCM. Per-org DEKs, sealed by master KEK in env
- R2 object storage: server-side encryption (Cloudflare-managed keys)
- Backups: daily
pg_dumpto R2, 7-day local retention, encrypted in transit and at rest
Password hashing
- PBKDF2-SHA256, 100,000 iterations, 32-byte key, random salt per password
- Legacy SHA-256 hashes auto-upgraded on successful login
- Timing-safe verification: dummy PBKDF2 against a pre-computed hash for non-existent users — login timing is constant (anti-enumeration)
TOTP MFA
- RFC 6238 compliant, 30-second window, ±1 step drift tolerance
- Replay protection: used codes cached in KV for 90 seconds
- Backup codes: 10 one-time codes, SHA-256 hashed at rest
Authentication model
Email+password → JWT pair; refresh via __Host- cookie
SAML / OIDC → JWT pair. Same tokens as password login.
Browser OAuth flow → one-time code → JWT pair
Auth key on first boot → permanent node_key. Auth key is revocable.
Long-lived until revoked. Org-scoped.
Platform operator only. Cannot bypass plan gates at backend.
Refresh token binding: refresh tokens are bound to a device
fingerprint (UA + /24 IP prefix). Token theft across devices fails with
DEVICE_MISMATCH.
Account lockout: 10 failed logins per email per 5 min → 15-minute lockout. Dummy PBKDF2 ensures lockout response is timing-identical to invalid-credentials response.
Token blacklist: password change and account deletion set
jwt_blacklist:<user_id> in KV (TTL = 30d, matching refresh
token). All existing tokens reject immediately.
CSRF protection
Origin check on every state-changing request. Only whitelisted origins
(login.quickztna.com, dev hosts) accepted. Combined with CORS
(only whitelisted origins in Access-Control-Allow-Origin), this
is sufficient for a same-origin SPA. No X-CSRF-Token cookie — Origin check + HttpOnly cookies is the
chosen model.
Rate limiting
10 / 5 min · 10 / 5 min (separate counters)
5 / 5 min
5 / 5 min
5 / 5 min
5 / 5 min
200 concurrent
10 / min · 4 KB payload max
Client IP detection: cf-connecting-ip →
x-real-ip → x-forwarded-for.
x-forwarded-for is never trusted first (spoofable).
Secrets handling
- Private keys (CAs, signing keys, WireGuard static keys) encrypted with AES-256-GCM before DB insertion. Fail hard if encryption fails — never fall back to plaintext
- Environment:
JWT_PRIVATE_KEY,JWT_PUBLIC_KEY, master KEKs live in Docker env vars (not in the image) - Rotation: secrets vault items support scheduled rotation via
secret-rotationcron
Audit trail
Every state change writes to audit_logs with:
org_id,user_id,action,resource_type,resource_iddetails(JSONB),ip_address,user_agent,created_at
Immutable — no UPDATE, no DELETE paths in application code. 90-day retention via audit-log-retention cron. Exported to Loki for cross-org observability.
Data handling
- Org isolation: every DB query filters by
org_idor fails. Enforced at middleware level; never trusted from client input - Cascade deletes: preserved where orphan rows are safe. SET NULL used for audit/compliance tables so records survive user deletion
- GDPR:
/auth/delete-accountcascades user-scoped data; anonymizes audit rows; notifies via email - PII in logs: user-agent, IP, email truncated. Never passwords, keys, or tokens
- Docker capabilities: firewall container has only
NET_ADMIN+NET_RAW.SYS_ADMINremoved (it was equivalent to root)
Threat model
What we defend against
- Passive network adversary — TLS + WireGuard PQC tunnel protects everything in transit
- Future quantum adversary — ML-KEM-768 hybrid key exchange; stored traffic cannot be decrypted post-quantum
- Credential theft — 1h access token + device-bound refresh + blacklist-on-change + MFA + posture
- Server-side compromise of one org — org isolation means one tenant cannot reach another's data
- Cross-site request forgery — Origin check + SameSite=Strict cookies
- XSS token theft — access token in memory only (never localStorage); refresh token in HttpOnly cookie
- Bruteforce — per-IP and per-email rate limits; account lockout; timing-safe password verification
- Replay attacks — TOTP replay cache; refresh token single-use (rotates on each refresh)
- Log injection — structured logs via pino; user input never concatenated into log lines
What we do NOT defend against
- Root-level compromise of the production server — would expose everything. Mitigation: UFW firewall, non-root container users, secrets in env only
- Compromise of the CA signing key — used for issued x509/SSH certs. Mitigation: short-lived certs (≤ 1h), rotation
- Malicious authorized admin — an org admin with legitimate credentials can exfiltrate their own org's data. Mitigation: audit logs, session recording
- Supply-chain attacks on upstream Go/Node deps — Mitigation: lockfiles, Dependabot, minimal deps
- Data residency — data currently lives on servers in the chosen region only. Multi-region replication available on request
Vulnerability disclosure
Security issues: email security@quickztna.com.
PGP key on /.well-known/security.txt.
We commit to:
- Acknowledging receipt within 24 hours
- Triaging and providing status within 5 business days
- Coordinated disclosure on a timeline appropriate to severity
- Public credit in release notes (opt-out available)
Out-of-scope: rate limit abuse that doesn't exfiltrate data, missing best-practice headers on non-sensitive endpoints, self-XSS.
Compliance
Policies and readiness documents available on request to sales@quickztna.com:
- SOC 2 readiness
- Audit log policy (90-day retention, immutable, exportable)
- Business continuity (daily backups, 25h staleness alerting, streaming replication)
- Data handling policy (classification + retention)
- Information security policy