mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-05 12:22:28 +00:00
feat(fuzz): add input robustness testing harnesses and fixes
- Add fuzz testing scripts for `nostrEventSchemas.js`, `dmDecryptor.js`, and `magnetUtils.js` in `scripts/agent/`. - Fix `build...` functions in `nostrEventSchemas.js` to handle `null`/`undefined` inputs. - Fix `buildVideoPostEvent` to gracefully handle circular references in content serialization. - Fix `normalizeAndAugmentMagnet` in `magnetUtils.js` to handle non-iterable `extraTrackers`. - Include minimal reproducers for discovered crashes in `examples/reproducers/`.
This commit is contained in:
1 parent
e4851e274a
commit
ec5fc23164
7 files changed
+686
-111
No files matched your search
@@ -0,0 +1,23 @@
|
||||
# Fuzz Testing Reproducers
|
||||
|
||||
This directory contains reproducers for crashes found during fuzz testing.
|
||||
These files contain the input that caused a crash and the error message.
|
||||
|
||||
## How to run
|
||||
|
||||
The reproducers are JSON files. To re-run a specific case, you can use the fuzz harness and load the JSON input, or manually import the function and call it with the input.
|
||||
|
||||
Example structure:
|
||||
```json
|
||||
{
|
||||
"target": "functionName",
|
||||
"input": { ... },
|
||||
"error": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## Known Issues
|
||||
|
||||
- `buildVideoPostEvent` crashes on circular objects if not handled (Fixed).
|
||||
- `normalizeAndAugmentMagnet` crashes on non-iterable `extraTrackers` (Fixed).
|
||||
- `build...` functions crash on `null`/`undefined` input (Fixed).
|
||||
+3
-1
@@ -25,6 +25,8 @@ export function normalizeAndAugmentMagnet(
|
||||
appProtocol,
|
||||
} = {}
|
||||
) {
|
||||
const safeExtraTrackers = Array.isArray(extraTrackers) ? extraTrackers : [];
|
||||
|
||||
const {
|
||||
initial,
|
||||
canonicalValue,
|
||||
@@ -49,7 +51,7 @@ export function normalizeAndAugmentMagnet(
|
||||
|
||||
let didChange = didMutate;
|
||||
|
||||
if (ensureTrackers(params, [...WSS_TRACKERS, ...extraTrackers])) {
|
||||
if (ensureTrackers(params, [...WSS_TRACKERS, ...safeExtraTrackers])) {
|
||||
didChange = true;
|
||||
}
|
||||
|
||||
|
||||
+138
-110
@@ -687,13 +687,14 @@ function mergePointerTags(pointerTags = []) {
|
||||
return merged;
|
||||
}
|
||||
|
||||
export function buildVideoPostEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
dTagValue,
|
||||
content,
|
||||
additionalTags = [],
|
||||
}) {
|
||||
export function buildVideoPostEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
dTagValue,
|
||||
content,
|
||||
additionalTags = [],
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.VIDEO_POST);
|
||||
const tags = [];
|
||||
if (schema?.topicTag?.name && schema?.topicTag?.value) {
|
||||
@@ -708,21 +709,33 @@ export function buildVideoPostEvent({
|
||||
tags.push(...sanitizedAdditionalTags.map((tag) => tag.slice()));
|
||||
}
|
||||
|
||||
let serializedContent = "";
|
||||
if (typeof content === "string") {
|
||||
serializedContent = content;
|
||||
} else {
|
||||
try {
|
||||
serializedContent = JSON.stringify(content ?? {});
|
||||
} catch (error) {
|
||||
serializedContent = "{}";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
kind: schema?.kind ?? 30078,
|
||||
pubkey,
|
||||
created_at,
|
||||
tags,
|
||||
content: typeof content === "string" ? content : JSON.stringify(content ?? {}),
|
||||
content: serializedContent,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildVideoMirrorEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
tags = [],
|
||||
content = "",
|
||||
}) {
|
||||
export function buildVideoMirrorEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
tags = [],
|
||||
content = "",
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.VIDEO_MIRROR);
|
||||
const combinedTags = [];
|
||||
appendSchemaTags(combinedTags, schema);
|
||||
@@ -742,20 +755,21 @@ export function buildVideoMirrorEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildRepostEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
eventId = "",
|
||||
eventRelay = "",
|
||||
address = "",
|
||||
addressRelay = "",
|
||||
authorPubkey = "",
|
||||
additionalTags = [],
|
||||
repostKind,
|
||||
targetKind,
|
||||
targetEvent = null,
|
||||
serializedEvent = "",
|
||||
}) {
|
||||
export function buildRepostEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
eventId = "",
|
||||
eventRelay = "",
|
||||
address = "",
|
||||
addressRelay = "",
|
||||
authorPubkey = "",
|
||||
additionalTags = [],
|
||||
repostKind,
|
||||
targetKind,
|
||||
targetEvent = null,
|
||||
serializedEvent = "",
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.REPOST);
|
||||
const tags = [];
|
||||
|
||||
@@ -856,12 +870,13 @@ export function buildRepostEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildRelayListEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
relays = [],
|
||||
additionalTags = [],
|
||||
}) {
|
||||
export function buildRelayListEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
relays = [],
|
||||
additionalTags = [],
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.RELAY_LIST);
|
||||
const tags = [];
|
||||
const relayTagName = schema?.relayTagName || "r";
|
||||
@@ -926,17 +941,18 @@ export function buildRelayListEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildViewEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
pointerValue,
|
||||
pointerTag,
|
||||
pointerTags = [],
|
||||
dedupeTag,
|
||||
includeSessionTag = false,
|
||||
additionalTags = [],
|
||||
content = "",
|
||||
}) {
|
||||
export function buildViewEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
pointerValue,
|
||||
pointerTag,
|
||||
pointerTags = [],
|
||||
dedupeTag,
|
||||
includeSessionTag = false,
|
||||
additionalTags = [],
|
||||
content = "",
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.VIEW_EVENT);
|
||||
const tags = [];
|
||||
if (schema?.topicTag?.name && schema?.topicTag?.value) {
|
||||
@@ -986,17 +1002,18 @@ export function buildViewEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildReactionEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
pointerValue,
|
||||
pointerTag,
|
||||
pointerTags = [],
|
||||
targetPointer = null,
|
||||
targetAuthorPubkey = "",
|
||||
additionalTags = [],
|
||||
content = "",
|
||||
}) {
|
||||
export function buildReactionEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
pointerValue,
|
||||
pointerTag,
|
||||
pointerTags = [],
|
||||
targetPointer = null,
|
||||
targetAuthorPubkey = "",
|
||||
additionalTags = [],
|
||||
content = "",
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.VIDEO_REACTION);
|
||||
const tags = [];
|
||||
|
||||
@@ -1117,30 +1134,31 @@ export function buildReactionEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildCommentEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
videoEventId = "",
|
||||
videoEventRelay = "",
|
||||
videoDefinitionAddress = "",
|
||||
videoDefinitionRelay = "",
|
||||
rootIdentifier = "",
|
||||
rootIdentifierRelay = "",
|
||||
parentCommentId = "",
|
||||
parentCommentRelay = "",
|
||||
threadParticipantPubkey = "",
|
||||
threadParticipantRelay = "",
|
||||
rootKind,
|
||||
rootAuthorPubkey = "",
|
||||
rootAuthorRelay = "",
|
||||
parentKind,
|
||||
parentAuthorPubkey = "",
|
||||
parentAuthorRelay = "",
|
||||
parentIdentifier = "",
|
||||
parentIdentifierRelay = "",
|
||||
additionalTags = [],
|
||||
content = "",
|
||||
}) {
|
||||
export function buildCommentEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
videoEventId = "",
|
||||
videoEventRelay = "",
|
||||
videoDefinitionAddress = "",
|
||||
videoDefinitionRelay = "",
|
||||
rootIdentifier = "",
|
||||
rootIdentifierRelay = "",
|
||||
parentCommentId = "",
|
||||
parentCommentRelay = "",
|
||||
threadParticipantPubkey = "",
|
||||
threadParticipantRelay = "",
|
||||
rootKind,
|
||||
rootAuthorPubkey = "",
|
||||
rootAuthorRelay = "",
|
||||
parentKind,
|
||||
parentAuthorPubkey = "",
|
||||
parentAuthorRelay = "",
|
||||
parentIdentifier = "",
|
||||
parentIdentifierRelay = "",
|
||||
additionalTags = [],
|
||||
content = "",
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.VIDEO_COMMENT);
|
||||
const tags = [];
|
||||
|
||||
@@ -1361,13 +1379,14 @@ export function buildCommentEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildWatchHistoryEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
monthIdentifier,
|
||||
pointerTags = [],
|
||||
content,
|
||||
}) {
|
||||
export function buildWatchHistoryEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
monthIdentifier,
|
||||
pointerTags = [],
|
||||
content,
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.WATCH_HISTORY);
|
||||
const tags = [];
|
||||
const identifierName = schema?.identifierTag?.name || "d";
|
||||
@@ -1400,12 +1419,13 @@ export function buildWatchHistoryEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSubscriptionListEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
encryption,
|
||||
}) {
|
||||
export function buildSubscriptionListEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
encryption,
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.SUBSCRIPTION_LIST);
|
||||
const tags = [];
|
||||
const identifierName = schema?.identifierTag?.name || "d";
|
||||
@@ -1428,12 +1448,13 @@ export function buildSubscriptionListEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildBlockListEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
encryption,
|
||||
}) {
|
||||
export function buildBlockListEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
encryption,
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.USER_BLOCK_LIST);
|
||||
const tags = [];
|
||||
const identifierName = schema?.identifierTag?.name || "d";
|
||||
@@ -1456,11 +1477,12 @@ export function buildBlockListEvent({
|
||||
};
|
||||
}
|
||||
|
||||
export function buildHashtagPreferenceEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
}) {
|
||||
export function buildHashtagPreferenceEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.HASHTAG_PREFERENCES);
|
||||
const tags = [];
|
||||
const identifierName = schema?.identifierTag?.name || "d";
|
||||
@@ -1492,7 +1514,12 @@ function resolveAdminNoteType(listKey) {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAdminListEvent(listKey, { pubkey, created_at, hexPubkeys = [] }) {
|
||||
export function buildAdminListEvent(listKey, params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
hexPubkeys = []
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(resolveAdminNoteType(listKey));
|
||||
if (!schema) {
|
||||
devLogger.warn(
|
||||
@@ -1527,11 +1554,12 @@ export function buildAdminListEvent(listKey, { pubkey, created_at, hexPubkeys =
|
||||
};
|
||||
}
|
||||
|
||||
export function buildStorageChallengeEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
challenge,
|
||||
}) {
|
||||
export function buildStorageChallengeEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
challenge,
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.STORAGE_CHALLENGE);
|
||||
const tags = [];
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
import { decryptDM } from '../../js/dmDecryptor.js';
|
||||
import { getRandomFuzzInput, randomHex } from './fuzz-utils.mjs';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const REPRODUCERS_DIR = 'examples/reproducers/dmDecryptor';
|
||||
fs.mkdirSync(REPRODUCERS_DIR, { recursive: true });
|
||||
|
||||
const failures = [];
|
||||
|
||||
function recordFailure(target, input, error) {
|
||||
const id = Date.now() + Math.random().toString(36).substring(7);
|
||||
const filename = path.join(REPRODUCERS_DIR, `${target}-${id}.json`);
|
||||
|
||||
// Handle circular input for logging
|
||||
let safeInput = input;
|
||||
try {
|
||||
JSON.stringify(input);
|
||||
} catch {
|
||||
safeInput = "[Circular or non-serializable input]";
|
||||
}
|
||||
|
||||
const report = {
|
||||
target,
|
||||
input: safeInput,
|
||||
error: {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
};
|
||||
|
||||
fs.writeFileSync(filename, JSON.stringify(report, null, 2));
|
||||
failures.push(report);
|
||||
console.error(`[FAIL] ${target}: ${error.message} (Report saved to ${filename})`);
|
||||
}
|
||||
|
||||
function runFuzz(name, fn, inputGenerator, iterations = 1000) {
|
||||
console.log(`Fuzzing ${name} for ${iterations} iterations...`);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const input = inputGenerator();
|
||||
try {
|
||||
fn(input);
|
||||
} catch (error) {
|
||||
recordFailure(name, input, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock decryptor
|
||||
const mockDecryptor = {
|
||||
scheme: 'nip44_v2',
|
||||
decrypt: async (pubkey, ciphertext) => {
|
||||
// sometimes succeed, sometimes fail
|
||||
if (Math.random() > 0.5) {
|
||||
return JSON.stringify({ content: 'decrypted', pubkey: randomHex(), created_at: Date.now()/1000 });
|
||||
} else {
|
||||
throw new Error('Decrypt failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const throwingDecryptor = {
|
||||
scheme: 'nip44',
|
||||
decrypt: async () => { throw new Error("Hard fail"); }
|
||||
}
|
||||
|
||||
const wrapper = async (input) => {
|
||||
// input is fuzzy junk.
|
||||
// We construct a call to decryptDM(event, context)
|
||||
|
||||
let event = input;
|
||||
let context = {
|
||||
actorPubkey: randomHex(),
|
||||
decryptors: [mockDecryptor, throwingDecryptor]
|
||||
};
|
||||
|
||||
if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
|
||||
// If input is an object, treat it as the event structure, but potentially corrupted
|
||||
event = {
|
||||
kind: input.kind !== undefined ? input.kind : 4,
|
||||
content: input.content || 'ciphertext',
|
||||
pubkey: input.pubkey || randomHex(),
|
||||
tags: input.tags || [['p', randomHex()]],
|
||||
created_at: input.created_at || Date.now()/1000,
|
||||
...input
|
||||
};
|
||||
|
||||
// Also fuzz context sometimes
|
||||
if (Math.random() > 0.8) {
|
||||
context = input.context || getRandomFuzzInput();
|
||||
}
|
||||
}
|
||||
|
||||
await decryptDM(event, context);
|
||||
};
|
||||
|
||||
runFuzz('decryptDM', wrapper, getRandomFuzzInput);
|
||||
|
||||
// Write summary
|
||||
fs.writeFileSync('artifacts/fuzz-report-dmDecryptor.json', JSON.stringify(failures, null, 2));
|
||||
if (failures.length > 0) {
|
||||
console.log(`\nFuzzing finished with ${failures.length} failures.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\nFuzzing finished with 0 failures.");
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
import { normalizeAndAugmentMagnet } from '../../js/magnetUtils.js';
|
||||
import { getRandomFuzzInput } from './fuzz-utils.mjs';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const REPRODUCERS_DIR = 'examples/reproducers/magnetUtils';
|
||||
fs.mkdirSync(REPRODUCERS_DIR, { recursive: true });
|
||||
|
||||
const failures = [];
|
||||
|
||||
function recordFailure(target, input, error) {
|
||||
const id = Date.now() + Math.random().toString(36).substring(7);
|
||||
const filename = path.join(REPRODUCERS_DIR, `${target}-${id}.json`);
|
||||
|
||||
// Handle circular input for logging
|
||||
let safeInput = input;
|
||||
try {
|
||||
JSON.stringify(input);
|
||||
} catch {
|
||||
safeInput = "[Circular or non-serializable input]";
|
||||
}
|
||||
|
||||
const report = {
|
||||
target,
|
||||
input: safeInput,
|
||||
error: {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
};
|
||||
|
||||
fs.writeFileSync(filename, JSON.stringify(report, null, 2));
|
||||
failures.push(report);
|
||||
console.error(`[FAIL] ${target}: ${error.message} (Report saved to ${filename})`);
|
||||
}
|
||||
|
||||
function runFuzz(name, fn, inputGenerator, iterations = 1000) {
|
||||
console.log(`Fuzzing ${name} for ${iterations} iterations...`);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const input = inputGenerator();
|
||||
try {
|
||||
fn(input);
|
||||
} catch (error) {
|
||||
recordFailure(name, input, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapper = (input) => {
|
||||
// input is fuzzy junk.
|
||||
// normalizeAndAugmentMagnet(rawValue, options)
|
||||
|
||||
let rawValue = input;
|
||||
let options = {};
|
||||
|
||||
if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
|
||||
rawValue = input.rawValue || getRandomFuzzInput();
|
||||
options = input.options || {
|
||||
webSeed: getRandomFuzzInput(),
|
||||
torrentUrl: getRandomFuzzInput(),
|
||||
xs: getRandomFuzzInput(),
|
||||
extraTrackers: getRandomFuzzInput()
|
||||
};
|
||||
}
|
||||
|
||||
normalizeAndAugmentMagnet(rawValue, options);
|
||||
};
|
||||
|
||||
runFuzz('normalizeAndAugmentMagnet', wrapper, getRandomFuzzInput);
|
||||
|
||||
// Write summary
|
||||
fs.writeFileSync('artifacts/fuzz-report-magnetUtils.json', JSON.stringify(failures, null, 2));
|
||||
if (failures.length > 0) {
|
||||
console.log(`\nFuzzing finished with ${failures.length} failures.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\nFuzzing finished with 0 failures.");
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
|
||||
import {
|
||||
buildVideoPostEvent,
|
||||
buildVideoMirrorEvent,
|
||||
buildRepostEvent,
|
||||
buildRelayListEvent,
|
||||
buildViewEvent,
|
||||
buildReactionEvent,
|
||||
buildCommentEvent,
|
||||
buildWatchHistoryEvent,
|
||||
buildSubscriptionListEvent,
|
||||
buildBlockListEvent,
|
||||
buildHashtagPreferenceEvent,
|
||||
buildAdminListEvent,
|
||||
buildStorageChallengeEvent,
|
||||
sanitizeAdditionalTags,
|
||||
getNostrEventSchema
|
||||
} from '../../js/nostrEventSchemas.js';
|
||||
import { getRandomFuzzInput, randomString, randomHex } from './fuzz-utils.mjs';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const REPRODUCERS_DIR = 'examples/reproducers/nostrEventSchemas';
|
||||
fs.mkdirSync(REPRODUCERS_DIR, { recursive: true });
|
||||
|
||||
const failures = [];
|
||||
|
||||
function recordFailure(target, input, error) {
|
||||
const id = Date.now() + Math.random().toString(36).substring(7);
|
||||
const filename = path.join(REPRODUCERS_DIR, `${target}-${id}.json`);
|
||||
|
||||
// Handle circular input for logging
|
||||
let safeInput = input;
|
||||
try {
|
||||
JSON.stringify(input);
|
||||
} catch {
|
||||
safeInput = "[Circular or non-serializable input]";
|
||||
}
|
||||
|
||||
const report = {
|
||||
target,
|
||||
input: safeInput,
|
||||
error: {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
};
|
||||
|
||||
fs.writeFileSync(filename, JSON.stringify(report, null, 2));
|
||||
failures.push(report);
|
||||
console.error(`[FAIL] ${target}: ${error.message} (Report saved to ${filename})`);
|
||||
}
|
||||
|
||||
function runFuzz(name, fn, inputGenerator, iterations = 1000) {
|
||||
console.log(`Fuzzing ${name} for ${iterations} iterations...`);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const input = inputGenerator();
|
||||
try {
|
||||
fn(input);
|
||||
} catch (error) {
|
||||
recordFailure(name, input, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap functions that take multiple arguments or objects
|
||||
const wrappers = {
|
||||
buildVideoPostEvent: (input) => {
|
||||
// Input is expected to be an object, but we fuzz it with anything.
|
||||
// If it is an object, we fuzz its properties too.
|
||||
if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
|
||||
buildVideoPostEvent({
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput(),
|
||||
dTagValue: input.dTagValue || getRandomFuzzInput(),
|
||||
content: input.content || getRandomFuzzInput(),
|
||||
additionalTags: input.additionalTags || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildVideoPostEvent(input);
|
||||
}
|
||||
},
|
||||
buildVideoMirrorEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildVideoMirrorEvent({
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput(),
|
||||
tags: input.tags || getRandomFuzzInput(),
|
||||
content: input.content || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildVideoMirrorEvent(input);
|
||||
}
|
||||
},
|
||||
buildRepostEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildRepostEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildRepostEvent(input);
|
||||
}
|
||||
},
|
||||
buildRelayListEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildRelayListEvent({
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput(),
|
||||
relays: input.relays || getRandomFuzzInput(),
|
||||
additionalTags: input.additionalTags || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildRelayListEvent(input);
|
||||
}
|
||||
},
|
||||
buildViewEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildViewEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildViewEvent(input);
|
||||
}
|
||||
},
|
||||
buildReactionEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildReactionEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildReactionEvent(input);
|
||||
}
|
||||
},
|
||||
buildCommentEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildCommentEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildCommentEvent(input);
|
||||
}
|
||||
},
|
||||
buildWatchHistoryEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildWatchHistoryEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildWatchHistoryEvent(input);
|
||||
}
|
||||
},
|
||||
buildSubscriptionListEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildSubscriptionListEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildSubscriptionListEvent(input);
|
||||
}
|
||||
},
|
||||
buildBlockListEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildBlockListEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildBlockListEvent(input);
|
||||
}
|
||||
},
|
||||
buildHashtagPreferenceEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildHashtagPreferenceEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildHashtagPreferenceEvent(input);
|
||||
}
|
||||
},
|
||||
buildAdminListEvent: (input) => {
|
||||
// expects listKey and params
|
||||
const key = getRandomFuzzInput();
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildAdminListEvent(key, {
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildAdminListEvent(key, input);
|
||||
}
|
||||
},
|
||||
buildStorageChallengeEvent: (input) => {
|
||||
if (typeof input === 'object' && input !== null) {
|
||||
buildStorageChallengeEvent({
|
||||
...input,
|
||||
pubkey: input.pubkey || getRandomFuzzInput(),
|
||||
created_at: input.created_at || getRandomFuzzInput()
|
||||
});
|
||||
} else {
|
||||
buildStorageChallengeEvent(input);
|
||||
}
|
||||
},
|
||||
sanitizeAdditionalTags: (input) => {
|
||||
sanitizeAdditionalTags(input);
|
||||
},
|
||||
getNostrEventSchema: (input) => {
|
||||
getNostrEventSchema(input);
|
||||
}
|
||||
};
|
||||
|
||||
// Main execution
|
||||
Object.entries(wrappers).forEach(([name, fn]) => {
|
||||
runFuzz(name, fn, getRandomFuzzInput);
|
||||
});
|
||||
|
||||
// Write summary
|
||||
fs.writeFileSync('artifacts/fuzz-report-nostrEventSchemas.json', JSON.stringify(failures, null, 2));
|
||||
if (failures.length > 0) {
|
||||
console.log(`\nFuzzing finished with ${failures.length} failures.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\nFuzzing finished with 0 failures.");
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
export function randomString(length = 100) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/`~';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function randomUnicodeString(length = 100) {
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
// Random printable unicode range (roughly)
|
||||
result += String.fromCharCode(Math.floor(Math.random() * 0xFFFF));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function randomSurrogateString(length = 100) {
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
// Generate random surrogates
|
||||
const code = Math.random() > 0.5
|
||||
? Math.floor(0xD800 + Math.random() * (0xDBFF - 0xD800 + 1))
|
||||
: Math.floor(0xDC00 + Math.random() * (0xDFFF - 0xDC00 + 1));
|
||||
result += String.fromCharCode(code);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function randomInt(max = 10000) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
export function randomBool() {
|
||||
return Math.random() > 0.5;
|
||||
}
|
||||
|
||||
export function randomObject(depth = 2) {
|
||||
if (depth <= 0) return randomString(10);
|
||||
const obj = {};
|
||||
const keys = randomInt(5) + 1;
|
||||
for (let i = 0; i < keys; i++) {
|
||||
obj[randomString(5)] = Math.random() > 0.5 ? randomString(10) : randomObject(depth - 1);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function randomHex(length = 64) {
|
||||
return randomBytes(length / 2).toString('hex');
|
||||
}
|
||||
|
||||
export function pickRandom(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
export function fuzzGenerators() {
|
||||
const generators = [
|
||||
() => null,
|
||||
() => undefined,
|
||||
() => "",
|
||||
() => " ",
|
||||
() => 0,
|
||||
() => 1,
|
||||
() => -1,
|
||||
() => NaN,
|
||||
() => Infinity,
|
||||
() => -Infinity,
|
||||
() => [],
|
||||
() => [null],
|
||||
() => {},
|
||||
() => randomString(1000),
|
||||
() => randomUnicodeString(100),
|
||||
() => randomSurrogateString(20),
|
||||
() => randomObject(),
|
||||
() => randomHex(64),
|
||||
() => "magnet:?xt=urn:btih:" + randomHex(40),
|
||||
() => "https://" + randomString(20) + ".com",
|
||||
() => {
|
||||
const obj = {};
|
||||
obj.self = obj;
|
||||
return obj; // Circular
|
||||
}
|
||||
];
|
||||
return generators;
|
||||
}
|
||||
|
||||
export function getRandomFuzzInput() {
|
||||
const gens = fuzzGenerators();
|
||||
return pickRandom(gens)();
|
||||
}
|
||||
Reference in new issue
Block a user