mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #2464 from PR0M3TH3AN/agents/weekly/fuzz-agent/2026-02-13-weekly-run-17870958921723138324
chore(agents): run fuzz-agent weekly task — fuzz nostr schemas
This commit is contained in:
4 files changed
+313
No files matched your search
@@ -0,0 +1,18 @@
|
||||
# Context: Weekly Fuzz Agent Run (2026-02-13)
|
||||
|
||||
## Goal
|
||||
Improve input robustness by fuzzing high-risk parsers/decoders in `js/nostrEventSchemas.js`.
|
||||
|
||||
## Scope
|
||||
- Target: `js/nostrEventSchemas.js`
|
||||
- Task: Implement a fuzz harness to generate invalid inputs for event builder functions and validators.
|
||||
- Deliverables:
|
||||
- `scripts/agent/fuzz-nostr-schemas.mjs` (harness)
|
||||
- `examples/reproducers/` (if failures found)
|
||||
- `artifacts/fuzz-report-nostr-schemas-2026-02-13.json` (report)
|
||||
- Potential fixes in `js/nostrEventSchemas.js`
|
||||
|
||||
## Constraints
|
||||
- Do not fuzz public relays.
|
||||
- Keep crypto paths safe.
|
||||
- Minimal dependencies.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Agent Task Log Entry
|
||||
|
||||
- **Date:** 2026-02-13
|
||||
- **Agent:** fuzz-agent
|
||||
- **Prompt:** docs/agents/prompts/weekly/bitvid-fuzz-agent.md
|
||||
- **Status:** started
|
||||
- **Branch:** agents/weekly/fuzz-agent/2026-02-13-weekly-run
|
||||
- **Summary:** Task claimed — execution beginning
|
||||
@@ -0,0 +1,8 @@
|
||||
# Agent Task Log Entry
|
||||
|
||||
- **Date:** 2026-02-13
|
||||
- **Agent:** fuzz-agent
|
||||
- **Prompt:** docs/agents/prompts/weekly/bitvid-fuzz-agent.md
|
||||
- **Status:** completed
|
||||
- **Branch:** agents/weekly/fuzz-agent/2026-02-13-weekly-run
|
||||
- **Summary:** Fuzzed js/nostrEventSchemas.js with 52,000 randomized inputs; 0 failures found. Artifact generated.
|
||||
@@ -0,0 +1,279 @@
|
||||
import {
|
||||
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,
|
||||
validateEventStructure,
|
||||
NOTE_TYPES
|
||||
} from '../../js/nostrEventSchemas.js';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const REPRO_DIR = path.join(__dirname, '../../examples/reproducers');
|
||||
const ARTIFACTS_DIR = path.join(__dirname, '../../artifacts');
|
||||
|
||||
// Ensure directories exist
|
||||
if (!fs.existsSync(REPRO_DIR)) fs.mkdirSync(REPRO_DIR, { recursive: true });
|
||||
if (!fs.existsSync(ARTIFACTS_DIR)) fs.mkdirSync(ARTIFACTS_DIR, { recursive: true });
|
||||
|
||||
const TARGETS = {
|
||||
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,
|
||||
};
|
||||
|
||||
// Fuzzing Configuration
|
||||
const CONFIG = {
|
||||
iterations: 2000,
|
||||
seed: process.env.SEED || Date.now().toString(),
|
||||
maxDepth: 3,
|
||||
maxStringLength: 1024,
|
||||
timeoutMs: 5000 // Per function call
|
||||
};
|
||||
|
||||
console.log(`[Fuzz] Starting fuzz run with seed: ${CONFIG.seed}`);
|
||||
|
||||
// Random Generator
|
||||
class Random {
|
||||
constructor(seed) {
|
||||
this.state = BigInt(seed);
|
||||
}
|
||||
|
||||
next() {
|
||||
// Simple LCG
|
||||
this.state = (this.state * 6364136223846793005n + 1442695040888963407n) % 18446744073709551616n;
|
||||
return Number(this.state) / Number(18446744073709551616n);
|
||||
}
|
||||
|
||||
bool() { return this.next() > 0.5; }
|
||||
|
||||
int(min, max) {
|
||||
return Math.floor(this.next() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
pick(arr) {
|
||||
if (arr.length === 0) return undefined;
|
||||
return arr[this.int(0, arr.length - 1)];
|
||||
}
|
||||
|
||||
string(length = 10) {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+{}|:"<>?';
|
||||
let res = '';
|
||||
const len = this.int(0, length);
|
||||
for (let i = 0; i < len; i++) res += this.pick(chars);
|
||||
return res;
|
||||
}
|
||||
|
||||
unicodeString(length = 10) {
|
||||
let res = '';
|
||||
const len = this.int(0, length);
|
||||
for (let i = 0; i < len; i++) res += String.fromCodePoint(this.int(0, 0x10FFFF));
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
const rng = new Random(CONFIG.seed);
|
||||
|
||||
// Input Generators
|
||||
const GENERATORS = {
|
||||
null: () => null,
|
||||
undefined: () => undefined,
|
||||
number: () => rng.int(-10000, 10000),
|
||||
float: () => rng.next() * 10000,
|
||||
boolean: () => rng.bool(),
|
||||
string: () => rng.string(rng.int(0, 100)),
|
||||
longString: () => rng.string(rng.int(1000, CONFIG.maxStringLength)),
|
||||
unicodeString: () => rng.unicodeString(rng.int(0, 100)),
|
||||
emptyArray: () => [],
|
||||
emptyObject: () => ({}),
|
||||
|
||||
// Specific nasty inputs
|
||||
surrogatePair: () => '\uD800', // Unmatched surrogate
|
||||
constructorKey: () => 'constructor',
|
||||
prototypeKey: () => '__proto__',
|
||||
|
||||
// Composite generators
|
||||
array: (depth = 0) => {
|
||||
if (depth > CONFIG.maxDepth) return [];
|
||||
const len = rng.int(0, 5);
|
||||
const arr = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr.push(generateAny(depth + 1));
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
|
||||
object: (depth = 0) => {
|
||||
if (depth > CONFIG.maxDepth) return {};
|
||||
const len = rng.int(0, 5);
|
||||
const obj = {};
|
||||
for (let i = 0; i < len; i++) {
|
||||
obj[rng.string(5)] = generateAny(depth + 1);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
function generateAny(depth = 0) {
|
||||
const types = Object.keys(GENERATORS).filter(k => k !== 'array' && k !== 'object');
|
||||
// 20% chance of composite types if depth allows
|
||||
if (depth < CONFIG.maxDepth && rng.bool()) {
|
||||
types.push('array', 'object');
|
||||
}
|
||||
const type = rng.pick(types);
|
||||
return GENERATORS[type](depth);
|
||||
}
|
||||
|
||||
// Generate specifically malicious params for builder functions
|
||||
function generateParams() {
|
||||
// Sometimes return non-object
|
||||
if (rng.next() < 0.1) return generateAny();
|
||||
|
||||
const params = {};
|
||||
const keys = [
|
||||
'pubkey', 'created_at', 'content', 'additionalTags', 'tags', 'relays',
|
||||
'id', 'eventId', 'recipientPubkey', 'url', 'magnet', 'title', 'dTagValue',
|
||||
'metadata', 'reason', 'attachment', 'pTags'
|
||||
];
|
||||
|
||||
// Fuzz common keys
|
||||
keys.forEach(key => {
|
||||
if (rng.bool()) params[key] = generateAny();
|
||||
});
|
||||
|
||||
// Inject specific structure for additionalTags (common crash point: expecting array of arrays)
|
||||
if (rng.bool()) {
|
||||
params.additionalTags = rng.next() < 0.5
|
||||
? generateAny() // Random junk
|
||||
: [generateAny(), generateAny()]; // Array of junk
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
// Report
|
||||
const report = {
|
||||
target: 'nostrEventSchemas.js',
|
||||
seed: CONFIG.seed,
|
||||
iterations: CONFIG.iterations,
|
||||
startTime: new Date().toISOString(),
|
||||
failures: []
|
||||
};
|
||||
|
||||
// Runner
|
||||
function runFuzz() {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const [funcName, func] of Object.entries(TARGETS)) {
|
||||
console.log(`Fuzzing ${funcName}...`);
|
||||
for (let i = 0; i < CONFIG.iterations; i++) {
|
||||
const input = generateParams();
|
||||
|
||||
try {
|
||||
func(input);
|
||||
passed++;
|
||||
} catch (err) {
|
||||
failed++;
|
||||
console.error(`[FAILURE] ${funcName} crashed!`);
|
||||
console.error(err);
|
||||
|
||||
const caseId = `${funcName}-${Date.now()}-${i}`;
|
||||
const reproPath = path.join(REPRO_DIR, `fuzz-nostr-schemas-${new Date().toISOString().split('T')[0]}`, caseId);
|
||||
|
||||
fs.mkdirSync(reproPath, { recursive: true });
|
||||
|
||||
// Save Input
|
||||
fs.writeFileSync(path.join(reproPath, 'input.json'), JSON.stringify(input, null, 2));
|
||||
|
||||
// Save Repro Script
|
||||
const reproScript = `
|
||||
import { ${funcName} } from '../../../../../js/nostrEventSchemas.js';
|
||||
const input = ${JSON.stringify(input, null, 2)};
|
||||
try {
|
||||
${funcName}(input);
|
||||
console.log('Did not crash.');
|
||||
} catch (e) {
|
||||
console.error('Crashed:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
`;
|
||||
fs.writeFileSync(path.join(reproPath, 'repro.mjs'), reproScript);
|
||||
|
||||
report.failures.push({
|
||||
caseId,
|
||||
function: funcName,
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
reproPath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
report.endTime = new Date().toISOString();
|
||||
report.summary = { passed, failed };
|
||||
|
||||
// Write report
|
||||
const reportPath = path.join(ARTIFACTS_DIR, `fuzz-report-nostr-schemas-${new Date().toISOString().split('T')[0]}.json`);
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
|
||||
console.log(`[Fuzz] Finished. Passed: ${passed}, Failed: ${failed}`);
|
||||
console.log(`[Fuzz] Report saved to ${reportPath}`);
|
||||
|
||||
if (failed > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
runFuzz();
|
||||
Reference in new issue
Block a user