import { runFuzzer, rng } from "./fuzz-shared.mjs"; import { TextEncoder, TextDecoder } from "util"; // Polyfills global.TextEncoder = TextEncoder; global.TextDecoder = TextDecoder; global.window = { bitvidNostrEventOverrides: {} }; // Import the target module // Note: We are in scripts/agent/ import * as Schemas from "../../js/nostrEventSchemas.js"; async function fuzzTest(iteration) { // We choose a random function to fuzz in each iteration or fuzz all of them? // Let's pick one random target per iteration to keep it simple and isolate crashes. const targets = [ "buildVideoPostEvent", "buildViewEvent", "buildCommentEvent", "buildReactionEvent", "buildZapRequestEvent", "sanitizeAdditionalTags", "validateEventStructure", "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); let inputs = {}; // Helper to generate a comprehensive random params object const genParams = () => { // 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; // Otherwise generate a rich object const params = { pubkey: rng.mixedString(64), created_at: rng.bool() ? rng.int(0, 2000000000) : rng.nastyString(), content: rng.bool() ? rng.mixedString(1000) : rng.recursiveObject(2), additionalTags: rng.bool() ? rng.array(() => rng.array(() => rng.mixedString(20), 5), 10) : rng.nastyString(), }; // Add specific fields that might be used by specific builders const extraFields = [ "dTagValue", "url", "magnet", "thumbnail", "description", "mode", "videoRootId", "pointerValue", "pointerTag", "pointerTags", "dedupeTag", "includeSessionTag", "videoEventId", "videoEventRelay", "videoDefinitionAddress", "rootIdentifier", "parentCommentId", "rootKind", "rootAuthorPubkey", "parentKind", "recipientPubkey", "relays", "amountSats", "lnurl", "eventId", "coordinate", "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()) { 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 } } }); return params; }; switch (targetName) { case "buildVideoPostEvent": inputs = genParams(); Schemas.buildVideoPostEvent(inputs); break; case "buildViewEvent": inputs = genParams(); Schemas.buildViewEvent(inputs); break; case "buildCommentEvent": inputs = genParams(); Schemas.buildCommentEvent(inputs); break; case "buildReactionEvent": inputs = genParams(); Schemas.buildReactionEvent(inputs); break; case "buildZapRequestEvent": inputs = genParams(); Schemas.buildZapRequestEvent(inputs); break; case "sanitizeAdditionalTags": // sanitizeAdditionalTags expects an array of tags (arrays) if (rng.bool()) { inputs = rng.array(() => rng.array(() => rng.mixedString(20), 5), 20); } else { inputs = rng.nastyString(); // or object, etc. } Schemas.sanitizeAdditionalTags(inputs); break; case "validateEventStructure": const type = rng.oneOf(Object.values(Schemas.NOTE_TYPES)); let event = genParams(); // repurpose genParams to make a fake event // Ensure event is an object for this specific test setup, unless we want to fuzz that too. // validateEventStructure checks: if (!event || typeof event !== "object") return { valid: false ... } // So passing a string is valid input for the function (should not crash), // but my fuzzer crashes trying to assign .kind to it. if (event && typeof event === 'object') { event.kind = rng.int(0, 50000); event.tags = rng.array(() => rng.array(() => rng.mixedString(10), 3), 5); } inputs = { type, event }; Schemas.validateEventStructure(type, event); break; case "setNostrEventSchemaOverrides": const randomType = rng.oneOf(Object.values(Schemas.NOTE_TYPES)); if (rng.bool()) { // Circular object const obj = { foo: "bar" }; obj.self = obj; inputs = { [randomType]: obj }; } else { inputs = { [randomType]: genParams() }; } // If we set overrides, we should also trigger usage of them to see if it crashes later Schemas.setNostrEventSchemaOverrides(inputs); // 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", 10000, fuzzTest).catch(err => { console.error("Fatal fuzzer error:", err); process.exit(1); });