Security posture
The controls below are implemented end-to-end. This is still a young codebase: we publish our threat model and our disclosure process, and we will earn the rest with time and operator feedback.
HMAC-chained audit log
Per-row HMAC-SHA256 signed with Z4J_AUDIT_CHAIN_SECRET, a dedicated key separate from the application secret, plus a prev_row_hmac chain and a separately authenticated head and row count. Detects modification and deletion by the application, a downgraded adapter, or any writer that skips the audit service. Not a defense against a hostile database role: see the threat model.
Argon2id password hashing
64 MiB memory, 3 iterations, parallelism 4. Minimum 12 characters; passwords shorter than 16 characters require 3-of-4 character classes, and every password is checked against the denylist.
Wire-protocol HMAC v2
Every WebSocket frame signed. Replay guard (±60s + nonce + monotonic seq). Session-bound frames. SignatureError closes the socket with code 4403.
CSRF double-submit
__Host-prefixed CSRF cookie + X-CSRF-Token header required on every mutating endpoint. Constant-time comparison.
Secure cookies
HttpOnly session cookie, Secure + SameSite=Lax in prod, __Host- prefix. No JS access to session tokens.
IP rate limits
Per source IP per brain worker: login (20/min), invitation preview plus accept (30/min shared), password-reset request plus confirm (10/min shared). Multi-worker fleets multiply the effective ceiling; use a shared reverse-proxy limit when required.
Account lockout
10 failed login attempts cause a 15-minute account lockout by default. Combines with IP rate limits for layered defense.
Redaction pipeline
Rule-based recursive scrubber at the agent, with built-in key and value patterns plus operator extensions. Defense-in-depth re-scrub on z4j.
SSRF hardening
Private and special-use destinations are blocked for webhooks, channel URLs, and SMTP. HTTP targets use scheme allow-lists and DNS-pinned dialing; SMTP is re-resolved before send.
HSTS + CSP
Strict-Transport-Security (1yr) plus a Content-Security-Policy with hashed inline scripts and style blocks, required inline style attributes, and no unsafe-eval. X-Frame-Options: DENY; Referrer-Policy: strict-origin.
RBAC everywhere
Every mutating endpoint checks membership + role. UI mirrors the server policy (useCan hook). Cross-project access = 403.
Secret storage
Tokens are stored as keyed hashes rather than plaintext. Production deployments provide server secrets through environment or a secret manager; packaged SQLite deployments persist generated secrets in /data/secret.env. No secret-recovery backdoor.
What we publish
A control plane is worth what its weakest documented boundary is worth, so we write the boundaries down rather than only the controls.
Threat model
Who we defend against, what we assume, and which threats are explicitly out of scope. Includes the actors table and the assumptions each control rests on.
Learn moreSecurity policy
Reporting process, response targets, supported version lines, safe harbour, and the published advisory list with fixed-in versions.
Learn moreAdvisories
Coordinated-disclosure advisories, published with a CVE ID once a fix has been broadly deployed.
Learn moreAudit chain, and where it stops
How the chain is built and verified, what a clean verification does and does not tell you, and how to export a head off-box so the check survives a hostile database role.
Learn moreReporting a vulnerability
Please report security issues privately to [email protected]. Include a clear reproduction, expected vs. actual behavior, and a severity estimate.
Response targets: acknowledgment within 48 hours; preliminary assessment within 5 business days; fix and advisory within 30 days of confirmation, or within 7 days when the issue is critical. Coordinated disclosure runs 90 days from your first report, so triage time never comes out of your window. These numbers are copied from the security policy in the repository, which is the version that binds us.
What we already know about is listed too. Those targets cover something you report to us. A weakness we found ourselves and decided to defer gets no clock, because nobody is waiting on us; it gets a public entry, a stated mitigation, and the release it is scheduled for. One is open today, and we rate it critical for anyone whose threat model includes the database role: such a role can defeat both in-database boundaries, so treat those credentials as equivalent to full control of z4j's integrity guarantees. The fix is a design change rather than a patch, so it is scheduled behind a major boundary. The known-limitations list and the threat model give both paths in full, along with the mitigation and what the fix has to prove before the entry comes off the list.
We credit reporters in the advisory (opt-in). We do not offer a paid bounty at this time.
Read the full security docs
Threat model, controls, and the CVE disclosure policy: all on z4j.dev.