feat(scratchnode): cross-domain private-notes bridge via opaque stateful token (#4)#496
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
2e00a8e to
bd053b4
Compare
Vercel preflight failed:
|
✅ Dogfood Visual QA Gate: PASSED
ArtifactsDownload the Generated by Dogfood QA Gate |
Vercel preflight failed:
|
Vercel preflight failed:
|
feat(scratchnode): cross-domain private-notes bridge via opaque stateful token (#4) The last broken leg of the ScratchNode->NodeBench transition. A guest private notes are owner-keyed by sn_session_id, which is origin-partitioned (nodebenchai.com cannot read it) and doubles as the credential (notes layer has no auth check). Shipping it across origins would leak a permanent credential into URL/referer/logs. Founder-chosen design: opaque stateful token. - convex/eventHandoff.ts: mintEventHandoffToken membership-gates the caller (liveEventMembers by_event_session), snapshots THAT member notes for THIS event into a token row, returns ONLY a CSPRNG opaque token. The raw session id is NEVER stored (table dump yields no credential, no cross-event access). consume is fail-closed on every check, returns only the read-only snapshot. New liveEventHandoffTokens table: event-scoped, 10-min TTL, few-use, SHA-256 bound hash. - /events/:slug/private route (App.tsx, above the single-segment matcher) -> ScratchnodePrivateBridge consumes ?token=, renders notes read-only (DOMPurify), honest invalid/expired/empty states, never displays or logs the token. - home-v5.html: openNodeBenchPrivateHandoff mints a token then navigates to the real /events/<slug>/private?token= (only the opaque token travels). Honest fallback to /scratchnode-events if minting fails. Completes the earlier interim retarget. Tests: 8 ADVERSARIAL convex-test (non-member denied, token never exposes session id, expired/used-up/forged fail closed, cross-event isolation, idempotent reuse) + 5 component (valid/expired/missing/empty/sanitization/token-never-rendered) + in-page SN-LIVE-015b. 13/13 new green; full tsc + build clean. Held: does NOT auto-merge -- prod Convex deploy incident (Codex out-of-band clobber, see AGENT_COORDINATION.md) must be resolved first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
fix(scratchnode): local ConvexError class in eventHandoff.ts (convex/values lacks it) CI tsc caught TS2305: this Convex version does not export ConvexError from convex/values (local tsc resolution masked it). Mirror the local class events.ts defines so thrown mint errors still carry a typed data.code. 8/8 adversarial tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
8e539dc to
0943ea5
Compare
|
Demo: walkthrough of the surfaces this PR changed is available as a workflow artifact ( |
This was referenced Jun 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@
Why
The last broken leg of the ScratchNode → NodeBench transition. A guests private notes are owner-keyed by
sn_session_id, which is origin-partitioned (nodebenchai.com cant read it) and doubles as the credential (the notes layer has no auth check). Shipping it across origins would leak a permanent credential into the URL/referer/logs — the roadmaps #1 risk. Founder-chosen design: opaque stateful token.What
convex/eventHandoff.ts—mintEventHandoffToken({ slug, sessionId })membership-gates the caller, snapshots THAT members notes for THIS event into a token row, returns only a CSPRNG opaque token. The raw session id is never stored (a table dump yields no credential and no cross-event access).consumeEventHandoffTokenis fail-closed on every check (unknown/expired/used-up/scope) and returns only the read-only snapshot. NewliveEventHandoffTokenstable: event-scoped, 10-min TTL, few-use,boundSessionHash= SHA-256 (one-way)./events/:slug/privateroute →ScratchnodePrivateBridgeconsumes?token=, renders notes read-only (DOMPurify-sanitized), honest invalid/expired/empty states + "sign in to keep these" CTA, and never displays or logs the token.home-v5.html—openNodeBenchPrivateHandoffmints a token then navigates to the real/events/<slug>/private?token=…(only the opaque token travels). Honest fallback to the shipped/scratchnode-eventssurface if minting fails — never a 404, never a forged link.Security (the roadmaps biggest risk)
Never put the session id in a URL/log ✓ · membership-checked mint ✓ · fail-closed verify ✓ · event-scoped, read-only, short-TTL, few-use ✓ · raw session id never stored (snapshot, not the id) ✓.
Tests
8 ADVERSARIAL convex-test scenarios (
scratchnode.handoffToken.test.ts: non-member denied, token never exposes the session id, expired/used-up/forged all fail closed, cross-event isolation, idempotent reuse) + 5 component (ScratchnodePrivateBridge.test.tsx) + in-pageSN-LIVE-015b. 13/13 new green; fulltsc+npm run buildclean.This does not auto-merge. It adds functions to the shared Convex deployment that Codexs out-of-band deploy is currently clobbering (see the OPEN INCIDENT note in
AGENT_COORDINATION.md+ thegetPublishedWikiStructuredBySlugServer Error in prod). Merge only after that incident is resolved and prod is verified healthy.🤖 Generated with Claude Code
@