Skip to content

Derive the sign-in challenge in Pocket before prompting - #1243

Merged
nedtwigg merged 5 commits into
mainfrom
pocket-signin-challenge
Oct 20, 2026
Merged

nedtwigg merged 5 commits into
mainfrom
pocket-signin-challenge

Conversation

@nedtwigg

Copy link
Copy Markdown
Member

Hardening for Pocket's sign-in.

Pocket used to hand the challenge POST /api/signin/begin answers straight to its authenticator. A sign-in assertion and a presence assertion come from the same passkey under the same RP ID, and a verifier reads only the challenge clientDataJSON names, so nothing kept a sign-in tap from being signed over a value shaped like a presence challenge. Sign-in now signs a value derived under its own domain:

  • signinChallenge(challenge) in remote-lib-common/src/security/presence.ts, beside presenceChallenge: base64url SHA-256(lengthPrefixedConcat("dormouse/signin/v1", challenge's bytes)). It refuses an unbounded or non-base64url challenge.
  • PocketClient.signin signs signinChallenge(begin.challenge) and sends { challenge, assertion } to finish (SigninFinishRequest gains challenge).
  • verifySigninAssertion, which both Relays run, takes the whole request. It consumes the named begin challenge first, as before, and then accepts only an assertion over its derivation. A missing, malformed, or unissued challenge, or an assertion over anything else (including begin's raw bytes), gets 400 unrecognized or expired challenge. relay/src/app.ts and hosted/server/relay-api.ts pass the body through.

Since presence challenges are hashed under dormouse/presence/v1, the two kinds of assertion are now disjoint. A sign-in could stand in for a presence proof only through a SHA-256 collision.

Rollout

Each Relay serves its own Pocket same-origin, and index.html is no-cache, so a fresh load always pairs a Relay with a matching Pocket. A Pocket tab left open across the deploy fails its next sign-in with that 400 until it reloads. Existing sessions are unaffected, since only sign-in changes. There is deliberately no fallback that accepts the old raw-challenge assertion.

Tests

  • lib/src/remote/client/pocket-client.test.ts → "the presence proof":
    • a Relay answers sign-in's begin with a presence challenge for a connection on the phone's own paired static;
    • the authenticator is asked only for signinChallenge of it, and finish names the Relay's challenge;
    • presented to the Burrow as that connection's presence proof, the sign-in assertion admits nothing and opens no window.
  • remote-lib-common/test/presence.test.mjs: an independently computed vector for signinChallenge, its distinct domain, and its refusals.
  • remote-lib-common/test/relay-common.test.mjs: the verifier's order (nothing spent without a named challenge), refusal of raw-bytes and other-derivation assertions, and acceptance of a padded serialization.
  • relay/test/signin.test.mjs and hosted/server/tests/relay.test.ts: each Relay refuses an assertion over begin's bytes as given. The existing sign-in helpers now sign the derivation.

The Pocket test goes red with signin reverted to sign begin.challenge; the Relay tests go red with the derivation check removed.

Specs

  • docs/specs/security-remote.md → "Trust boundary": S271 now covers every assertion Pocket requests: signin must sign signinChallenge of the Relay's challenge, and any other getAssertion call must sign a challenge derived under its own domain. Rationale extended; word budget ratcheted to 7100.
  • docs/specs/remote-security-model.md → "Presence proofs": new bullet, "A sign-in assertion is never a presence assertion".
  • docs/specs/relay.md: sign-in verification is over the derived challenge.
  • New canary seed .github/audit/canaries/pocket-signin-challenge-underived.patch (Rule: S271). The existing pocket-presence-challenge-unchecked.patch is rebuilt against the new test file, with the same mutation.

This PR touches .github/, so the owner merges it.

🤖 Generated with Claude Code

Pocket's sign-in now signs `signinChallenge(begin.challenge)`, a SHA-256
under its own `dormouse/signin/v1` domain, instead of the bytes the
Relay's `POST /api/signin/begin` answers, and `finish` names the begin
challenge beside the assertion. Both Relays verify through the shared
`verifySigninAssertion`, which now consumes the named challenge and
accepts only an assertion over its derivation. A sign-in assertion can
no longer equal a presence assertion, whose challenge is hashed under
`dormouse/presence/v1`.

S271 widens to cover every assertion Pocket requests, with a canary seed
for the sign-in half; the presence-half seed is rebased onto the new
test file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: a418795
Status:⚡️  Build in progress...

View logs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found no issues in the diff. Approval is held only because Workflow Lint is red. zizmor flags ci.yml:403 ("action's hash pin has mismatched or missing version comment"). This PR doesn't touch that file, and main fails the same way at ed40d01 (run). Every other check passes on this head.

nedtwigg and others added 2 commits October 20, 2026 08:53
The two Pocket tests that hand the Burrow a proof for a connection on the
phone's own paired static now build it, and check that it gains nothing,
through one helper. The sign-in verifier reads its request with a plain
cast. Both canary seeds are rebuilt against the new test file, with the
same mutations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nedtwigg
nedtwigg merged commit 90d84e3 into main Oct 20, 2026
9 of 10 checks passed
@nedtwigg
nedtwigg deleted the pocket-signin-challenge branch October 20, 2026 17:22
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