[object Object]

← back to Costa Rica

cycle 28 docs: YOLO_NOTES ledger — admin CSRF fix + gate-coupling decision memo

0b0f3fc2e0cc87316b995b3def09e91616be51c3 · 2026-09-24 06:27:01 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic

Files touched

Diff

commit 0b0f3fc2e0cc87316b995b3def09e91616be51c3
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Sep 24 06:27:01 2026 -0700

    cycle 28 docs: YOLO_NOTES ledger — admin CSRF fix + gate-coupling decision memo
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic
---
 YOLO_NOTES.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/YOLO_NOTES.md b/YOLO_NOTES.md
index f9f18f2..ed5c1cb 100644
--- a/YOLO_NOTES.md
+++ b/YOLO_NOTES.md
@@ -597,3 +597,21 @@ Canonical apply order now: `004_marketplace → 005_apple → 006_contacts → 0
 **Cost:** $0 (local + two Cody passes — the audit AND a diff-gate that found a genuine concurrency bug in the fix, verified by reproduction).
 
 **Backlog:** SIX gated memos on Steve's desk (host-claim, reconciler cron, payout-visibility, place-xss-DEPLOY [security-urgent], meic-dedup, createcharge-idempotency) + the 2 SIWA GO-LIVE decisions. Next cycle: fresh cold audit on another un-audited surface (candidates: routes/admin.js authZ, lib/whatsapp.js inbound handling beyond the sig check, routes/webhooks.js state-machine, or lib/money.js invariants).
+
+---
+
+## Cycle 28 — cold Cody audit of admin authZ: CSRF fixed, gate-coupling → HIGH memo (TK-10346)
+
+**Surface:** routes/admin.js (host-claim approval + bookings/PII/GMV) + the server.js basic-auth gate — an un-audited authZ surface.
+
+**Mostly sound, two real issues:** Cody confirmed injection is clean (all parameterized), PII selects are scoped (no secrets/hashes), and the basic-auth mount ordering correctly covers `/api/admin` today. Two real findings:
+
+1. **CSRF on `POST /api/admin/claims/:placeId/:hostId` (REAL, exploitable today) — FIXED.** Basic-auth creds are auto-attached cross-site by the browser, and `express.urlencoded` is mounted globally, so an attacker page's auto-submitting `<form>` (application/x-www-form-urlencoded — a simple request, no CORS preflight) could approve a host claim against a logged-in admin. CORS is scoped to `/api/app` and doesn't apply to a simple form POST anyway. Fix: the mutation now requires `application/json` — a cross-site simple form can't set it without a preflight that admin (no CORS) rejects; the real UI (`public/admin.html:69`) already sends JSON, so transparent. Verified `req.is()` semantics + that application/json is non-simple. Test: form-urlencoded & text/plain → 415 no UPDATE, JSON → 200, bad-decision → 400. 226→**230**. Commit `75b27d7`.
+
+2. **Admin coupled to the public-directory gate (HIGH) → DECISION memo, NOT auto-fixed.** `/api/admin` sits behind the SAME single basic-auth switch as the public directory, and `docs/GO-LIVE.md §8` explicitly plans to REMOVE `BASIC_AUTH_*` to open the directory — which would silently strip auth off claim-approval + bookings-PII (server.js fails OPEN when unset). Not live today (basic-auth is set) but a documented future launch action = a loaded gun. Drafted `2026-09-24-TK-10346-costa-admin-auth-coupling-DECISION.md` (A: separate cookie-session admin auth [recommended, also kills CSRF structurally]; B: interim fail-closed one-liner I can ship on approval; C: GO-LIVE checklist only). Did NOT unilaterally change admin auth — it's entangled with Steve's deliberate GO-LIVE toggle and could affect admin access.
+
+**Not a new memo — re-confirmed the EXISTING one:** Cody independently found `routes/app.js:525` gates `POST /host/listings` on a claim ROW existing (`claimed`), NOT `claim_status='approved'` — so a host can self-list a bookable place without admin approval. That's the already-open `host-claim-approval-unenforced-DECISION` (cycle 23); Cody's finding just confirms it's the REAL trust boundary (the admin door is locked; the window is in app.js). Noted, not re-drafted.
+
+**Cost:** $0 (local + one Cody audit pass). No re-gate on the 1-line CSRF content-type guard — the surface was just Cody-audited this cycle, fix is minimal + tested + bypass-reasoned; proportionate.
+
+**Backlog:** SEVEN gated items on Steve's desk now (host-claim-approval, reconciler-cron, payout-visibility, place-xss-DEPLOY [security-urgent], meic-dedup, createcharge-idempotency, admin-auth-coupling [HIGH]) + 2 SIWA GO-LIVE decisions. Next cycle: fresh cold audit on the last big un-audited surfaces — routes/webhooks.js payment-webhook state machine (replay/out-of-order), lib/whatsapp.js inbound handling, or lib/money.js money-math invariants.

← 75b27d7 costa-rica: block CSRF on the admin claim-approval mutation  ·  back to Costa Rica  ·  auto-data-snapshot: 2026-09-24T07:06:26 (1 data files) — doc fc341d1 →