Parent: #9. Depends on #46 (B-2).
Scope
Two files, two worlds:
- `packages/extension/src/content/passkey-shim.ts` — MAIN world (manifest: `world: "MAIN"`, `run_at: "document_start"`). Overrides `navigator.credentials.create` / `.get`. Saves originals before override for fallback.
- `packages/extension/src/content/content-passkey.ts` — ISOLATED world. Relays between page and background. Reads `window.location.origin` itself (never from page-payload).
Security-critical: origin verification
- Isolated world sends its own `origin` to background
- Background cross-checks against `sender.url` from `chrome.runtime.onMessage`
- Background verifies `rp.id` is a registrable-domain suffix of origin's effective domain (PSL rules — use `psl` npm package; small, audited, no transitive deps)
- Mismatch → reject with `PASSKEY_RP_MISMATCH`, never forward to core
Fallback policy
Per design §3.2:
- Vault locked → show "Unlock NeoKeeWeb to use passkey" overlay, wait for unlock, retry
- No matching entry → ask user: "Use NeoKeeWeb to register new / Use native authenticator / Cancel"
- User declines NeoKeeWeb path → call saved original method, return its Promise result
- Spec-compliant timeout handling (respect `options.publicKey.timeout`)
Manifest change
`manifest.json`: register `passkey-shim.ts` as `"world": "MAIN"` content script for `<all_urls>`, `run_at: document_start`. Requires Chromium 111+ / Firefox 128+. Safari — shim is a no-op (by platform).
Acceptance
Parent: #9. Depends on #46 (B-2).
Scope
Two files, two worlds:
Security-critical: origin verification
Fallback policy
Per design §3.2:
Manifest change
`manifest.json`: register `passkey-shim.ts` as `"world": "MAIN"` content script for `<all_urls>`, `run_at: document_start`. Requires Chromium 111+ / Firefox 128+. Safari — shim is a no-op (by platform).
Acceptance