If you discover a security vulnerability, please report it privately via GitHub Security Advisories. Do not open a public issue.
We aim to acknowledge reports within 48 hours and provide a fix or mitigation plan within 7 days.
| Threat | Mitigation |
|---|---|
| Data at rest compromise | Envelope encryption: per-secret DEK (AES-256-GCM) wrapped by master KEK. AAD binds the secret key name to ciphertext at the GCM layer. HMAC-SHA256 with independent INTEGRITY_KEY binds all encrypted fields. Two integrity layers: AAD catches key-name rebinding, HMAC catches all tampering even if the encryption key is compromised. |
| Stolen session or token | Cloudflare Access validates at the edge, Worker re-validates the JWT. Service tokens must be registered with name, role, and scopes. Direct auth (--secret) validates SHA-256 hash with timing-safe comparison. Tag-based RBAC restricts access to matching secrets. |
| Identity spoofing via JWT | JWT common_name fallback only accepted when payload.type === "app" (Cloudflare-issued service token JWT). Interactive IdP JWTs cannot inject a common_name to impersonate a service token. |
| Privilege escalation | RBAC with last-admin protection. Policy management requires interactive admin auth. Users can be disabled without deletion. ALLOWED_EMAILS fallback grants reader (not admin). Self-deletion blocked. |
| Audit tampering | SHA-256 hash-chained audit log with timestamp in hash input. Each entry links to the previous. Modifying or deleting entries breaks the chain. Verifiable with hfs audit-verify. |
| Insider bulk exfiltration | disable_export feature flag blocks bulk export. Tag-based RBAC limits scope. All access logged with identity and request ID. |
| Compromised server | E2E secrets encrypted client-side with age before reaching the Worker. A compromised Worker or database sees only age ciphertext. Use --private for personal secrets or --e2e for team-shared secrets. Service tokens can have their own age keys (--age-key) for zero-knowledge CI/CD. |
| SSRF via webhooks | Webhook URLs must be HTTPS. Private/reserved IPv4 ranges blocked (10.x, 172.16-31.x, 192.168.x, 169.254.x, 100.64-127.x CGNAT, 127.x loopback). IPv6 ULA (fc/fd) and link-local (fe80) blocked. IPv4-mapped IPv6 (::ffff:) resolved and checked. Bare hostnames rejected. |
| Replay attacks (WARP) | Timestamp-based HMAC challenge-response with 2-minute window in both directions. Future timestamps rejected. Fingerprint bound to ZT CA certificate. |
| Threat | Why |
|---|---|
| Compromised Cloudflare account | Dashboard access can read Wrangler secrets (the master key). Same trust boundary as any cloud-hosted vault. E2E secrets remain protected - age decryption requires the client's private key. |
| Malicious Worker deployment | A modified Worker can read the master key at runtime. Mitigate with CI/CD controls, branch protection, and Access policies. E2E secrets remain protected. |
| DDoS | No application-level rate limiting. Relies on Cloudflare's edge DDoS protection. |
| ReDoS via admin regex | FLAG_SECRET_NAME_PATTERN accepts admin-set regex patterns (200-char cap). V8's irregular engine mitigates most backtracking, but nested quantifiers could cause CPU timeouts. Only admins can set this flag. |
| Service token WARP bypass | Service tokens are exempt from WARP enforcement by design. Machine-to-machine traffic cannot enroll in WARP. The exemption is set server-side based on D1 token lookup and cannot be forged. |
| Audit chain mutation under concurrency | The hash chain self-heals under concurrent inserts by updating the predecessor hash. This means committed entries can be mutated (to correct chain order). A compromised database admin could exploit this window. hfs audit-verify detects tampered chains but not legitimate self-heals. |
Internet -> Cloudflare Edge (DDoS, TLS, Access) -> Worker (JWT, RBAC, encryption) -> D1 (ciphertext only)
The master key (ENCRYPTION_KEY) and optional INTEGRITY_KEY are the root of trust. They are stored as Wrangler secrets, encrypted at rest by Cloudflare, and available only at Worker runtime.
- Set
ENCRYPTION_KEYviawrangler secret put(never in code or wrangler.jsonc) - Configure Cloudflare Access with your IdP and appropriate policies
- Register all service tokens with minimal scopes (
readfor CI,read,writefor deploy); use--secretfor direct auth - Add users to the
userstable with appropriate roles (don't rely onALLOWED_EMAILS)
- Run
hfs scanon your codebase to find and migrate hardcoded secrets - Set a separate
INTEGRITY_KEYviawrangler secret putfor HMAC key separation - Use
--age-keywhen registering service tokens for zero-knowledge CI/CD - Run
hfs re-encryptto migrate all secrets to envelope encryption - Enable
require_descriptionandrequire_tagsflags for organizational discipline - Set
max_secretsto prevent unbounded growth - Set
disable_exportin production to prevent bulk exfiltration - Enable
require_warpflag to enforce Cloudflare WARP enrollment (docs) - Configure Access policy to require hardware keys (
hwk) for interactive sessions - Use tag-based RBAC: create roles with policy rules to limit access by team/environment
- Use
hfs set --privatefor personal secrets (only your key can decrypt) - Use
hfs set --e2efor team secrets (RBAC-based recipients, revocable viarewrap) - Run
hfs keygen --registerand back up your identity file securely - After revoking a user, run
hfs rewrap --allto re-encrypt without their key - Periodically run
hfs audit-verifyto check hash chain integrity - Set
audit_retention_daysappropriate to your compliance requirements - Set
FLAG_SECRET_NAME_PATTERNto enforce key naming conventions (use simple patterns only) - Verify config file permissions:
ls -la ~/Library/Preferences/hfs-nodejs/config.jsonshould be0600
See Encryption Architecture - Key Rotation for step-by-step instructions and diagrams covering master key, integrity key, and age identity rotation.
| Property | Implementation |
|---|---|
| Encryption | AES-256-GCM via Web Crypto API (crypto.subtle). Per-secret random DEK, wrapped by master KEK. |
| AAD binding | Secret key name passed as GCM Additional Authenticated Data. Prevents ciphertext transplant between keys. |
| Integrity | HMAC-SHA256 with independent key (or HKDF-derived). Binds key name + ciphertext + IV + encrypted DEK + DEK IV. |
| Key derivation | HKDF-SHA256 with fixed application salt "secret-vault-hmac-v1" and info "hmac-integrity". |
| E2E encryption | age (X25519 + ChaCha20-Poly1305). Client-side, server never sees plaintext. |
| Credential hashing | SHA-256 for service token secrets. Timing-safe comparison via constant-time XOR loop. |
| Audit chain | SHA-256 hash of `prevId |
The Worker uses only three runtime dependencies:
hono+@hono/zod-openapi- HTTP framework and schema validationjose- JWT verification (JWKS)
All cryptographic operations use the Web Crypto API (crypto.subtle), built into the Cloudflare Workers runtime. No third-party crypto libraries.