diff --git a/js/nostr/toolkit.js b/js/nostr/toolkit.js index 94f46e5a..05d47e0b 100644 --- a/js/nostr/toolkit.js +++ b/js/nostr/toolkit.js @@ -151,7 +151,11 @@ function normalizeRelayList(relays) { // 64-char lowercase hex. A single malformed/odd-length entry makes strict relays // (e.g. primal) reject the whole REQ with "uneven size input to from_hex", // silently dropping that subscription's results. -const HEX64_FILTER_FIELDS = ["ids", "authors", "#e", "#p", "#q"]; +// Includes the NIP-22 UPPERCASE root-scope tags #E (root event id) and #P (root +// author pubkey) — relays hex-decode these too, and the kind-1111 comment +// subscriptions query by them, so an odd-length value there was bypassing the +// sanitizer and getting the whole REQ rejected with "uneven size input to from_hex". +const HEX64_FILTER_FIELDS = ["ids", "authors", "#e", "#p", "#q", "#E", "#P"]; const HEX64_PATTERN = /^[0-9a-f]{64}$/; // Strip invalid hex from the hex-only filter fields so one bad value can't get a diff --git a/tests/filter-hex-sanitize.test.mjs b/tests/filter-hex-sanitize.test.mjs index a3701af7..ab56bbe1 100644 --- a/tests/filter-hex-sanitize.test.mjs +++ b/tests/filter-hex-sanitize.test.mjs @@ -62,3 +62,20 @@ test("ignores empty/absent hex fields", () => { const out = sanitizeHexFilterFields({ kinds: [1], authors: [] }); assert.deepEqual(out, { kinds: [1], authors: [] }); }); + +test("sanitizes NIP-22 uppercase root tags #E/#P (kind-1111 comments)", () => { + // Relays hex-decode #E (root event id) and #P (root author pubkey) too. An + // odd-length value there previously bypassed the sanitizer and got the whole + // comment REQ rejected ("uneven size input to from_hex"). + const out = sanitizeHexFilterFields({ + kinds: [1111], + "#E": [HEX_A, ODD], + "#P": [HEX_B], + "#K": ["30078"], // a non-hex tag must be left untouched + "#A": ["30078:" + HEX_A + ":dtag"], // address tag is not hex-decoded + }); + assert.deepEqual(out["#E"], [HEX_A], "drops the odd-length root event id"); + assert.deepEqual(out["#P"], [HEX_B]); + assert.deepEqual(out["#K"], ["30078"], "#K (kind) untouched"); + assert.deepEqual(out["#A"], ["30078:" + HEX_A + ":dtag"], "#A (address) untouched"); +}); diff --git a/todo/TODO_2026-06-20_pre-launch.md b/todo/TODO_2026-06-20_pre-launch.md index cd2b4375..a6dc2b03 100644 --- a/todo/TODO_2026-06-20_pre-launch.md +++ b/todo/TODO_2026-06-20_pre-launch.md @@ -233,9 +233,13 @@ un-hides when WebTorrent flips green). The real gaps: `loadCommunityBlacklistEntries` (admin editor/whitelist/blacklist lists? a per-author follow-set fetch?). Re-check on a fresh hard-refresh; if still ~32, trace the actual emitter. -- [ ] **`from_hex` source not root-caused.** The sanitizer (`733cb593`) defends + - dev-logs `[toolkit] dropped N invalid hex value(s) from filter ""` — - watch that log to find which subscription ships the odd-length hex, fix at source. +- [x] **`from_hex` root-caused + FIXED (2026-06-23).** The sanitizer + (`HEX64_FILTER_FIELDS`, toolkit.js) covered `ids/authors/#e/#p/#q` but NOT the + NIP-22 UPPERCASE root tags `#E`/`#P`, which relays also hex-decode and which + the kind-1111 comment subscriptions (`commentEvents.js`) query by. An + odd-length root event-id/author there bypassed the sanitizer and got the whole + REQ rejected ("uneven size input to from_hex"). Added `#E`/`#P` to the + sanitized fields; test in `tests/filter-hex-sanitize.test.mjs`. - [x] **Relay `from_hex` REQ rejects fixed** (`733cb593`): one odd-length/non-hex value in `ids/authors/#e/#p/#q` made strict relays reject a whole REQ; now sanitized at the pool choke point (`normalizeFilterList`).