mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #2316 from PR0M3TH3AN/ai/fuzz-nostr-targets-20260210-192630771952606835
Add fuzzing suite for Nostr parsing, DM decryption, and Magnet normalization
This commit is contained in:
5 files changed
+624
-193
No files matched your search
@@ -1,86 +1,91 @@
|
||||
// Fuzz harness for js/dmDecryptor.js
|
||||
import "./setup-test-env.js";
|
||||
import { Fuzzer } from "./fuzz-lib.mjs";
|
||||
import {
|
||||
randomInt,
|
||||
randomBoolean,
|
||||
randomString,
|
||||
randomHex,
|
||||
randomArray,
|
||||
randomValue,
|
||||
randomObject,
|
||||
runFuzzer,
|
||||
} from "./fuzz-utils.mjs";
|
||||
import { decryptDM } from "../../js/dmDecryptor.js";
|
||||
|
||||
const fuzzer = new Fuzzer("dmDecryptor");
|
||||
function genEvent() {
|
||||
const kind = Math.random() < 0.8 ? (Math.random() < 0.5 ? 4 : 1059) : randomInt(0, 20000);
|
||||
const tags = randomArray(() => {
|
||||
const tagName = Math.random() < 0.5 ? "p" : (Math.random() < 0.5 ? "encrypted" : randomString(5));
|
||||
return [tagName, randomString(20), randomString(10)];
|
||||
}, 0, 10);
|
||||
|
||||
class MockDecryptor {
|
||||
constructor(scheme, shouldFail = false) {
|
||||
this.scheme = scheme;
|
||||
this.shouldFail = shouldFail;
|
||||
this.supportsGiftWrap = scheme === "nip44" || scheme === "nip44_v2";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return {
|
||||
kind,
|
||||
pubkey: randomHex(64),
|
||||
created_at: randomInt(0, 2000000000),
|
||||
tags,
|
||||
content: randomString(randomInt(0, 500)),
|
||||
};
|
||||
}
|
||||
|
||||
async function fuzzTest(fuzzer, state) {
|
||||
const schemes = ["nip04", "nip44", "nip44_v2", "unknown", null];
|
||||
function mockDecryptor(behavior) {
|
||||
return {
|
||||
scheme: Math.random() < 0.5 ? "nip44" : "nip04",
|
||||
decrypt: async (pubkey, ciphertext) => {
|
||||
if (behavior === "fail") {
|
||||
throw new Error("Decryption failed");
|
||||
}
|
||||
if (behavior === "invalid") {
|
||||
return 123; // Invalid return type
|
||||
}
|
||||
if (behavior === "empty") {
|
||||
return "";
|
||||
}
|
||||
if (behavior === "garbage") {
|
||||
return randomString(100); // Not JSON
|
||||
}
|
||||
// Success
|
||||
const innerEvent = {
|
||||
kind: randomInt(0, 20000),
|
||||
pubkey: randomHex(64),
|
||||
created_at: randomInt(0, 2000000000),
|
||||
content: randomString(50),
|
||||
tags: [],
|
||||
};
|
||||
return JSON.stringify(innerEvent);
|
||||
},
|
||||
supportsGiftWrap: Math.random() < 0.7,
|
||||
};
|
||||
}
|
||||
|
||||
// Create decryptors
|
||||
function genDecryptors() {
|
||||
const count = randomInt(0, 5);
|
||||
const decryptors = [];
|
||||
const numDecryptors = fuzzer.randInt(0, 3);
|
||||
for(let i=0; i<numDecryptors; i++) {
|
||||
const scheme = fuzzer.pick(schemes);
|
||||
decryptors.push(new MockDecryptor(scheme, fuzzer.randBool()));
|
||||
for (let i = 0; i < count; i++) {
|
||||
const behaviors = ["success", "fail", "invalid", "empty", "garbage"];
|
||||
const behavior = behaviors[randomInt(0, behaviors.length - 1)];
|
||||
decryptors.push(mockDecryptor(behavior));
|
||||
}
|
||||
|
||||
// Create event
|
||||
// kinds: 4 (legacy), 1059 (gift wrap), random
|
||||
const kinds = [4, 1059, fuzzer.randInt(0, 20000)];
|
||||
const kind = fuzzer.pick(kinds);
|
||||
|
||||
const event = {
|
||||
kind: kind,
|
||||
pubkey: fuzzer.randString(64, "0123456789abcdef"),
|
||||
content: fuzzer.randString(100), // ciphertext
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: []
|
||||
};
|
||||
|
||||
// Add random tags
|
||||
const numTags = fuzzer.randInt(0, 5);
|
||||
for(let i=0; i<numTags; i++) {
|
||||
event.tags.push(fuzzer.randArray(() => 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;
|
||||
// Sometimes add invalid decryptor objects
|
||||
if (Math.random() < 0.2) {
|
||||
decryptors.push(null);
|
||||
decryptors.push({});
|
||||
decryptors.push({ decrypt: "not a function" });
|
||||
}
|
||||
return decryptors;
|
||||
}
|
||||
|
||||
fuzzer.runFuzzLoop(5000, fuzzTest).catch(console.error);
|
||||
function genContext() {
|
||||
return {
|
||||
actorPubkey: randomHex(64),
|
||||
decryptors: genDecryptors(),
|
||||
};
|
||||
}
|
||||
|
||||
function genArgs() {
|
||||
return [genEvent(), genContext()];
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await runFuzzer("decryptDM", decryptDM, genArgs);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,45 +1,50 @@
|
||||
// Fuzz harness for js/magnetUtils.js
|
||||
import "./setup-test-env.js";
|
||||
import { Fuzzer } from "./fuzz-lib.mjs";
|
||||
import { normalizeAndAugmentMagnet } from "../../js/magnetUtils.js";
|
||||
import {
|
||||
randomInt,
|
||||
randomBoolean,
|
||||
randomString,
|
||||
randomHex,
|
||||
randomArray,
|
||||
randomValue,
|
||||
randomObject,
|
||||
runFuzzer,
|
||||
} from "./fuzz-utils.mjs";
|
||||
import {
|
||||
normalizeAndAugmentMagnet,
|
||||
safeDecodeMagnet,
|
||||
extractBtihFromMagnet,
|
||||
normalizeInfoHash,
|
||||
} from "../../js/magnetUtils.js";
|
||||
|
||||
const fuzzer = new Fuzzer("magnetUtils");
|
||||
function genMagnetString() {
|
||||
if (Math.random() < 0.2) return randomString(100); // Garbage
|
||||
if (Math.random() < 0.2) return ""; // Empty
|
||||
|
||||
async function fuzzTest(fuzzer, state) {
|
||||
// Generate inputs
|
||||
const rawValue = fuzzer.randBool() ? fuzzer.randString(100) : "magnet:?xt=urn:btih:" + fuzzer.randString(40, "0123456789abcdef");
|
||||
|
||||
// Inject garbage into magnet link
|
||||
let malformedValue = rawValue;
|
||||
if (fuzzer.randBool()) {
|
||||
// url encode some chars
|
||||
malformedValue = encodeURIComponent(rawValue);
|
||||
}
|
||||
|
||||
if (fuzzer.randBool()) {
|
||||
// Inject huge string
|
||||
malformedValue += fuzzer.randString(10000);
|
||||
}
|
||||
|
||||
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:",
|
||||
logger: () => {}
|
||||
};
|
||||
|
||||
state.input = {
|
||||
rawValue: malformedValue,
|
||||
options
|
||||
};
|
||||
|
||||
try {
|
||||
normalizeAndAugmentMagnet(malformedValue, options);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
let magnet = "magnet:?";
|
||||
const params = [];
|
||||
const count = randomInt(0, 5);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const key = Math.random() < 0.5 ? (Math.random() < 0.5 ? "xt" : "tr") : randomString(2);
|
||||
const value = randomString(20, true);
|
||||
params.push(`${key}=${encodeURIComponent(value)}`);
|
||||
}
|
||||
return magnet + params.join("&");
|
||||
}
|
||||
|
||||
fuzzer.runFuzzLoop(5000, fuzzTest).catch(console.error);
|
||||
function genOptions() {
|
||||
return {
|
||||
webSeed: Math.random() < 0.5 ? randomString(20) : [randomString(20), randomString(20)],
|
||||
torrentUrl: Math.random() < 0.5 ? randomString(30) : undefined,
|
||||
xs: Math.random() < 0.5 ? randomString(30) : undefined,
|
||||
extraTrackers: Math.random() < 0.5 ? [randomString(30)] : undefined,
|
||||
appProtocol: Math.random() < 0.5 ? "http:" : "https:",
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await runFuzzer("safeDecodeMagnet", safeDecodeMagnet, () => [genMagnetString()]);
|
||||
await runFuzzer("extractBtihFromMagnet", extractBtihFromMagnet, () => [genMagnetString()]);
|
||||
await runFuzzer("normalizeInfoHash", normalizeInfoHash, () => [randomString(40)]);
|
||||
await runFuzzer("normalizeAndAugmentMagnet", normalizeAndAugmentMagnet, () => [genMagnetString(), genOptions()]);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,81 +1,285 @@
|
||||
// Fuzz harness for js/nostrEventSchemas.js
|
||||
import "./setup-test-env.js";
|
||||
import { Fuzzer } from "./fuzz-lib.mjs";
|
||||
import * as schemas from "../../js/nostrEventSchemas.js";
|
||||
import {
|
||||
randomInt,
|
||||
randomBoolean,
|
||||
randomString,
|
||||
randomHex,
|
||||
randomArray,
|
||||
randomValue,
|
||||
randomObject,
|
||||
runFuzzer,
|
||||
} from "./fuzz-utils.mjs";
|
||||
import * as Schemas from "../../js/nostrEventSchemas.js";
|
||||
|
||||
const fuzzer = new Fuzzer("nostrEventSchemas");
|
||||
|
||||
// Mappers for build functions
|
||||
const BUILDERS = Object.keys(schemas)
|
||||
.filter(k => k.startsWith('build'))
|
||||
.map(k => ({ name: k, fn: schemas[k] }));
|
||||
|
||||
async function fuzzTest(fuzzer, state) {
|
||||
// 1. Fuzz Builder Functions
|
||||
const builder = fuzzer.pick(BUILDERS);
|
||||
|
||||
// Generate random params
|
||||
const params = fuzzer.randJSON();
|
||||
|
||||
// Inject huge strings occasionally
|
||||
if (Math.random() < 0.05) {
|
||||
if (!params || typeof params !== 'object') {
|
||||
// make it an object
|
||||
} else {
|
||||
// add huge string field
|
||||
params.hugeField = "A".repeat(1000000); // 1MB
|
||||
}
|
||||
}
|
||||
|
||||
state.input = {
|
||||
target: builder.name,
|
||||
params: params
|
||||
};
|
||||
|
||||
try {
|
||||
let args = [params];
|
||||
if (builder.name === 'buildAdminListEvent') {
|
||||
const listKeys = ["moderation", "editors", "whitelist", "blacklist", "invalid", null];
|
||||
args = [fuzzer.pick(listKeys), params];
|
||||
}
|
||||
|
||||
const event = builder.fn(...args);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 randomEvent = fuzzer.randJSON();
|
||||
|
||||
state.input = {
|
||||
target: "validateEventStructure",
|
||||
type: type,
|
||||
event: randomEvent
|
||||
};
|
||||
|
||||
schemas.validateEventStructure(type, randomEvent);
|
||||
}
|
||||
function genTag() {
|
||||
return randomArray(() => randomString(randomInt(0, 20), true), 0, 5);
|
||||
}
|
||||
|
||||
// Run for 5000 iterations
|
||||
fuzzer.runFuzzLoop(5000, fuzzTest).catch(console.error);
|
||||
function genAdditionalTags() {
|
||||
const choice = randomInt(0, 3);
|
||||
if (choice === 0) return [null]; // Invalid input
|
||||
if (choice === 1) return [randomString(100)]; // Invalid input
|
||||
return [randomArray(genTag, 0, 10)];
|
||||
}
|
||||
|
||||
function genEvent() {
|
||||
return {
|
||||
kind: randomInt(0, 50000),
|
||||
pubkey: randomHex(64),
|
||||
created_at: randomInt(0, 2000000000),
|
||||
tags: randomArray(genTag, 0, 20),
|
||||
content: randomString(randomInt(0, 1000), true),
|
||||
};
|
||||
}
|
||||
|
||||
function genValidateEventStructureArgs() {
|
||||
const type = randomArray(() => randomString(10), 1)[0]; // Random type
|
||||
// Also pick a valid type sometimes
|
||||
const validTypes = Object.values(Schemas.NOTE_TYPES);
|
||||
const selectedType =
|
||||
Math.random() < 0.5
|
||||
? validTypes[randomInt(0, validTypes.length - 1)]
|
||||
: type;
|
||||
|
||||
const event = Math.random() < 0.1 ? null : genEvent();
|
||||
return [selectedType, event];
|
||||
}
|
||||
|
||||
function genParams(extraKeys = []) {
|
||||
const base = randomObject(2, 2);
|
||||
const commonKeys = [
|
||||
"pubkey",
|
||||
"created_at",
|
||||
"additionalTags",
|
||||
"content",
|
||||
"tags",
|
||||
];
|
||||
const keys = [...commonKeys, ...extraKeys];
|
||||
|
||||
keys.forEach((key) => {
|
||||
if (Math.random() < 0.7) {
|
||||
if (key === "additionalTags") {
|
||||
base[key] = randomArray(genTag, 0, 5);
|
||||
} else if (key === "created_at") {
|
||||
base[key] = randomInt(0, 2000000000);
|
||||
} else if (key === "pubkey") {
|
||||
base[key] = randomHex(64);
|
||||
} else {
|
||||
base[key] = randomValue(0, 2);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [base];
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await runFuzzer(
|
||||
"sanitizeAdditionalTags",
|
||||
Schemas.sanitizeAdditionalTags,
|
||||
genAdditionalTags
|
||||
);
|
||||
await runFuzzer(
|
||||
"validateEventStructure",
|
||||
Schemas.validateEventStructure,
|
||||
genValidateEventStructureArgs
|
||||
);
|
||||
|
||||
// Build functions
|
||||
const buildFunctions = [
|
||||
{
|
||||
name: "buildVideoPostEvent",
|
||||
fn: Schemas.buildVideoPostEvent,
|
||||
keys: ["dTagValue", "infoHash", "url", "videoRootId"],
|
||||
},
|
||||
{
|
||||
name: "buildHttpAuthEvent",
|
||||
fn: Schemas.buildHttpAuthEvent,
|
||||
keys: ["url", "method", "payload"],
|
||||
},
|
||||
{
|
||||
name: "buildReportEvent",
|
||||
fn: Schemas.buildReportEvent,
|
||||
keys: ["eventId", "userId", "reportType", "relayHint"],
|
||||
},
|
||||
{
|
||||
name: "buildGiftWrapEvent",
|
||||
fn: Schemas.buildGiftWrapEvent,
|
||||
keys: ["recipientPubkey", "ciphertext", "relayHint"],
|
||||
},
|
||||
{
|
||||
name: "buildSealEvent",
|
||||
fn: Schemas.buildSealEvent,
|
||||
keys: ["ciphertext"],
|
||||
},
|
||||
{
|
||||
name: "buildChatMessageEvent",
|
||||
fn: Schemas.buildChatMessageEvent,
|
||||
keys: ["recipientPubkey"],
|
||||
},
|
||||
{
|
||||
name: "buildVideoMirrorEvent",
|
||||
fn: Schemas.buildVideoMirrorEvent,
|
||||
keys: [],
|
||||
},
|
||||
{
|
||||
name: "buildRepostEvent",
|
||||
fn: Schemas.buildRepostEvent,
|
||||
keys: [
|
||||
"eventId",
|
||||
"eventRelay",
|
||||
"address",
|
||||
"addressRelay",
|
||||
"authorPubkey",
|
||||
"repostKind",
|
||||
"targetKind",
|
||||
"targetEvent",
|
||||
"serializedEvent",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "buildShareEvent",
|
||||
fn: Schemas.buildShareEvent,
|
||||
keys: ["video", "relays"],
|
||||
},
|
||||
{
|
||||
name: "buildRelayListEvent",
|
||||
fn: Schemas.buildRelayListEvent,
|
||||
keys: ["relays"],
|
||||
},
|
||||
{
|
||||
name: "buildDmRelayListEvent",
|
||||
fn: Schemas.buildDmRelayListEvent,
|
||||
keys: ["relays"],
|
||||
},
|
||||
{
|
||||
name: "buildProfileMetadataEvent",
|
||||
fn: Schemas.buildProfileMetadataEvent,
|
||||
keys: ["metadata"],
|
||||
},
|
||||
{
|
||||
name: "buildMuteListEvent",
|
||||
fn: Schemas.buildMuteListEvent,
|
||||
keys: ["pTags", "encrypted", "encryptionTag"],
|
||||
},
|
||||
{
|
||||
name: "buildDeletionEvent",
|
||||
fn: Schemas.buildDeletionEvent,
|
||||
keys: ["eventIds", "addresses", "reason"],
|
||||
},
|
||||
{
|
||||
name: "buildLegacyDirectMessageEvent",
|
||||
fn: Schemas.buildLegacyDirectMessageEvent,
|
||||
keys: ["recipientPubkey", "ciphertext"],
|
||||
},
|
||||
{
|
||||
name: "buildDmAttachmentEvent",
|
||||
fn: Schemas.buildDmAttachmentEvent,
|
||||
keys: ["recipientPubkey", "attachment"],
|
||||
},
|
||||
{
|
||||
name: "buildDmReadReceiptEvent",
|
||||
fn: Schemas.buildDmReadReceiptEvent,
|
||||
keys: ["recipientPubkey", "eventId", "messageKind"],
|
||||
},
|
||||
{
|
||||
name: "buildDmTypingIndicatorEvent",
|
||||
fn: Schemas.buildDmTypingIndicatorEvent,
|
||||
keys: ["recipientPubkey", "eventId", "expiresAt"],
|
||||
},
|
||||
{
|
||||
name: "buildViewEvent",
|
||||
fn: Schemas.buildViewEvent,
|
||||
keys: [
|
||||
"pointerValue",
|
||||
"pointerTag",
|
||||
"pointerTags",
|
||||
"dedupeTag",
|
||||
"includeSessionTag",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "buildZapRequestEvent",
|
||||
fn: Schemas.buildZapRequestEvent,
|
||||
keys: [
|
||||
"recipientPubkey",
|
||||
"relays",
|
||||
"amountSats",
|
||||
"lnurl",
|
||||
"eventId",
|
||||
"coordinate",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "buildReactionEvent",
|
||||
fn: Schemas.buildReactionEvent,
|
||||
keys: [
|
||||
"pointerValue",
|
||||
"pointerTag",
|
||||
"pointerTags",
|
||||
"targetPointer",
|
||||
"targetAuthorPubkey",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "buildCommentEvent",
|
||||
fn: Schemas.buildCommentEvent,
|
||||
keys: [
|
||||
"videoEventId",
|
||||
"videoEventRelay",
|
||||
"videoDefinitionAddress",
|
||||
"videoDefinitionRelay",
|
||||
"rootIdentifier",
|
||||
"rootIdentifierRelay",
|
||||
"parentCommentId",
|
||||
"parentCommentRelay",
|
||||
"threadParticipantPubkey",
|
||||
"threadParticipantRelay",
|
||||
"parentIdentifier",
|
||||
"parentIdentifierRelay",
|
||||
"rootKind",
|
||||
"rootAuthorPubkey",
|
||||
"rootAuthorRelay",
|
||||
"parentAuthorPubkey",
|
||||
"parentAuthorRelay",
|
||||
"parentKind",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "buildWatchHistoryEvent",
|
||||
fn: Schemas.buildWatchHistoryEvent,
|
||||
keys: ["monthIdentifier", "pointerTags"],
|
||||
},
|
||||
{
|
||||
name: "buildSubscriptionListEvent",
|
||||
fn: Schemas.buildSubscriptionListEvent,
|
||||
keys: ["encryption"],
|
||||
},
|
||||
{
|
||||
name: "buildBlockListEvent",
|
||||
fn: Schemas.buildBlockListEvent,
|
||||
keys: ["encryption"],
|
||||
},
|
||||
{
|
||||
name: "buildHashtagPreferenceEvent",
|
||||
fn: Schemas.buildHashtagPreferenceEvent,
|
||||
keys: [],
|
||||
},
|
||||
];
|
||||
|
||||
for (const { name, fn, keys } of buildFunctions) {
|
||||
await runFuzzer(name, fn, () => genParams(keys));
|
||||
}
|
||||
|
||||
// buildAdminListEvent needs special handling for 2 args
|
||||
await runFuzzer(
|
||||
"buildAdminListEvent",
|
||||
Schemas.buildAdminListEvent,
|
||||
() => {
|
||||
const listKey = Math.random() < 0.5 ? randomString(10) : (Math.random() < 0.5 ? "moderation" : "blacklist");
|
||||
const params = genParams(["hexPubkeys"])[0];
|
||||
return [listKey, params];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,140 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const ARTIFACTS_DIR = path.resolve(__dirname, "../../artifacts");
|
||||
const REPRODUCERS_DIR = path.resolve(__dirname, "../../examples/reproducers");
|
||||
|
||||
if (!fs.existsSync(ARTIFACTS_DIR)) {
|
||||
fs.mkdirSync(ARTIFACTS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(REPRODUCERS_DIR)) {
|
||||
fs.mkdirSync(REPRODUCERS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
export function randomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
export function randomBoolean() {
|
||||
return Math.random() < 0.5;
|
||||
}
|
||||
|
||||
export function randomString(length = 10, includeUnicode = false) {
|
||||
let result = "";
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const unicodeChars = "😀😎🌍🚀中文测试ñçüéåß∂ƒ©˙∆˚¬…æΩ≈ç√∫˜µ≤≥÷";
|
||||
const controlChars = "\x00\x01\x02\n\r\t\b\f\v";
|
||||
|
||||
// Use Array.from to correctly handle surrogate pairs
|
||||
const pool = Array.from(
|
||||
characters +
|
||||
(includeUnicode ? unicodeChars : "") +
|
||||
(includeUnicode ? controlChars : "")
|
||||
);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += pool[Math.floor(Math.random() * pool.length)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function randomHex(length = 64) {
|
||||
let result = "";
|
||||
const characters = "0123456789abcdef";
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function randomArray(generator, minLength = 0, maxLength = 10) {
|
||||
const length = randomInt(minLength, maxLength);
|
||||
const result = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
result.push(generator());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function randomValue(depth = 0, maxDepth = 3) {
|
||||
if (depth > maxDepth) {
|
||||
return randomString(5);
|
||||
}
|
||||
const type = randomInt(0, 5);
|
||||
switch (type) {
|
||||
case 0:
|
||||
return randomInt(-1000, 1000);
|
||||
case 1:
|
||||
return randomString(randomInt(0, 100), true);
|
||||
case 2:
|
||||
return randomBoolean();
|
||||
case 3:
|
||||
return null;
|
||||
case 4:
|
||||
return undefined;
|
||||
case 5:
|
||||
return randomObject(depth + 1, maxDepth);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function randomObject(depth = 0, maxDepth = 3) {
|
||||
if (depth > maxDepth) {
|
||||
return {};
|
||||
}
|
||||
const numKeys = randomInt(0, 5);
|
||||
const obj = {};
|
||||
for (let i = 0; i < numKeys; i++) {
|
||||
const key = randomString(5);
|
||||
obj[key] = randomValue(depth + 1, maxDepth);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export async function runFuzzer(targetName, fn, generator, iterations = 1000) {
|
||||
const reportPath = path.join(ARTIFACTS_DIR, `fuzz-report-${targetName}.json`);
|
||||
const failures = [];
|
||||
|
||||
console.log(`Starting fuzzer for ${targetName} with ${iterations} iterations...`);
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const inputs = generator();
|
||||
try {
|
||||
const result = await fn(...inputs);
|
||||
// Optional: Check if result is valid if we have a way to know
|
||||
} catch (error) {
|
||||
console.error(`Crash in ${targetName} at iteration ${i}:`, error.message);
|
||||
const failure = {
|
||||
iteration: i,
|
||||
inputs,
|
||||
error: {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name
|
||||
},
|
||||
};
|
||||
failures.push(failure);
|
||||
|
||||
// Save reproducer
|
||||
const reproducerPath = path.join(
|
||||
REPRODUCERS_DIR,
|
||||
targetName,
|
||||
`crash-${i}.json`
|
||||
);
|
||||
if (!fs.existsSync(path.dirname(reproducerPath))) {
|
||||
fs.mkdirSync(path.dirname(reproducerPath), { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(reproducerPath, JSON.stringify(inputs, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(reportPath, JSON.stringify(failures, null, 2));
|
||||
console.log(`Fuzzer finished. Found ${failures.length} failures. Report saved to ${reportPath}`);
|
||||
return failures;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const ARTIFACTS_DIR = path.resolve(__dirname, "../../artifacts");
|
||||
|
||||
const TARGETS = {
|
||||
nostrEventSchemas: [
|
||||
"sanitizeAdditionalTags",
|
||||
"validateEventStructure",
|
||||
"buildVideoPostEvent",
|
||||
"buildHttpAuthEvent",
|
||||
"buildReportEvent",
|
||||
"buildGiftWrapEvent",
|
||||
"buildSealEvent",
|
||||
"buildChatMessageEvent",
|
||||
"buildVideoMirrorEvent",
|
||||
"buildRepostEvent",
|
||||
"buildShareEvent",
|
||||
"buildRelayListEvent",
|
||||
"buildDmRelayListEvent",
|
||||
"buildProfileMetadataEvent",
|
||||
"buildMuteListEvent",
|
||||
"buildDeletionEvent",
|
||||
"buildLegacyDirectMessageEvent",
|
||||
"buildDmAttachmentEvent",
|
||||
"buildDmReadReceiptEvent",
|
||||
"buildDmTypingIndicatorEvent",
|
||||
"buildViewEvent",
|
||||
"buildZapRequestEvent",
|
||||
"buildReactionEvent",
|
||||
"buildCommentEvent",
|
||||
"buildWatchHistoryEvent",
|
||||
"buildSubscriptionListEvent",
|
||||
"buildBlockListEvent",
|
||||
"buildHashtagPreferenceEvent",
|
||||
"buildAdminListEvent",
|
||||
],
|
||||
dmDecryptor: ["decryptDM"],
|
||||
magnetUtils: [
|
||||
"safeDecodeMagnet",
|
||||
"extractBtihFromMagnet",
|
||||
"normalizeInfoHash",
|
||||
"normalizeAndAugmentMagnet",
|
||||
],
|
||||
};
|
||||
|
||||
function main() {
|
||||
for (const [target, functions] of Object.entries(TARGETS)) {
|
||||
const summary = {
|
||||
target,
|
||||
totalFailures: 0,
|
||||
details: {},
|
||||
};
|
||||
|
||||
for (const fn of functions) {
|
||||
const reportFile = path.join(ARTIFACTS_DIR, `fuzz-report-${fn}.json`);
|
||||
if (fs.existsSync(reportFile)) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(reportFile, "utf8"));
|
||||
summary.details[fn] = data;
|
||||
summary.totalFailures += data.length;
|
||||
} catch (e) {
|
||||
console.error(`Failed to read report for ${fn}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const summaryPath = path.join(ARTIFACTS_DIR, `fuzz-report-${target}.json`);
|
||||
fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
|
||||
console.log(`Generated summary for ${target} at ${summaryPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user