Files
bitvid/scripts/agent/fuzz-nostrEventSchemas.mjs
T
google-labs-jules[bot]andPR0M3TH3AN 932e57c724 Fuzz testing and hardening for nostrEventSchemas and magnetUtils
- Added fuzzing scripts for nostrEventSchemas, dmDecryptor, and magnetUtils.
- Fixed crashes in nostrEventSchemas related to unsafe stringification of content.
- Fixed crash in magnetUtils when options argument is null.
- Added safeStringify helper in nostrEventSchemas.js.
- Included fuzzing reports and reproducers.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-01-23 16:12:42 +00:00

249 lines
6.0 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import {
buildVideoPostEvent,
buildVideoMirrorEvent,
buildRepostEvent,
buildShareEvent,
buildRelayListEvent,
buildDmRelayListEvent,
buildProfileMetadataEvent,
buildMuteListEvent,
buildDeletionEvent,
buildLegacyDirectMessageEvent,
buildDmAttachmentEvent,
buildDmReadReceiptEvent,
buildDmTypingIndicatorEvent,
buildViewEvent,
buildZapRequestEvent,
buildReactionEvent,
buildCommentEvent,
buildWatchHistoryEvent,
buildSubscriptionListEvent,
buildBlockListEvent,
buildHashtagPreferenceEvent,
sanitizeAdditionalTags,
} from '../../js/nostrEventSchemas.js';
// Setup artifacts and reproducers
const ARTIFACTS_DIR = 'artifacts';
const REPRODUCERS_DIR = 'examples/reproducers';
const REPORT_FILE = path.join(ARTIFACTS_DIR, 'fuzz-report-nostrEventSchemas.json');
if (!fs.existsSync(ARTIFACTS_DIR)) fs.mkdirSync(ARTIFACTS_DIR, { recursive: true });
if (!fs.existsSync(REPRODUCERS_DIR)) fs.mkdirSync(REPRODUCERS_DIR, { recursive: true });
const BIG_NAUGHTY_LIST = [
undefined,
null,
0,
1,
-1,
NaN,
Infinity,
-Infinity,
true,
false,
'',
' ',
'\n',
'\t',
'\r',
'\0',
'a',
'0',
'[]',
'{}',
'null',
'undefined',
'NaN',
'Infinity',
'<>',
'\'',
'"',
'`',
'\\',
'/',
'.',
',',
':',
';',
'!',
'?',
'@',
'#',
'$',
'%',
'^',
'&',
'*',
'(',
')',
'-',
'_',
'+',
'=',
'[',
']',
'{',
'}',
'|',
'~',
'💩', // Emoji
'Hello 🌍', // Text with emoji
'Zalgo text: Hͥ̽ͣ̃ͤ́eͧ̈́lͥlͦo', // Zalgo
'\uD800', // Lone surrogate
'\uDFFF',
'\uD83D\uDCA9', // Valid surrogate pair (Pile of Poo)
'\u0000', // Null byte
'\uFFFF',
'\uFFFD', // Replacement character
Array(1000).fill('a').join(''), // Long string
{ toString: () => { throw new Error('Explosion!'); } }, // Object that throws on toString
{ toJSON: () => { throw new Error('JSON Explosion!'); } }, // Object that throws on toJSON
Object.create(null), // Null prototype object
[],
{},
[null],
[undefined],
[''],
{ a: 1 },
];
// Add a recursive object
const recursiveObj = {};
recursiveObj.self = recursiveObj;
BIG_NAUGHTY_LIST.push(recursiveObj);
const report = {
target: 'nostrEventSchemas',
timestamp: new Date().toISOString(),
failures: [],
};
function logFailure(funcName, args, error) {
const hash = Math.random().toString(36).substring(2, 10);
const reproDir = path.join(REPRODUCERS_DIR, `issue-${Date.now()}-${hash}`);
fs.mkdirSync(reproDir, { recursive: true });
const reproScript = `
import { ${funcName} } from '../../../js/nostrEventSchemas.js';
const args = ${JSON.stringify(args, (key, value) => {
if (value === undefined) return '__UNDEFINED__';
if (Number.isNaN(value)) return '__NAN__';
if (value === Infinity) return '__INFINITY__';
if (value === -Infinity) return '__NEG_INFINITY__';
if (typeof value === 'object' && value !== null) {
// Handle circular references for JSON stringify
const seen = new WeakSet();
const circularReplacer = (k, v) => {
if (typeof v === 'object' && v !== null) {
if (seen.has(v)) {
return '[Circular]';
}
seen.add(v);
}
return v;
};
// Simple circular check handling for top level if needed, but here we just try-catch or simplify
try {
return value;
} catch(e) {
return '[Unserializable]';
}
}
return value;
}, 2).replace(/"__UNDEFINED__"/g, 'undefined').replace(/"__NAN__"/g, 'NaN').replace(/"__INFINITY__"/g, 'Infinity').replace(/"__NEG_INFINITY__"/g, '-Infinity')};
console.log('Running reproduction for ${funcName}...');
try {
${funcName}(...args);
console.log('No crash reproduced.');
} catch (error) {
console.log('Crash reproduced:');
console.error(error);
}
`;
fs.writeFileSync(path.join(reproDir, 'repro.mjs'), reproScript);
fs.writeFileSync(path.join(reproDir, 'README.md'), `To run:\n\n\`\`\`bash\nnode --import ../../../tests/test-helpers/setup-localstorage.mjs repro.mjs\n\`\`\`\n\nError: ${error.message}\nStack: ${error.stack}`);
report.failures.push({
function: funcName,
args: args.map(a => {
try {
return JSON.stringify(a);
} catch {
return '[Circular/Unserializable]';
}
}),
error: {
message: error.message,
stack: error.stack,
},
reproDir,
});
console.error(`FAILURE in ${funcName}: ${error.message}`);
}
function fuzzFunction(func, funcName) {
console.log(`Fuzzing ${funcName}...`);
// Test 1: Single argument fuzzing
for (const input of BIG_NAUGHTY_LIST) {
try {
func(input);
} catch (error) {
logFailure(funcName, [input], error);
}
}
// Test 2: Object property fuzzing (since most accept a params object)
const baseParams = {};
for (const input of BIG_NAUGHTY_LIST) {
// Try to inject the naughty value into common keys
const keys = ['pubkey', 'created_at', 'content', 'tags', 'additionalTags', 'metadata', 'relays'];
for (const key of keys) {
try {
func({ ...baseParams, [key]: input });
} catch (error) {
logFailure(funcName, [{ [key]: input }], error);
}
}
}
}
const targets = {
buildVideoPostEvent,
buildVideoMirrorEvent,
buildRepostEvent,
buildShareEvent,
buildRelayListEvent,
buildDmRelayListEvent,
buildProfileMetadataEvent,
buildMuteListEvent,
buildDeletionEvent,
buildLegacyDirectMessageEvent,
buildDmAttachmentEvent,
buildDmReadReceiptEvent,
buildDmTypingIndicatorEvent,
buildViewEvent,
buildZapRequestEvent,
buildReactionEvent,
buildCommentEvent,
buildWatchHistoryEvent,
buildSubscriptionListEvent,
buildBlockListEvent,
buildHashtagPreferenceEvent,
sanitizeAdditionalTags,
};
for (const [name, func] of Object.entries(targets)) {
fuzzFunction(func, name);
}
fs.writeFileSync(REPORT_FILE, JSON.stringify(report, null, 2));
console.log(`Fuzzing complete. Report saved to ${REPORT_FILE}`);