security: remove NIP-98 signed event cache (replay prevention)#18
Closed
jjohare wants to merge 1 commit into
Closed
security: remove NIP-98 signed event cache (replay prevention)#18jjohare wants to merge 1 commit into
jjohare wants to merge 1 commit into
Conversation
Remove the nip98Cache Map and all caching logic. The cache reused signed events for identical URL+method+bodyHash within 60 seconds, causing the same event ID to be sent multiple times. Servers with replay protection reject duplicate event IDs, and the predictable cache keys create an additional attack surface. NIP-98 event creation is computationally cheap (~1ms with noble/secp256k1), so caching provides no meaningful benefit. Co-Authored-By: claude-flow <ruv@ruv.net>
melvincarvalho
self-requested a review
May 13, 2026 06:52
Collaborator
Author
|
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. |
jjohare
added a commit
that referenced
this pull request
Jun 15, 2026
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
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
nip98CacheMap and all associated caching logic fromsrc/background.jsurl:method:bodyHash) create an additional attack surfaceTest plan
Co-Authored-By: claude-flow ruv@ruv.net