← back to Costa Rica
cycle 27 docs: YOLO_NOTES + GO-LIVE — SIWA hardening + 2 deferred pre-launch decisions
edda387cacb9e81f5a5ad7ce54011a85feeba0b7 · 2026-09-24 05:52:47 -0700 · Steve
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic
Files touched
M YOLO_NOTES.mdM docs/GO-LIVE.md
Diff
commit edda387cacb9e81f5a5ad7ce54011a85feeba0b7
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 24 05:52:47 2026 -0700
cycle 27 docs: YOLO_NOTES + GO-LIVE — SIWA hardening + 2 deferred pre-launch decisions
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic
---
YOLO_NOTES.md | 23 +++++++++++++++++++++++
docs/GO-LIVE.md | 4 ++++
2 files changed, 27 insertions(+)
diff --git a/YOLO_NOTES.md b/YOLO_NOTES.md
index ccea837..f9f18f2 100644
--- a/YOLO_NOTES.md
+++ b/YOLO_NOTES.md
@@ -574,3 +574,26 @@ Canonical apply order now: `004_marketplace → 005_apple → 006_contacts → 0
**Cost:** $0 (local; no Cody spawn this cycle — item 1 was a mechanical pattern-copy self-verified, item 2 is a decision-memo not a fix).
**Backlog:** FIVE gated items now on Steve's desk — host-claim approval, reconciler cron, payout-visibility, place-xss-DEPLOY (security-urgent), and this MEIC dedup decision. Concrete-deferred code backlog is now EMPTY (all 3 of Cody's cycle-24 ingest findings resolved: XSS fixed cycle 24, timeout+isolation cycle 25, cr-osm-match cycle 26; dedup → decision). Next cycle: a FRESH cold Cody audit on a not-yet-audited surface (candidates: lib/apple.js Sign-in-with-Apple token/JWT verification; lib/whatsapp.js + routes/webhooks.js signature verification; routes/admin.js authz; or the money-math in lib/money.js).
+
+---
+
+## Cycle 27 — cold Cody audit of Sign-in-with-Apple: crypto sound, 3 hardening gaps fixed (TK-10346)
+
+**Surface:** lib/apple.js (SIWA identity-token verification) + routes/app.js /auth/apple — an un-audited, account-takeover-class surface. (Webhook signatures were checked first and are already verified with timingSafeEqual — a weaker target, skipped.)
+
+**Crypto core = SOUND (Cody proved empirically, not by inspection):** 5 forge attempts — empty sig / alg:none, HS256-with-pubkey-as-secret (alg confusion), garbage bytes, full-length garbage, tampered payload — ALL return false. `header.alg` is never read; `crypto.verify` is hardcoded to RSA-SHA256; `kid` only selects among Apple-fetched keys. No takeover path. iss/aud/signature all enforced. `app_users.email`+`apple_sub` both UNIQUE (verified) so the account-link UPDATE can't hit multiple rows. The route already gates email-linking on `email_verified`.
+
+**3 real gaps fixed (hardening/availability, reversible, on a security-critical file):**
+1. **JWKS kid-miss never refetched** → a valid token on Apple's newly-ROTATED key 401'd for up to the 1h cache TTL (silent outage on Apple's undocumented schedule). Now a kid-miss forces ONE cooldown-guarded refetch.
+2. **exp checked only-if-present** → a signature-valid token with no exp never expired. Now REQUIRED (`=== undefined` → reject; exp:0 still hits the expiry check).
+3. **/auth/apple leaked e.message** (fetchT timeout text, JSON.parse errors) to the client, violating the file's own M2/R4 policy 6 lines below. Now server-side log + generic 401.
+
+**Cody's diff-gate caught a REAL concurrency bug in my own fix:** `_lastFetch = now` was written AFTER the awaits, so a burst of concurrent kid-miss requests all read the stale clock and each fired a fetch (Cody reproduced: 20 concurrent garbage kids → 20 fetches), defeating the anti-hammer guard AND risking an Apple-side rate-limit self-DoS. Fixed with an **in-flight-promise dedup** (start the cooldown clock synchronously before the await; concurrent callers join the single fetch) — which also kills the pre-existing hourly cache-expiry thundering-herd for free. The test also found a bug in my cooldown env-parse (`Number('0')||60000` swallowed a legit 0). Two self-inflicted bugs, both caught before commit.
+
+**Test (`test/apple-verify.test.js`):** generates a REAL RSA keypair, serves it as Apple's JWKS via mocked fetch, and proves the full matrix — valid / tampered / empty-sig(reaches crypto.verify, not the length bail) / wrong-key / bad-aud / bad-iss / expired / **no-exp** / **rotation-refetch** / **20-way concurrent burst → ≤1 fetch**. Suite 216 → **226**, serial green. Commit `3114048`.
+
+**Two pre-launch DECISIONS deferred to GO-LIVE §7 (not mechanical, no live exploit):** (1) account-splitting when an Apple email differs from a pre-existing account's email (needs a merge flow vs accept+document); (2) nonce/replay hardening (needs a client-side ceremony + server nonce-ledger vs accept+document given TLS+short-exp) — plus a note that there's NO rate limiting anywhere in the app (worth adding on /auth/*).
+
+**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).
diff --git a/docs/GO-LIVE.md b/docs/GO-LIVE.md
index 22b1acc..90b745c 100644
--- a/docs/GO-LIVE.md
+++ b/docs/GO-LIVE.md
@@ -77,5 +77,9 @@ These closed in the /yoloforever cycles; they need no creds and are already live
## 7. App Store
ASC app 6799240433; build #13 on TestFlight. Remaining: attach build + `store/listing.md` desc + 5 screenshots + export-compliance in the ASC console, then Submit for Review (your Apple login).
+**Sign-in-with-Apple — two pre-launch DECISIONS (cycle-27 Cody audit; crypto core verified sound, these are hardening/UX, not takeover):**
+1. **Account-splitting on email mismatch.** `/auth/apple` links by `apple_sub`, else by *verified* email, else INSERTs. A user who registered by password with email X, then signs in with Apple whose currently-verified email is Y (alias / Hide-My-Email toggled / primary changed), falls through to a NEW orphaned `app_users` row — their old bookings become invisible from the new account, silently. Decide before launch: add a "we found another account — merge?" flow, or accept-and-document the risk.
+2. **Nonce / replay.** No nonce is generated client-side or checked server-side (`grep nonce` = 0 hits). A captured `identity_token` can be replayed to `/auth/apple` within Apple's short exp window to mint a victim session (requires the attacker to already have the raw bearer credential — medium, not takeover). Decide: add a client nonce ceremony + server nonce-claim check (ideally a one-time-use ledger), or accept-and-document given TLS + short exp. Also note: no rate limiting anywhere in the app (`grep rate.?limit` = 0) — worth adding on `/auth/*` regardless.
+
## 8. Optional — public web directory
The web directory pages are basic-auth gated (`admin`/`DW2024!`); the app API (`/api/app`) + `/webhooks` are already public. Dropping the web gate exposes ~34k listings publicly — a customer-facing publish of scraped third-party business data; decide deliberately before removing `BASIC_AUTH_*`.
← 3114048 costa-rica: harden Sign-in-with-Apple verify — JWKS rotation
·
back to Costa Rica
·
costa-rica: block CSRF on the admin claim-approval mutation 75b27d7 →