ธีม
Pre-/clear security scan — 2026-07-29
Moved out of STATE.md on 2026-07-30 (all four fixes are shipped, deployed, and carry their own mistakes.md entries; this is the narrative record). Migrations 0100 + 0101, commit 397ff56.
A deliberate sweep, not a spot-check. Everything below was proven against the live DB in rolled-back transactions before being fixed, and re-proven after.
- A buyer could zero their own order's total (0100).
shop_orders_update_self_earlyis row-level with no column guard:total=0, subtotal=0, fee=0ACCEPTED, plusadmin_noteand atimelineentry forged asby:"admin". They could NOT escape the pending/review window (the USING doubles as the CHECK), which is the only thing that contained it. Third table with this exact defect afterusers(0028) andvs_tickets(0096). Proof:tools/shop0100-buyer-guard.mjs(12 checks — 5 attacks blocked, and the 3 real buyer call sites insrc/js/shop/api.jsreplayed to prove checkout still works). get_pr_ticket_by_idmatched withILIKE(0101) — so the ticket id was a PATTERN, not a capability.{"p_id":"%"}with the public anon key returned a real ticket incl. the submitter's email and brief; pattern-walking enumerates all of them. Nowlower(id) = lower(btrim(p_id)). The VS twin already used=and was verified unaffected with the same probe.- The ten team resolvers were anon-callable (0101) — an anonymous oracle:
effective_team_permissions_for_emailreturned any address's exact grant set. Revoked fromanon/authenticated/PUBLIC; nothing outside SQL called them and their real callers are SECURITY DEFINER.sync_my_team_permissions()KEPT itsauthenticatedgrant —auth.jscalls it every login and it only resolves the caller's own identity. - The
visladder's SQL and JS implementations disagreed on 3 of 26 inputs ('t','1',1for the legacyinternalflag). Failed SAFE (the server stripped an entry the client would also have hidden), but it is the drift "keep them in step" was supposed to prevent.tools/vs-remark-vis-mirror.mjsnow diffs them mechanically over every legal and malformed shape.
The two live conclusions from this scan — the knowingly-accepted pair of unguarded owner UPDATE policies, and the XSS re-audit that was NOT done — stay in STATE.md, because a future change still has to respect them.