Files
bitvid/tests/filter-hex-sanitize.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 733cb5937b nostr: sanitize hex-only filter fields (fix relay "from_hex" REQ rejects)
Strict relays (primal) reject a whole REQ with "uneven size input to
from_hex" when any ids/authors/#e/#p/#q entry isn't 64-char hex — silently
dropping that subscription's results. One malformed value anywhere in a
filter poisoned the request.

Sanitize at the universal pool choke point (normalizeFilterList, used by
both the shimmed pool.sub and pool.list): lowercase/trim and drop non-
64-hex entries from the hex-only fields; if a once-populated hex field ends
up empty, drop that filter rather than silently broadening the query.
Non-hex fields (#a/#d/#t/kinds/…) are untouched. Dev-logs what it strips
(with the filter's kinds) so the malformed source can be traced.

Also records pre-launch TODO items: NIP-71 inbound ingest is now live
(Phase 2, incl. the resolve-posted-at history-fetch fix), and a new
launch-blocker — channel-profile walls fetch only kind-30078 so a
creator's NIP-71 videos don't show on their profile.

Tests: filter-hex-sanitize (strip/keep/lowercase/drop-empty/leave-non-hex);
mutation-verified. 1244/1245 unit (known UploadModal flake).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 18:24:05 -04:00

65 lines
2.3 KiB
JavaScript

// Sanitizing hex-only filter fields (ids/authors/#e/#p/#q) before they reach a
// relay. One odd-length/non-hex value otherwise makes strict relays reject the
// whole REQ ("uneven size input to from_hex"), silently dropping results.
import assert from "node:assert/strict";
import test from "node:test";
import { sanitizeHexFilterFields } from "../js/nostr/toolkit.js";
const HEX_A = "a".repeat(64);
const HEX_B = "b".repeat(64);
const ODD = "abc"; // odd length -> from_hex failure
const SHORT = "a".repeat(63); // wrong length
test("strips invalid hex from authors but keeps valid entries", () => {
const out = sanitizeHexFilterFields({ kinds: [0], authors: [HEX_A, ODD, HEX_B] });
assert.deepEqual(out.authors, [HEX_A, HEX_B]);
assert.deepEqual(out.kinds, [0], "non-hex fields untouched");
});
test("lowercases and trims valid hex", () => {
const out = sanitizeHexFilterFields({ ids: [` ${HEX_A.toUpperCase()} `] });
assert.deepEqual(out.ids, [HEX_A]);
});
test("drops the whole filter when a populated hex field becomes empty", () => {
// If every author was invalid, removing the key would broaden the query to
// "all authors" — drop the filter instead.
const out = sanitizeHexFilterFields({ kinds: [1], authors: [ODD, SHORT] });
assert.equal(out, null);
});
test("sanitizes event/pubkey tag filters (#e/#p/#q)", () => {
const out = sanitizeHexFilterFields({
kinds: [1984],
"#e": [HEX_A, ODD],
"#p": [HEX_B],
});
assert.deepEqual(out["#e"], [HEX_A]);
assert.deepEqual(out["#p"], [HEX_B]);
});
test("leaves non-hex tag fields (#a/#d/#t) alone", () => {
const filter = {
kinds: [21, 22],
"#a": ["30078:" + HEX_A + ":some-d-tag"],
"#d": ["subscriptions"],
"#t": ["music"],
};
const out = sanitizeHexFilterFields(filter);
assert.deepEqual(out["#a"], filter["#a"]);
assert.deepEqual(out["#d"], filter["#d"]);
assert.deepEqual(out["#t"], filter["#t"]);
});
test("returns the same object when nothing needs sanitizing (no needless copy)", () => {
const filter = { kinds: [0], authors: [HEX_A] };
const out = sanitizeHexFilterFields(filter);
assert.equal(out, filter);
});
test("ignores empty/absent hex fields", () => {
const out = sanitizeHexFilterFields({ kinds: [1], authors: [] });
assert.deepEqual(out, { kinds: [1], authors: [] });
});