From 9f67b9e06b4a3f24eb695311aac607aac2482abd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:27:36 +0000 Subject: [PATCH] feat: add fuzz testing harnesses and fix crash in nostrEventSchemas - Expand fuzzing coverage for `js/nostrEventSchemas.js` to include all builder functions. - Fix a crash in `buildAdminListEvent` where non-array `hexPubkeys` caused a TypeError. - Update `scripts/agent/fuzz-dmDecryptor.mjs` and `scripts/agent/fuzz-magnetUtils.mjs` iterations. - Add reproducer for the `buildAdminListEvent` crash. - Generate fuzz reports in `artifacts/`. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- .../crash-buildAdminListEvent-hexPubkeys.json | 10 ++ js/nostrEventSchemas.js | 12 +- scripts/agent/fuzz-dmDecryptor.mjs | 2 +- scripts/agent/fuzz-magnetUtils.mjs | 2 +- scripts/agent/fuzz-nostrEventSchemas.mjs | 121 +++++++++++++++++- 5 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 examples/reproducers/crash-buildAdminListEvent-hexPubkeys.json diff --git a/examples/reproducers/crash-buildAdminListEvent-hexPubkeys.json b/examples/reproducers/crash-buildAdminListEvent-hexPubkeys.json new file mode 100644 index 00000000..78fd7a70 --- /dev/null +++ b/examples/reproducers/crash-buildAdminListEvent-hexPubkeys.json @@ -0,0 +1,10 @@ +{ + "target": "nostrEventSchemas", + "timestamp": "2026-01-28T16:18:44.207Z", + "error": { + "message": "hexPubkeys.forEach is not a function", + "stack": "TypeError: hexPubkeys.forEach is not a function\n at Module.buildAdminListEvent (file:///app/js/nostrEventSchemas.js:2535:14)\n at fuzzTest (file:///app/scripts/agent/fuzz-nostrEventSchemas.mjs:232:17)\n at runFuzzer (file:///app/scripts/agent/fuzz-shared.mjs:164:21)", + "name": "TypeError" + }, + "input": null +} \ No newline at end of file diff --git a/js/nostrEventSchemas.js b/js/nostrEventSchemas.js index 7408ddcf..fae71355 100644 --- a/js/nostrEventSchemas.js +++ b/js/nostrEventSchemas.js @@ -2532,11 +2532,13 @@ export function buildAdminListEvent(listKey, params) { tags.push([schema.identifierTag.name, schema.identifierTag.value]); } const participantTagName = schema?.participantTagName || "p"; - hexPubkeys.forEach((hex) => { - if (typeof hex === "string" && hex.trim()) { - tags.push([participantTagName, hex.trim()]); - } - }); + if (Array.isArray(hexPubkeys)) { + hexPubkeys.forEach((hex) => { + if (typeof hex === "string" && hex.trim()) { + tags.push([participantTagName, hex.trim()]); + } + }); + } appendSchemaTags(tags, schema); const event = { diff --git a/scripts/agent/fuzz-dmDecryptor.mjs b/scripts/agent/fuzz-dmDecryptor.mjs index b3f74a8c..5f713a9d 100644 --- a/scripts/agent/fuzz-dmDecryptor.mjs +++ b/scripts/agent/fuzz-dmDecryptor.mjs @@ -69,7 +69,7 @@ async function fuzzTest(iteration) { return { event, context }; } -runFuzzer("dmDecryptor", 5000, fuzzTest) +runFuzzer("dmDecryptor", 10000, fuzzTest) .catch(err => { console.error("Fatal fuzzer error:", err); process.exit(1); diff --git a/scripts/agent/fuzz-magnetUtils.mjs b/scripts/agent/fuzz-magnetUtils.mjs index 4e402776..342930b1 100644 --- a/scripts/agent/fuzz-magnetUtils.mjs +++ b/scripts/agent/fuzz-magnetUtils.mjs @@ -101,7 +101,7 @@ async function fuzzTest(iteration) { return { rawValue, options }; } -runFuzzer("magnetUtils", 5000, fuzzTest).catch(err => { +runFuzzer("magnetUtils", 10000, fuzzTest).catch(err => { console.error("Fatal fuzzer error:", err); process.exit(1); }); diff --git a/scripts/agent/fuzz-nostrEventSchemas.mjs b/scripts/agent/fuzz-nostrEventSchemas.mjs index f587d64b..073e9046 100644 --- a/scripts/agent/fuzz-nostrEventSchemas.mjs +++ b/scripts/agent/fuzz-nostrEventSchemas.mjs @@ -25,7 +25,26 @@ async function fuzzTest(iteration) { "buildZapRequestEvent", "sanitizeAdditionalTags", "validateEventStructure", - "setNostrEventSchemaOverrides" + "setNostrEventSchemaOverrides", + "buildRepostEvent", + "buildShareEvent", + "buildRelayListEvent", + "buildDmRelayListEvent", + "buildProfileMetadataEvent", + "buildMuteListEvent", + "buildDeletionEvent", + "buildLegacyDirectMessageEvent", + "buildDmAttachmentEvent", + "buildDmReadReceiptEvent", + "buildDmTypingIndicatorEvent", + "buildWatchHistoryEvent", + "buildSubscriptionListEvent", + "buildBlockListEvent", + "buildHashtagPreferenceEvent", + "buildAdminListEvent", + "buildHttpAuthEvent", + "buildReportEvent", + "buildVideoMirrorEvent" ]; const targetName = rng.oneOf(targets); @@ -33,7 +52,7 @@ async function fuzzTest(iteration) { // Helper to generate a comprehensive random params object const genParams = () => { - // 50% chance of being undefined/null/non-object to test robustness + // 10% chance of being undefined/null/non-object to test robustness if (Math.random() < 0.1) return rng.nastyString(); if (Math.random() < 0.1) return null; @@ -52,14 +71,27 @@ async function fuzzTest(iteration) { "videoEventId", "videoEventRelay", "videoDefinitionAddress", "rootIdentifier", "parentCommentId", "rootKind", "rootAuthorPubkey", "parentKind", "recipientPubkey", "relays", "amountSats", "lnurl", "eventId", "coordinate", - "targetPointer", "targetAuthorPubkey" + "targetPointer", "targetAuthorPubkey", + // Added fields + "repostKind", "targetKind", "targetEvent", "serializedEvent", + "video", "metadata", "pTags", "encrypted", "encryptionTag", + "eventIds", "addresses", "reason", "ciphertext", "attachment", + "messageKind", "expiresAt", "monthIdentifier", "hexPubkeys", + "payload", "reportType", "address", "addressRelay", "authorPubkey", + "eventRelay", "method", "userId", "relayHint" ]; extraFields.forEach(field => { if (rng.bool()) { params[field] = rng.mixedString(50); } else if (rng.bool()) { - params[field] = rng.recursiveObject(1); // Potentially invalid type + if (field === "targetEvent" || field === "video" || field === "attachment" || field === "metadata") { + params[field] = rng.recursiveObject(2); + } else if (field === "relays" || field === "pTags" || field === "eventIds" || field === "addresses" || field === "hexPubkeys") { + params[field] = rng.array(() => rng.mixedString(64), 5); + } else { + params[field] = rng.recursiveObject(1); // Potentially invalid type + } } }); @@ -132,12 +164,91 @@ async function fuzzTest(iteration) { // Trigger usage Schemas.getNostrEventSchema(randomType); break; + + case "buildRepostEvent": + inputs = genParams(); + Schemas.buildRepostEvent(inputs); + break; + case "buildShareEvent": + inputs = genParams(); + Schemas.buildShareEvent(inputs); + break; + case "buildRelayListEvent": + inputs = genParams(); + Schemas.buildRelayListEvent(inputs); + break; + case "buildDmRelayListEvent": + inputs = genParams(); + Schemas.buildDmRelayListEvent(inputs); + break; + case "buildProfileMetadataEvent": + inputs = genParams(); + Schemas.buildProfileMetadataEvent(inputs); + break; + case "buildMuteListEvent": + inputs = genParams(); + Schemas.buildMuteListEvent(inputs); + break; + case "buildDeletionEvent": + inputs = genParams(); + Schemas.buildDeletionEvent(inputs); + break; + case "buildLegacyDirectMessageEvent": + inputs = genParams(); + Schemas.buildLegacyDirectMessageEvent(inputs); + break; + case "buildDmAttachmentEvent": + inputs = genParams(); + Schemas.buildDmAttachmentEvent(inputs); + break; + case "buildDmReadReceiptEvent": + inputs = genParams(); + Schemas.buildDmReadReceiptEvent(inputs); + break; + case "buildDmTypingIndicatorEvent": + inputs = genParams(); + Schemas.buildDmTypingIndicatorEvent(inputs); + break; + case "buildWatchHistoryEvent": + inputs = genParams(); + Schemas.buildWatchHistoryEvent(inputs); + break; + case "buildSubscriptionListEvent": + inputs = genParams(); + Schemas.buildSubscriptionListEvent(inputs); + break; + case "buildBlockListEvent": + inputs = genParams(); + Schemas.buildBlockListEvent(inputs); + break; + case "buildHashtagPreferenceEvent": + inputs = genParams(); + Schemas.buildHashtagPreferenceEvent(inputs); + break; + case "buildAdminListEvent": + inputs = genParams(); + // buildAdminListEvent(listKey, params) + const listKey = rng.oneOf(["moderation", "editors", "whitelist", "blacklist", "communityBlacklistSources", rng.nastyString()]); + Schemas.buildAdminListEvent(listKey, inputs); + break; + case "buildHttpAuthEvent": + inputs = genParams(); + Schemas.buildHttpAuthEvent(inputs); + break; + case "buildReportEvent": + inputs = genParams(); + Schemas.buildReportEvent(inputs); + break; + case "buildVideoMirrorEvent": + inputs = genParams(); + Schemas.buildVideoMirrorEvent(inputs); + break; } return { target: targetName, inputs }; } -runFuzzer("nostrEventSchemas", 5000, fuzzTest).catch(err => { +runFuzzer("nostrEventSchemas", 10000, fuzzTest).catch(err => { console.error("Fatal fuzzer error:", err); process.exit(1); });