Skip to content

feat(nap-client-web): extension 0002, cross-origin signer (release 0.13.0) - #55

Merged
tcheeric merged 2 commits into
masterfrom
feat/cross-origin-signer
Oct 28, 2026
Merged

tcheeric merged 2 commits into
masterfrom
feat/cross-origin-signer

Conversation

@tcheeric

Copy link
Copy Markdown
Owner

Closes #54.

What

NAP extension 0002, cross-origin signer, in @imani/nap-client-web:

  • connectCrossOrigin() (relying origin): opens the key origin's signer page as a popup and returns a grant whose signer is an ordinary SessionSigner for createNapSession.
  • mountCrossOriginSigner() (key origin, /nap/signer): policy-gated signing grants.
  • createStorageConsentStore(): remembered approvals. Holds no secret.

The spec is docs/extensions/0002-cross-origin-signer.md. It is linked from RFC §22.1 and integration guide §6.2.

Security properties (each one tested)

  • Answers only window.opener, and only for policy origins, using the browser-reported event.origin. Strangers get silence.
  • Signs only grant kinds, with exactly one u tag whose origin is in the grant's audiences (closes §7.1 origin substitution for this path), and created_at within 300 s (no pre-signing).
  • Any completion proof after the first needs onSign (step-up is a real prompt, §10.3). Without onSign it is refused.
  • expectedPubkey mismatch is refused even after consent.
  • Grants end on TTL (≤ 900 s, enforced by the signer page's clock), lock/logout/identity change (also via BroadcastChannel), popup close, signer page reload, or disconnect().
  • A framed signer page answers nothing. Posts never use *. The relying side verifies the signature and template of every returned event.

Client-only

The server sees a byte-identical §11 proof. No wire change, so nap-java and supported_extensions are untouched.

Tests

  • crossOrigin.test.ts: 47 tests over a fake two-origin window model that enforces target origins, browser-set origin and source, and async structured clone. It includes the shared signer conformance suite and an end-to-end login against the real nap-server issueChallenge / verifyCompletion.
  • crossOrigin.browser.test.ts (opt-in, NAP_BROWSER=1, playwright-core devDep): real Chromium, two real origins (127.0.0.1 vs localhost). Checks connect and sign, the out-of-policy refusal, silence for an origin outside the policy, iframe silence, and COOP behaviour: same-origin and same-origin-allow-popups on the signer page break it, and same-origin-allow-popups on the relying page works. All of this is recorded in the spec.
  • npm run typecheck, npm test (830 passed) and release:pack pass locally. The new files are in the dist tarball.

Not in this version (spec §10)

Iframe mode, signed/fetched policy, NIP-98 path/method scoping, and a relay transport.

A sister origin gets a NAP session from a key held on another origin,
without the key leaving it. connectCrossOrigin() opens the key origin's
signer page as a popup; mountCrossOriginSigner() answers only its opener,
only for policy origins (browser-reported, not page-asserted), and signs
only kinds and u-tag audiences inside a short-lived grant. Later
completion proofs (step-up) must pass onSign. Grants end on lock, logout,
identity change, TTL, or popup close.

Client-only: servers and nap-java are unchanged. Spec in
docs/extensions/0002-cross-origin-signer.md; Chromium e2e behind
NAP_BROWSER=1 checks origin, framing and COOP behaviour.
frame.src = `${key}/nap/signer`;
document.body.append(frame);
const heard: unknown[] = [];
addEventListener('message', (event) => heard.push(event.data));
@tcheeric

Copy link
Copy Markdown
Owner Author

Dependency audit fails for reasons that already exist on master and have nothing to do with this PR. Running the same audit (npm audit --omit=dev --audit-level=moderate) against origin/master's lockfile fails the same way: undici (high) through testcontainers, from advisories published after master's last green run on 2026-10-15. This PR only adds playwright-core (dev, no advisories). The fix is the open testcontainers bump (#53) or an undici override, so I've kept it out of this PR. Validate passes on Node 20.19 and Node 22.x, and CodeQL passes.

Bump every package and the sibling ^ranges to 0.13.0, refresh the
lockfile, date the CHANGELOG entry, and add the 0.12.0 -> 0.13.0 notes
to UPGRADING.md (bump the range; COOP headers only if adopting the
cross-origin signer). Install examples in README, RELEASING, the guide
and tutorial 01 now name ^0.13.0.
@tcheeric tcheeric changed the title feat(nap-client-web): extension 0002, cross-origin signer feat(nap-client-web): extension 0002, cross-origin signer (release 0.13.0) Oct 28, 2026
@tcheeric
tcheeric merged commit f9e1ab5 into master Oct 28, 2026
4 of 5 checks passed
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.

[EXT-0002] Cross-origin signer: a sister origin gets a NAP session without holding the key

2 participants