fix: remove duplicate fetch/XHR interception from content script#14
fix: remove duplicate fetch/XHR interception from content script#14jjohare wants to merge 1 commit into
Conversation
Both src/injected.js (content script context) and src/nip98-interceptor.js (page context) were independently overriding window.fetch and XMLHttpRequest.prototype.send. This caused double interception of every request, double auth header injection, and double 401 retry loops. The content script's interception was also coupled to a window.__podkey_setAuthFn callback that was never properly wired — getAuthHeaderFn started as null and relied on an async setup race. This commit removes all fetch/XHR interception code from injected.js and leaves NIP-98 request interception solely to nip98-interceptor.js, which already has its own clean implementation using CustomEvent-based message passing to the content script. The content script now only handles what it should: relaying podkey-request/podkey-response events for NIP-07 and podkey-nip98-request/response events for NIP-98. Co-Authored-By: claude-flow <ruv@ruv.net>
There was a problem hiding this comment.
Pull request overview
This PR removes redundant fetch/XMLHttpRequest interception logic from the content script (src/injected.js) and leaves NIP-98 request signing/interception to the page-context interceptor (src/nip98-interceptor.js), with the content script acting as a CustomEvent relay to the background script.
Changes:
- Deleted the content-script-side fetch/XHR interception implementation to avoid duplicate auth injection and retry loops.
- Kept/clarified the content script’s responsibilities as a relay for NIP-07 (
podkey-request/podkey-response) and NIP-98 (podkey-nip98-request/podkey-nip98-response) messaging. - Added/kept script injection error handling and simplified load logging.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Fetch/XHR interception for NIP-98 auth headers is handled exclusively | ||
| * by src/nip98-interceptor.js (injected into the page context below). | ||
| * This content script only relays messages between the page and the | ||
| * extension background via CustomEvents. |
| * Fetch/XHR interception for NIP-98 auth headers is handled exclusively | ||
| * by src/nip98-interceptor.js (injected into the page context below). | ||
| * This content script only relays messages between the page and the |
|
Folded into #22 — a single CI-green, adversarially-reviewed PR that consolidates this branch with the other security fixes plus NIP-44 and residual gap-fixes (exact-host trust, NIP-98 nonce, signEvent self-verify, honest provider surface, auto-sign default OFF). Suggest reviewing/merging #22; this can be closed in its favour. |
|
DreamLab-AI Mega-Sprint seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Consolidated hardening of the Podkey key-holding signer: brings seven in-flight security/feature branches plus residual gap-fills, a polished CSP-clean UI, a CI build/test/lint gate, and a 133-test suite into one reviewable PR. The fix set was independently re-derived from a full audit of the extension and every item below was verified fixed *in the final tree* by an adversarial review pass (not just claimed). **Supersedes** (folded in here; safe to close in favour of this PR): #12, #14, #17, #18, #19, #20, #21. ## Security posture — before → after | Area | Before | After | |---|---|---| | Consent | Auto-approve everything (`showPermissionPrompt` hard-returned `true`) | **Explicit per-origin consent** popup that blocks on the user's decision; closing or 60s timeout denies | | Key at rest | `storage.local` (persisted plaintext) | **`storage.session` only** (in-memory, cleared on browser close); raw key never crosses to the page | | Crypto | A fake `crypto-browser.js` in the tree (`pubkey = sha256(privkey)`) | Deleted; **real `@noble` crypto** only, with `signEvent` schnorr self-verify before return | | NIP-98 | Signed-event cache → byte-identical id → replayable | **Fresh per-token 16-byte nonce**, redirect-aware `u` tag, page-side body-hash binding | | Solid-host trust | Substring match (`inrupt.net.evil.com` accepted) | **Exact-host match**, case-insensitive; lookalikes rejected | | Interception | Double fetch/XHR interception + double 401-retry | **Single interception path**, one retry | | Message channel | Page could inject arbitrary fields / origin / `privateKey` | Content script **whitelists 4 message types, forwards only safe fields, enforces the real origin** | | Provider surface | Advertised `nip04`/`getRelays` that throw / return `{}` | **Honest**: `getPublicKey`, `signEvent`, `nip44.{encrypt,decrypt}` only | | UI | Inline scripts + `innerHTML` XSS sinks | **CSP `script-src 'self'`**; all DOM via `textContent`/`createElement` | | Auto-sign | Defaulted **ON** (first-run silent trust + NIP-98 mint) | Defaults **OFF** — silent trusted-origin Solid signing and NIP-98 auto-trust are strictly opt-in | ## Notes for review - **Consent timeout:** `approve.js` actively sends `approved:false` + closes at the 60s mark (one-shot `decisionSent` guard) so the visible countdown is truthful; the background `chrome.windows` 60s timer remains a backstop. If you'd prefer the popup stay passive and let the background own the timeout, that is the single behavioural line to reconsider — everything else on the UI is restyling. - The page-context `nip98-interceptor.js` still duplicates a couple of `auth-header-utils.js` helpers because page context can't ESM-import the module; intentional, tracked separately. ## Verification - `npm run build` ✓ · `npm test` → **133 pass / 0 fail** ✓ · `npm run lint` → **0 errors** (now `no-unused-vars: error`) ✓ - New `.github/workflows/ci.yml` runs build → test → lint on every PR and push to `main`. 🤖 Generated by Claude Code
Summary
window.fetchandXMLHttpRequestinterception fromsrc/injected.js(content script context)src/nip98-interceptor.js(page context) already handles all fetch/XHR interception with its own clean implementation using CustomEvent-based message passinggetAuthHeaderFnstarted as null and relied on async setup viawindow.__podkey_setAuthFnpodkey-request/podkey-responseevents for NIP-07 andpodkey-nip98-request/podkey-nip98-responseevents for NIP-98Test plan
window.nostroperations (getPublicKey, signEvent) still work on Nostr client pages🤖 Generated with claude-flow