Build log · June 2026

Auditing the product against its own source code — sixteen findings, closed

In May 2026 a periodic adversarial pass audited the running product against its own source code, in five parallel deep-dives, and surfaced sixteen must-fix items that were closed over the following weeks. Only a handful were genuine zero-knowledge boundary issues; the rest were truthful-copy, legal, and ops gaps.

In May 2026 a periodic adversarial security pass audited the running product against its own source code. The prior full pass was the security review summarized on /security; this one verified findings against actual source files with file-and-line citations, not against the project's own documentation, because docs can be aspirational and the code is what users run. It ran on 2026-05-18 as five parallel deep-dives across cryptography, web security, public copy, pricing, and legal and ops.

The pass surfaced sixteen must-fix items, and over the following weeks they were closed. Of the sixteen, only a handful were genuine zero-knowledge or security-boundary issues. The rest were truthful-copy corrections, legal and compliance paperwork, and UX or ops gaps.

The highest-severity finding: a server-side plaintext handoff

The top item was a server handoff that should never have existed. The auth flow POSTed the plaintext vault password to /api/vault-handoff/set, which wrote it into a __Host-shieldfive_vault_handoff cookie. The Next.js handler received the plaintext password in its request body, which contradicts the "server never sees plaintext or keys" claim the whole product rests on. It existed because an earlier convenience fix reached for a server round-trip instead of staying client-side, to keep the vault unlocked across a hard reload from /auth to /files.

The fix (commit c95012e3) deleted the entire server-side handoff: the app/api/vault-handoff/ routes and utils/vaultUnlockHandoff.ts. Surviving a same-tab hard reload now relies on the keyring's existing wrapped-root-key session record, with the unlock modal as the fallback. The old routes remain as 410 stubs that clear the legacy cookie for any stale bundle still calling them. The leak was self-inflicted by a feature, and auditing against the code rather than the docs is what caught it.

Auth and abuse hardening

Several findings were account-takeover and enumeration gaps. MFA-unenroll, delete-account, and change-email now require AAL2 plus a fresh credential, through a shared utils/mfa.ts helper. That helper fails closed: if the assurance-level lookup errors, it returns requiresStepUp: true rather than silently passing, surfacing a distinct 503 so the gate stays shut. Sign-in, forgot-password, and reset now collapse distinct backend errors to one user-visible string, with the real cause logged server-side, removing the message tells that enumerate accounts. A signup hardening pass added behavior-score gating with adaptive Turnstile, per-IP rate-limiting, and a reaper for never-verified accounts. On billing, Stripe webhook processing got an atomic duplicate-delivery claim and append-only writes gated through SECURITY DEFINER RPCs.

Making the claims match the code

Most of the sixteen were not crypto. The audit found fabricated trust signals shipped in the locale bundle: invented testimonials, an unsourced "30+ countries" stat, and a "500 spots remaining" scarcity block. These were removed, and the trust-stat copy was replaced with four sourced facts (the internal-review finding count, its severity breakdown, the build-log post count, and the Apache-2.0 crypto license). A stale comparison claim about a competitor's post-quantum support was rewritten to distinguish the products accurately. A non-existent "ShieldFive Labs" legal entity was replaced everywhere with the real operator. The FAQ still described a removed per-file-password product and was rewritten to the current single-vault-key model. The privacy policy was spun out as a standalone route that discloses third-party recipients by category under GDPR Art. 13(1)(e), with the named sub-processor list available on request rather than published; refund copy was reconciled with the Terms, and an AUP plus an RFC 9116 security.txt landed. These are credibility and compliance gaps, not boundary breaks.

Crypto-library work since PQ-default

In parallel, @shieldfive/crypto moved from alpha.7 to alpha.11. It added Suite 0x04 (aes-gcm-v2), which widens the per-file nonce prefix from 4 to 8 bytes and shrinks the cross-file IV-collision probability, plus optional detached Ed25519 sender-attribution signatures. The @noble/post-quantum dependency was bumped to ^0.6.0. The default suite is unchanged: new files still use Suite 0x03, the ML-KEM-1024 plus XChaCha20-Poly1305 hybrid. Suite 0x04 is opt-in via a dedicated subpath import only.

A separate follow-up, not one of the sixteen, fixed an int4-overflow in the daily egress cap that had been 503-ing authenticated downloads. It landed out-of-band on 2026-06-04, after the audit.

What this still doesn't fix

An audit is a point-in-time pass. It verifies the code as it stands on the day it runs, and a later change can always reintroduce what it cleared. The only real guard against that is running it again.

Links