Connect start: pin the app-secret gate on every managed provider, refuse blank targets #9

Merged
heimdall merged 6 commits from heimdall/connect-start-app-auth into main 2026-11-09 12:18:27 +00:00
Member

Closes the Heimdall side of joystick:follow_up:connect-start-needs-app-auth.

Finding on arrival: the gate the follow-up asks for was already in main (b541e09: a connect start carrying a connection needs the app's own x-heimdall-app-secret, and the slug must match). It was pinned for Joystick only, and the architecture doc still called connect starts unauthenticated. Reuse of the existing resolveAppCaller/isAppCaller gate; nothing parallel added.

Changed

  • Pinned the gate for twitch, youtube and joystick: no secret, a foreign app's, a wrong one, and a direct foreign or null caller are all 401 and mint no state; StreamPixels' own secret is 201. Any connection kind (creator and account) is gated, not only creator.
  • Closed joystick:finding:cut-h1.s4.whitespace-targetid-passes-custody-gate: a blank target (" ", tab, U+200B, U+FEFF) passed the start gate and the callback mint. One predicate, isUsableConnectionTarget, now serves both; start answers 400 invalid_connection_target.
  • notes/streampixels-connect-redeem.md: what StreamPixels must change (remember the target it started, redeem with that stored target, refuse a creator connect it did not start). StreamPixels is untouched.
  • docs/architecture.md open-follow-up entry brought up to date.

Hand mutants (applied, suite run, reverted), all caught: connect-with-connection gate dropped, slug match dropped, caller presence dropped, blank check dropped, blank check creator-only (survived the first test set; test widened to both kinds, then caught), predicate ignoring format chars, predicate ignoring whitespace, callback mint rejecting only the empty string. tests/app.test.ts 104 passed after restore.

Not covered: Heimdall cannot see StreamPixels' session, so "the creator-manage session was proved" is StreamPixels' step before it starts the connect; the note says so. A connect with no connection on twitch/youtube is the viewer-session path and stays open. Not merged, not deployed.

🤖 Generated with Claude Code

Closes the Heimdall side of `joystick:follow_up:connect-start-needs-app-auth`. **Finding on arrival:** the gate the follow-up asks for was already in main (b541e09: a connect start carrying a `connection` needs the app's own `x-heimdall-app-secret`, and the slug must match). It was pinned for Joystick only, and the architecture doc still called connect starts unauthenticated. Reuse of the existing `resolveAppCaller`/`isAppCaller` gate; nothing parallel added. **Changed** - Pinned the gate for twitch, youtube and joystick: no secret, a foreign app's, a wrong one, and a direct foreign or null caller are all 401 and mint no state; StreamPixels' own secret is 201. Any connection kind (creator and account) is gated, not only creator. - Closed `joystick:finding:cut-h1.s4.whitespace-targetid-passes-custody-gate`: a blank target (`" "`, tab, U+200B, U+FEFF) passed the start gate and the callback mint. One predicate, `isUsableConnectionTarget`, now serves both; start answers `400 invalid_connection_target`. - `notes/streampixels-connect-redeem.md`: what StreamPixels must change (remember the target it started, redeem with that stored target, refuse a creator connect it did not start). StreamPixels is untouched. - `docs/architecture.md` open-follow-up entry brought up to date. **Hand mutants** (applied, suite run, reverted), all caught: connect-with-connection gate dropped, slug match dropped, caller presence dropped, blank check dropped, blank check creator-only (survived the first test set; test widened to both kinds, then caught), predicate ignoring format chars, predicate ignoring whitespace, callback mint rejecting only the empty string. `tests/app.test.ts` 104 passed after restore. **Not covered:** Heimdall cannot see StreamPixels' session, so "the creator-manage session was proved" is StreamPixels' step before it starts the connect; the note says so. A `connect` with no `connection` on twitch/youtube is the viewer-session path and stays open. Not merged, not deployed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A connect start naming a connection target already needed the app's own secret, but the gate was
pinned for Joystick only and a whitespace or zero-width targetId passed both the start gate and the
callback mint. One predicate now serves both; the gate is pinned for twitch, youtube and joystick,
and the note for StreamPixels says what its redeem must do.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fails on this commit (7 of 107 selected): the blank-target start accepts U+2800
with a 201 for twitch, youtube and joystick; a blank target without the app's
secret answers 400, not 401; the callback mint returns custody_only for U+2800.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
isUsableConnectionTarget now rejects targets made only of whitespace, controls,
default-ignorable code points and U+2800. The blank-target and
creator_connection_required refusals move after the app-secret gate, so a
caller without the secret gets 401.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fail on bda14e3: the blocklist accepts U+0300, U+E000, U+0378, a lone surrogate, U+16FE4, U+1D159 and U+F0000 (201 where 400 is wanted; custody_only where refused).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Heimdall!9
No description provided.