diff --git a/scripts/agent/fuzz-dm-decryptor.mjs b/scripts/agent/fuzz-dm-decryptor.mjs index 8c332dce..8ff7f983 100644 --- a/scripts/agent/fuzz-dm-decryptor.mjs +++ b/scripts/agent/fuzz-dm-decryptor.mjs @@ -1,72 +1,84 @@ +import "./setup-test-env.js"; import { Fuzzer } from "./fuzz-lib.mjs"; import { decryptDM } from "../../js/dmDecryptor.js"; -// Mock global dependencies -if (typeof global.window === "undefined") { - global.window = { - location: { protocol: "https:" }, - crypto: { - getRandomValues: (arr) => crypto.randomFillSync(arr), - subtle: { - digest: async (algo, data) => { - const hash = crypto.createHash("sha256").update(data).digest(); - return hash.buffer; - } - } - } - }; -} -if (typeof global.localStorage === "undefined") { - global.localStorage = { - getItem: () => null, - setItem: () => {}, - removeItem: () => {} - }; -} - const fuzzer = new Fuzzer("dm-decryptor"); -async function test(fuzzer, state) { - // Generate random event - const event = { - kind: fuzzer.randBool() ? fuzzer.pick([4, 1059]) : fuzzer.randInt(0, 20000), // 4=DM, 1059=GiftWrap - created_at: Math.floor(Date.now() / 1000), - tags: fuzzer.randArray(() => { - const tagType = fuzzer.pick(["p", "e", "encrypted"]); - if (tagType === "p") return ["p", fuzzer.randString(64, "0123456789abcdef")]; - if (tagType === "encrypted") return ["encrypted", fuzzer.pick(["nip04", "nip44", "nip44_v2"])]; - return [fuzzer.randString(5), fuzzer.randString(10)]; - }, 0, 5), - content: fuzzer.randUnicodeString(fuzzer.randInt(0, 200)), - pubkey: fuzzer.randString(64, "0123456789abcdef"), - id: fuzzer.randString(64, "0123456789abcdef") - }; +class MockDecryptor { + constructor(scheme, shouldFail = false) { + this.scheme = scheme; + this.shouldFail = shouldFail; + this.supportsGiftWrap = scheme === "nip44" || scheme === "nip44_v2"; + } - if (fuzzer.randBool()) event.content = fuzzer.randJSON(); // Garbage content - - // Mock decryptors - const decryptors = fuzzer.randArray(() => { - return { - decrypt: async (pubkey, ciphertext) => { - if (fuzzer.randBool()) throw new Error("Decrypt failed"); - // Return random valid JSON or garbage - if (fuzzer.randBool()) return JSON.stringify(fuzzer.randJSON()); - return fuzzer.randUnicodeString(50); - }, - scheme: fuzzer.pick(["nip04", "nip44", "nip44_v2", "unknown"]), - priority: fuzzer.randInt(0, 10), - supportsGiftWrap: fuzzer.randBool() - }; - }, 0, 3); - - const context = { - actorPubkey: fuzzer.randString(64, "0123456789abcdef"), - decryptors: decryptors - }; - - state.input = { event, context }; - - await decryptDM(event, context); + async decrypt(pubkey, ciphertext) { + if (this.shouldFail) { + throw new Error("Decryption failed (mock)"); + } + // Return something that might be JSON or might be garbage + if (fuzzer.randBool()) { + // Return valid JSON event string (inner rumor) + return JSON.stringify({ + kind: 1, + content: "Hello world " + fuzzer.randString(10), + pubkey: fuzzer.randString(64, "0123456789abcdef"), + created_at: Math.floor(Date.now() / 1000), + tags: [] + }); + } else { + // Return garbage string + return fuzzer.randString(50); + } + } } -fuzzer.runFuzzLoop(2000, test); +async function fuzzTest(fuzzer, state) { + const schemes = ["nip04", "nip44", "nip44_v2", "unknown"]; + + // Create decryptors + const decryptors = []; + const numDecryptors = fuzzer.randInt(0, 3); + for(let i=0; i fuzzer.randString(10), 1, 4)); + } + + // Context + const context = { + actorPubkey: fuzzer.randString(64, "0123456789abcdef"), + decryptors: decryptors + }; + + state.input = { + event, + context + }; + + try { + await decryptDM(event, context); + } catch (err) { + // We expect decryptDM to handle errors gracefully and return { ok: false, errors: ... } + // If it throws, it's a bug (except maybe argument validation if we pass null event) + throw err; + } +} + +fuzzer.runFuzzLoop(5000, fuzzTest).catch(console.error); diff --git a/scripts/agent/fuzz-magnet-utils.mjs b/scripts/agent/fuzz-magnet-utils.mjs index a0776e3f..fca6f43d 100644 --- a/scripts/agent/fuzz-magnet-utils.mjs +++ b/scripts/agent/fuzz-magnet-utils.mjs @@ -1,36 +1,38 @@ +import "./setup-test-env.js"; import { Fuzzer } from "./fuzz-lib.mjs"; import { normalizeAndAugmentMagnet } from "../../js/magnetUtils.js"; -// Mock global dependencies if needed -if (typeof global.window === "undefined") { - global.window = { - location: { protocol: "https:" } - }; -} - const fuzzer = new Fuzzer("magnet-utils"); -async function test(fuzzer, state) { - const rawValue = fuzzer.randBool() ? - `magnet:?xt=urn:btih:${fuzzer.randString(40, "0123456789abcdef")}&dn=${fuzzer.randString(10)}` : - fuzzer.randUnicodeString(100); +async function fuzzTest(fuzzer, state) { + // Generate inputs + const rawValue = fuzzer.randBool() ? fuzzer.randString(100) : "magnet:?xt=urn:btih:" + fuzzer.randString(40, "0123456789abcdef"); - const options = { - webSeed: fuzzer.randBool() ? fuzzer.randString(50) : fuzzer.randArray(() => fuzzer.randString(50), 0, 3), - torrentUrl: fuzzer.randBool() ? fuzzer.randString(50) : undefined, - xs: fuzzer.randBool() ? fuzzer.randString(50) : undefined, - extraTrackers: fuzzer.randArray(() => fuzzer.randString(30), 0, 5), - appProtocol: fuzzer.pick(["http:", "https:", "magnet:", "unknown:"]), - logger: () => {} // No-op logger - }; + // Inject garbage into magnet link + let malformedValue = rawValue; + if (fuzzer.randBool()) { + // url encode some chars + malformedValue = encodeURIComponent(rawValue); + } - // Sometimes pass garbage options - if (fuzzer.randBool()) { - options.webSeed = fuzzer.randJSON(); - } + const options = { + webSeed: fuzzer.randBool() ? fuzzer.randString(50) : [fuzzer.randString(50)], + torrentUrl: fuzzer.randString(50), + xs: fuzzer.randString(50), + extraTrackers: fuzzer.randArray(() => fuzzer.randString(30), 0, 3), + appProtocol: fuzzer.randBool() ? "http:" : "https:" + }; - state.input = { rawValue, options }; - normalizeAndAugmentMagnet(rawValue, options); + state.input = { + rawValue: malformedValue, + options + }; + + try { + normalizeAndAugmentMagnet(malformedValue, options); + } catch (err) { + throw err; + } } -fuzzer.runFuzzLoop(5000, test); +fuzzer.runFuzzLoop(5000, fuzzTest).catch(console.error); diff --git a/scripts/agent/fuzz-nostr-schemas.mjs b/scripts/agent/fuzz-nostr-schemas.mjs index bf5e117d..17450f47 100644 --- a/scripts/agent/fuzz-nostr-schemas.mjs +++ b/scripts/agent/fuzz-nostr-schemas.mjs @@ -1,106 +1,84 @@ +import "./setup-test-env.js"; import { Fuzzer } from "./fuzz-lib.mjs"; import * as schemas from "../../js/nostrEventSchemas.js"; -// Mock global dependencies if needed -if (typeof global.window === "undefined") { - global.window = {}; -} - const fuzzer = new Fuzzer("nostr-schemas"); -// Builders take a params object. -const builders = [ - "buildVideoPostEvent", - "buildVideoMirrorEvent", - "buildRepostEvent", - "buildRelayListEvent", - "buildDmRelayListEvent", - "buildProfileMetadataEvent", - "buildMuteListEvent", - "buildDmAttachmentEvent", - "buildDmReadReceiptEvent", - "buildDmTypingIndicatorEvent", - "buildViewEvent", - "buildZapRequestEvent", - "buildReactionEvent", - "buildCommentEvent", - "buildWatchHistoryEvent", - "buildSubscriptionListEvent", - "buildBlockListEvent", - "buildHashtagPreferenceEvent", +// Mappers for build functions +const BUILDERS = [ + { name: "buildVideoPostEvent", fn: schemas.buildVideoPostEvent }, + { name: "buildVideoMirrorEvent", fn: schemas.buildVideoMirrorEvent }, + { name: "buildRepostEvent", fn: schemas.buildRepostEvent }, + { name: "buildRelayListEvent", fn: schemas.buildRelayListEvent }, + { name: "buildDmRelayListEvent", fn: schemas.buildDmRelayListEvent }, + { name: "buildProfileMetadataEvent", fn: schemas.buildProfileMetadataEvent }, + { name: "buildMuteListEvent", fn: schemas.buildMuteListEvent }, + { name: "buildDmAttachmentEvent", fn: schemas.buildDmAttachmentEvent }, + { name: "buildDmReadReceiptEvent", fn: schemas.buildDmReadReceiptEvent }, + { name: "buildDmTypingIndicatorEvent", fn: schemas.buildDmTypingIndicatorEvent }, + { name: "buildViewEvent", fn: schemas.buildViewEvent }, + { name: "buildZapRequestEvent", fn: schemas.buildZapRequestEvent }, + { name: "buildReactionEvent", fn: schemas.buildReactionEvent }, + { name: "buildCommentEvent", fn: schemas.buildCommentEvent }, + { name: "buildWatchHistoryEvent", fn: schemas.buildWatchHistoryEvent }, + { name: "buildSubscriptionListEvent", fn: schemas.buildSubscriptionListEvent }, + { name: "buildBlockListEvent", fn: schemas.buildBlockListEvent }, + { name: "buildHashtagPreferenceEvent", fn: schemas.buildHashtagPreferenceEvent }, ]; -async function test(fuzzer, state) { - const action = fuzzer.randInt(0, 3); - let input = {}; +async function fuzzTest(fuzzer, state) { + // 1. Fuzz Builder Functions + const builder = fuzzer.pick(BUILDERS); - if (action === 0) { - // Fuzz sanitizeAdditionalTags - const tags = fuzzer.randArray(() => { - if (fuzzer.randBool()) { - return fuzzer.randArray(() => fuzzer.randString(10), 0, 5); - } else { - return fuzzer.randJSON(); // Garbage + // Generate random params + const params = fuzzer.randJSON(); + state.input = { + target: builder.name, + params: params + }; + + try { + // Some builders require specific args like buildAdminListEvent(listKey, params) + // but most take a single params object. + // We didn't include buildAdminListEvent in the list above because it takes 2 args. + + const event = builder.fn(params); + + // 2. Validate the generated event + if (event && event.kind) { + // Find schema type for this kind to validate + let type = null; + for (const [key, val] of Object.entries(schemas.NOTE_TYPES)) { + const schema = schemas.getNostrEventSchema(val); + if (schema && schema.kind === event.kind) { + type = val; + break; } - }, 0, 20); - input = { target: "sanitizeAdditionalTags", tags }; - state.input = input; - schemas.sanitizeAdditionalTags(tags); + } - } else if (action === 1) { - // Fuzz validateEventAgainstSchema - const types = Object.keys(schemas.NOTE_TYPES); + if (type) { + schemas.validateEventAgainstSchema(type, event); + } + } + } catch (err) { + throw err; + } + + // 3. Fuzz Validation directly with random events + if (fuzzer.randBool()) { + const types = Object.values(schemas.NOTE_TYPES); const type = fuzzer.pick(types); - const event = { - kind: fuzzer.randInt(0, 40000), - content: fuzzer.randUnicodeString(100), - tags: fuzzer.randArray(() => fuzzer.randArray(() => fuzzer.randString(10), 0, 5), 0, 10), - pubkey: fuzzer.randString(64, "0123456789abcdef"), - created_at: Math.floor(Date.now() / 1000) + const randomEvent = fuzzer.randJSON(); + + state.input = { + target: "validateEventAgainstSchema", + type: type, + event: randomEvent }; - // Mutate event - if (fuzzer.randBool()) event.content = fuzzer.randJSON(); - if (fuzzer.randBool()) event.tags = "not an array"; - - input = { target: "validateEventAgainstSchema", type, event }; - state.input = input; - schemas.validateEventAgainstSchema(type, event); - - } else if (action === 2) { - // Fuzz Builders - const builderName = fuzzer.pick(builders); - const builderFn = schemas[builderName]; - - // Generate random params - const params = fuzzer.randObject({ - pubkey: () => fuzzer.randString(64, "0123456789abcdef"), - created_at: () => Math.floor(Date.now() / 1000), - content: () => fuzzer.randUnicodeString(50), - additionalTags: () => fuzzer.randArray(() => [fuzzer.randString(5), fuzzer.randString(10)], 0, 5), - dTagValue: () => fuzzer.randString(10), - eventId: () => fuzzer.randString(64, "0123456789abcdef"), - // Add more random keys that might be expected or unexpected - [fuzzer.randString(5)]: () => fuzzer.randJSON() - }); - - input = { target: builderName, params }; - state.input = input; - - if (typeof builderFn === "function") { - builderFn(params); - } - } else if (action === 3) { - // buildAdminListEvent special case - const listKey = fuzzer.pick(["moderation", "editors", "whitelist", "blacklist", fuzzer.randString(5)]); - const params = { - pubkey: fuzzer.randString(64, "0123456789abcdef"), - hexPubkeys: fuzzer.randArray(() => fuzzer.randString(64, "0123456789abcdef"), 0, 5) - }; - input = { target: "buildAdminListEvent", listKey, params }; - state.input = input; - schemas.buildAdminListEvent(listKey, params); + schemas.validateEventAgainstSchema(type, randomEvent); } } -fuzzer.runFuzzLoop(2000, test); +// Run for 5000 iterations +fuzzer.runFuzzLoop(5000, fuzzTest).catch(console.error);