Threat Model & OWASP ASVS L1 Self-Attestation

The threats Vaulted is designed to defeat, the threats it isn't, and a control-by-control attestation against an industry baseline.

Version 1.0 · Published 2026-04-27 · Source: main · Author: Maxim Novak

About this document

This is a self-attestation, not a third-party audit. It exists so you can see exactly what Vaulted claims to protect, what it doesn't, and how each design decision maps to recognised controls. We will commission an independent review and publish its report alongside this page when budget allows. Until then, you can verify the core zero-knowledge claim yourself in five minutes with browser DevTools.

1. Scope & assumptions

This model covers the production deployment of vaulted.fyi including its web frontend, API routes, and Upstash Redis storage. It also covers the published CLI and MCP server insofar as they interact with the same API.

Out of scope:

  • The user's endpoint device (browser, OS, extensions, clipboard, screen).
  • The communication channel through which the share link is delivered (email, Slack, SMS, etc.).
  • The recipient's subsequent handling of the plaintext.
  • Vulnerabilities in Vercel or Upstash beneath the API surface we consume.

2. Assets & trust boundaries

Primary asset: the plaintext secret. It must never reach the server, persist outside the sender's and recipient's browsers, or be recoverable from server-side state alone.

Secondary assets: ciphertext, IVs, view counters, expiry timestamps, and the integrity / availability of the service.

Trust boundaries (in order of decreasing trust):

  1. Sender browser — trusted with plaintext and key.
  2. Recipient browser — trusted with plaintext and key after entering the link.
  3. Network between client and server — untrusted; mitigated by TLS 1.3 and the design choice that only ciphertext crosses it.
  4. Vaulted server (us) — semi-trusted. Treated as a passive ciphertext store. The threat model assumes Vaulted itself could be compromised without exposing plaintext.
  5. Upstash Redis — same trust level as the server. Encrypted blobs only.

3. Threat actors

Passive network observer. ISP, on-path actor with access to TLS metadata. Defeated by TLS and by the absence of plaintext or key on the wire.

Active network attacker. Can attempt to MitM. Defeated by TLS, HSTS preload, and certificate validation. A successful TLS compromise still yields only ciphertext.

Compromised server / hostile operator. Includes us. Cannot read stored secrets because the key never arrives. Can deny service or modify the served JS bundle — addressed in residual risks below.

Unauthenticated attacker with a valid share link. By design, anyone with the full link can decrypt. View limits, expiry, and optional passphrase reduce the window. This is a deliberate property, not a bug.

Targeted attacker against a specific sender or recipient. Can exploit the endpoint, the delivery channel, or the recipient's post-decryption handling. Outside Vaulted's control; documented for honesty.

4. Controls

  • Client-side AES-256-GCM with random 96-bit IVs from crypto.getRandomValues.
  • 256-bit keys generated per secret via crypto.subtle.generateKey; placed in the URL fragment, never transmitted.
  • Optional passphrase wrapping with PBKDF2 (high iteration count) and AES-GCM key wrap.
  • Atomic view-count enforcement via Redis HINCRBY with same-transaction deletion at limit.
  • TTL-based auto-expiry capped at 30 days; no extension after creation.
  • Per-IP sliding-window rate limiting on create and view endpoints.
  • noindex on /s/[id] view pages so secret URLs cannot leak via search.
  • TLS 1.3 with HSTS preload, secure security headers, no IP logging beyond rate-limit windows.

5. Residual risks (not defended against)

These are deliberate boundary decisions, documented so users can decide whether Vaulted fits their threat model.

  • Endpoint compromise. A keylogger, infected browser, or hostile extension can read plaintext after decryption.
  • Delivery-channel leak. If the share link is forwarded, archived, or quoted in a replied email thread, that channel becomes the attack surface.
  • Targeted bundle swap. A compromised CDN or Vercel edge could serve different JS to a specific user. Subresource Integrity and reproducible builds are partial mitigations and on the roadmap.
  • Recipient behaviour. Once decrypted, the recipient can copy, screenshot, or forward.
  • Weak passphrase. If used, a weak passphrase is brute-forceable by anyone with the wrapped key.
  • Rubber-hose / coercion. Out of scope for any technical control.

6. OWASP ASVS L1 self-attestation

The matrix below maps Vaulted's implementation against selected OWASP ASVS Level 1 requirements. Categories that don't apply (sessions, authentication, file upload) are marked N/A with a brief rationale rather than omitted, so absence of an entry doesn't read as evasion.

IDCategoryRequirementStatusEvidence
V1.1.1ArchitectureSecure SDLC with threat modelling for the applicationPassThis document. Reviewed at each material architecture change.
V1.4.1ArchitectureTrusted enforcement points enforce access controlsPassAPI route handlers validate inputs and consume views atomically via Redis HINCRBY in src/lib/redis-secrets-store.ts.
V2.10.xAuthenticationService / user authenticationN/AVaulted is anonymous. No user accounts, no service-to-service authentication. Authorisation is by knowledge of the secret ID and URL fragment key.
V3.xSession ManagementSession management requirementsN/ANo sessions, no cookies for authenticated state.
V5.1.3Input ValidationInput validation at trusted service layerPassManual runtime validation in src/app/api/secrets/route.ts. Payload ≤ 1000 chars enforced server-side; max views and TTL bounded.
V5.2.5SanitizationOutput encoding for context (HTML, JS, URL)PassReact escapes all interpolated values by default. The only dangerouslySetInnerHTML calls render JSON-LD literals constructed server-side from typed data.
V6.2.1Stored CryptographyUse approved cryptographic algorithms with safe defaultsPassAES-256-GCM via Web Crypto API. NIST SP 800-38D. See src/lib/crypto.ts.
V6.2.2Stored CryptographyCryptographically strong random number generationPasscrypto.getRandomValues for IVs and key material. crypto.subtle.generateKey for AES keys.
V6.2.5Stored CryptographyNo insecure or deprecated cryptographic primitivesPassNo MD5, SHA-1, DES, RC4, ECB, or CBC-without-MAC anywhere in the cryptographic path.
V6.3.1Stored CryptographyKeys protected against unauthorised accessPassEncryption key never reaches the server. Lives only in the URL fragment, processed client-side per RFC 3986. Optional passphrase wraps the key with PBKDF2 before sharing.
V7.1.1Error Handling & LoggingNo sensitive information in logsPassServer logs request shape and rate-limit decisions only. No ciphertext, no secret IDs in a way that could be cross-correlated, no IPs persisted beyond the rate-limit window.
V7.4.1Error Handling & LoggingGeneric error messagesPassAPI routes return generic HTTP status codes and short error strings. No stack traces or internal state leaked.
V8.1.1Data ProtectionSensitive data identified and protectedPassPlaintext is never received by the server. Ciphertext is stored encrypted at rest in Upstash Redis with TTL-based deletion and atomic view-limit enforcement.
V8.3.1Data ProtectionSensitive data not exposed in URLs or referrersPassEncryption key lives in the URL fragment; fragments are never sent to the server and are stripped from document.referrer by browsers per RFC 3986 §3.5.
V9.1.1CommunicationsTLS for all client connectivityPassTLS 1.3 enforced at the Vercel edge. HSTS preloaded. HTTP requests redirect to HTTPS.
V10.3.2Malicious CodeApplication code reviewed for malicious codePassSingle-maintainer codebase. All commits authored by the project owner; signed commits where supported. Dependency updates via Dependabot reviewed before merge.
V11.1.1Business LogicLogic flows protected against abusePassPer-IP rate limiting via Upstash Ratelimit (10 creates/min, 30 views/min). View counts decremented atomically.
V12.xFile & ResourcesFile upload and processing requirementsN/AVaulted does not accept file uploads.
V13.2.1API & Web ServicesRESTful endpoints validate request schemaPassAll POST/GET handlers validate path params, body shape, and content-type before processing.
V14.4.3ConfigurationStandard security headers configuredPassHSTS, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy set via Next.js config. Content-Security-Policy enforced per-request in src/proxy.ts with a nonce-based strict-dynamic script-src; API routes get a locked-down default-src none policy.

Numbering follows the ASVS 4.0 structure. The list is selected, not exhaustive — the goal is honesty about the controls that meaningfully apply to a stateless, account-less encrypted ciphertext store.

7. Change process

Any change that affects an item in this document — new endpoint, cryptographic change, dependency-affecting refactor, infrastructure change — must trigger a review of this page in the same PR. The version and published date above will increment when the document is updated; the prior version remains accessible in the public git history.

Sign-off

This threat model is published in good faith by the maintainer of Vaulted. Errors, omissions, and disagreements with the attestations above are explicitly invited — please report them to [email protected] or via the responsible-disclosure programme.

Maxim Novak — maintainer, vaulted.fyi · 2026-04-27