Skip to content

feat(scratchnode): cross-domain private-notes bridge via opaque stateful token (#4)#496

Merged
HomenShum merged 2 commits into
mainfrom
feat/scratchnode-private-notes-token
Jun 3, 2026
Merged

feat(scratchnode): cross-domain private-notes bridge via opaque stateful token (#4)#496
HomenShum merged 2 commits into
mainfrom
feat/scratchnode-private-notes-token

Conversation

@HomenShum
Copy link
Copy Markdown
Owner

@

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.tsmintEventHandoffToken({ 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). consumeEventHandoffToken is fail-closed on every check (unknown/expired/used-up/scope) and returns only the read-only snapshot. New liveEventHandoffTokens table: event-scoped, 10-min TTL, few-use, boundSessionHash = SHA-256 (one-way).
  • /events/:slug/private route → ScratchnodePrivateBridge consumes ?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.htmlopenNodeBenchPrivateHandoff mints a token then navigates to the real /events/<slug>/private?token=… (only the opaque token travels). Honest fallback to the shipped /scratchnode-events surface 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-page SN-LIVE-015b. 13/13 new green; full tsc + npm run build clean.

⚠️ Deploy held

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 + the getPublishedWikiStructuredBySlug Server Error in prod). Merge only after that incident is resolved and prod is verified healthy.

🤖 Generated with Claude Code
@

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nodebench-ai Ready Ready Preview, Comment Jun 3, 2026 9:10pm

Request Review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Vercel preflight failed: TypeScript Convex

Detail: convex typecheck failed

convex/eventHandoff.ts(30,10): error TS2305: Module '"convex/values"' has no exported member 'ConvexError'.

Fix: Run npx tsc -p convex --noEmit --pretty false.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

✅ Dogfood Visual QA Gate: PASSED

Check Status
Screenshots 23 captured (pass)
Walkthrough 9 chapters (pass)
Key Frames 9 extracted (pass)
Scribe Steps 8 how-to steps (pass)
Build success
Artifacts

Download the dogfood-evidence-8fe8562 artifact from the Actions tab for full screenshots, frames, and walkthrough video.


Generated by Dogfood QA Gate

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Vercel preflight failed: TypeScript Convex

Detail: convex typecheck failed

convex/eventHandoff.ts(30,10): error TS2305: Module '"convex/values"' has no exported member 'ConvexError'.

Fix: Run npx tsc -p convex --noEmit --pretty false.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Vercel preflight failed: TypeScript Convex

Detail: convex typecheck failed

convex/eventHandoff.ts(30,10): error TS2305: Module '"convex/values"' has no exported member 'ConvexError'.

Fix: Run npx tsc -p convex --noEmit --pretty false.

HShuM and others added 2 commits June 3, 2026 14:08
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>
@
@HomenShum HomenShum force-pushed the feat/scratchnode-private-notes-token branch from 8e539dc to 0943ea5 Compare June 3, 2026 21:08
@HomenShum HomenShum merged commit ffeea5e into main Jun 3, 2026
17 checks passed
@HomenShum HomenShum deleted the feat/scratchnode-private-notes-token branch June 3, 2026 21:21
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Demo: walkthrough of the surfaces this PR changed is available as a workflow artifact (pr-demo-496) at https://github.com/HomenShum/nodebench-ai/actions/runs/26913773404

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants