fase(6): fuzzing module complete

This commit is contained in:
debian
2026-03-05 09:22:55 -05:00
parent d62bd615bf
commit e746dc0497
46 changed files with 1727 additions and 35 deletions

View File

@@ -0,0 +1,319 @@
/**
* Unit tests for the fuzzing module.
*/
import { FuzzSession } from '../../src/modules/fuzzing/domain/entities/FuzzSession';
import { FuzzResult } from '../../src/modules/fuzzing/domain/entities/FuzzResult';
import { FuzzIntensity } from '../../src/modules/fuzzing/domain/value-objects/FuzzIntensity';
import { FuzzStrategy } from '../../src/modules/fuzzing/domain/value-objects/FuzzStrategy';
import { Seed } from '../../src/modules/fuzzing/domain/value-objects/Seed';
import { FuzzPayload } from '../../src/modules/fuzzing/domain/value-objects/FuzzPayload';
import { EmptyValueStrategy } from '../../src/modules/fuzzing/infrastructure/strategies/EmptyValueStrategy';
import { OversizedStringStrategy } from '../../src/modules/fuzzing/infrastructure/strategies/OversizedStringStrategy';
import { SpecialCharsStrategy } from '../../src/modules/fuzzing/infrastructure/strategies/SpecialCharsStrategy';
import { TypeMismatchStrategy } from '../../src/modules/fuzzing/infrastructure/strategies/TypeMismatchStrategy';
import { BoundaryValueStrategy } from '../../src/modules/fuzzing/infrastructure/strategies/BoundaryValueStrategy';
import { FuzzingEngineAdapter } from '../../src/modules/fuzzing/infrastructure/adapters/FuzzingEngineAdapter';
import { detectInputType } from '../../src/modules/fuzzing/infrastructure/adapters/InputTypeDetector';
import { IState } from '../../src/core/interfaces';
function makeState(domSnapshot = ''): IState {
return {
id: 'state1',
url: 'http://test.com',
title: 'Test',
timestamp: Date.now(),
domSnapshot,
visitCount: 1,
};
}
// ─── Value Objects ────────────────────────────────────────────────────────────
describe('FuzzIntensity', () => {
it('creates low intensity', () => {
expect(FuzzIntensity.low().value).toBe('low');
});
it('creates from string', () => {
expect(FuzzIntensity.fromString('high').value).toBe('high');
});
it('throws for invalid intensity', () => {
expect(() => FuzzIntensity.fromString('extreme')).toThrow();
});
it('equality works', () => {
expect(FuzzIntensity.medium().equals(FuzzIntensity.medium())).toBe(true);
expect(FuzzIntensity.low().equals(FuzzIntensity.high())).toBe(false);
});
});
describe('FuzzStrategy', () => {
it('creates all strategies', () => {
expect(FuzzStrategy.empty().value).toBe('empty');
expect(FuzzStrategy.specialChars().value).toBe('special_chars');
expect(FuzzStrategy.boundary().value).toBe('boundary');
});
it('throws for invalid strategy', () => {
expect(() => FuzzStrategy.fromString('invalid')).toThrow();
});
});
describe('Seed', () => {
it('creates valid seed', () => {
expect(Seed.create(42).value).toBe(42);
});
it('throws for negative seed', () => {
expect(() => Seed.create(-1)).toThrow();
});
it('throws for non-integer', () => {
expect(() => Seed.create(1.5)).toThrow();
});
it('creates from timestamp', () => {
const seed = Seed.fromTimestamp();
expect(seed.value).toBeGreaterThan(0);
});
});
describe('FuzzPayload', () => {
it('creates payload with value and strategy', () => {
const p = FuzzPayload.create("' OR 1=1 --", 'special_chars');
expect(p.value).toBe("' OR 1=1 --");
expect(p.strategy).toBe('special_chars');
});
});
// ─── FuzzSession Aggregate ───────────────────────────────────────────────────
describe('FuzzSession', () => {
it('creates a fuzz session with domain event', () => {
const result = FuzzSession.create({
crawlSessionId: 'crawl-1',
intensity: 'medium',
seed: 12345,
});
if (!result.ok) throw new Error('Expected Ok');
const session = result.value;
expect(session.intensity.value).toBe('medium');
expect(session.seed.value).toBe(12345);
expect(session.status).toBe('running');
expect(session.actionsExecuted).toBe(0);
expect(session.domainEvents).toHaveLength(1);
expect(session.domainEvents[0]!.eventName).toBe('fuzz.started');
});
it('fails with invalid intensity', () => {
const result = FuzzSession.create({
crawlSessionId: 'crawl-1',
intensity: 'extreme' as 'low',
seed: 1,
});
expect(result.ok).toBe(false);
});
it('fails with negative seed', () => {
const result = FuzzSession.create({
crawlSessionId: 'crawl-1',
intensity: 'low',
seed: -1,
});
expect(result.ok).toBe(false);
});
it('tracks vulnerability detection', () => {
const r1 = FuzzSession.create({ crawlSessionId: 'c1', intensity: 'high', seed: 1 });
if (!r1.ok) throw new Error('Expected Ok');
const session = r1.value;
session.clearEvents();
const fuzzResult = FuzzResult.create({
sessionId: session.id.toString(),
stateId: 'state1',
selector: '#email',
payload: "' OR 1=1 --",
strategy: 'special_chars',
anomalyType: 'xss_reflection',
severity: 'critical',
description: 'XSS reflected',
});
session.recordVulnerability(fuzzResult);
expect(session.vulnerabilitiesFound).toBe(1);
expect(session.actionsExecuted).toBe(1);
expect(session.domainEvents[0]!.eventName).toBe('fuzz.vulnerability_detected');
});
it('completes session with event', () => {
const r2 = FuzzSession.create({ crawlSessionId: 'c1', intensity: 'low', seed: 1 });
if (!r2.ok) throw new Error('Expected Ok');
const session = r2.value;
session.clearEvents();
session.complete();
expect(session.status).toBe('completed');
expect(session.completedAt).toBeDefined();
expect(session.domainEvents[0]!.eventName).toBe('fuzz.completed');
});
});
// ─── Strategies ───────────────────────────────────────────────────────────────
describe('EmptyValueStrategy', () => {
const s = new EmptyValueStrategy();
it('applies to all types', () => {
expect(s.appliesTo('email')).toBe(true);
expect(s.appliesTo('number')).toBe(true);
expect(s.appliesTo('select')).toBe(true);
});
it('returns empty/whitespace values', () => {
const vals = s.values();
expect(vals).toContain('');
expect(vals).toContain(' ');
expect(vals).toContain('\t');
});
});
describe('OversizedStringStrategy', () => {
it('applies to text, email, password, textarea', () => {
const s = new OversizedStringStrategy('medium');
expect(s.appliesTo('text')).toBe(true);
expect(s.appliesTo('email')).toBe(true);
expect(s.appliesTo('number')).toBe(false);
});
it('low = 256 chars', () => {
const s = new OversizedStringStrategy('low');
expect(s.values()[0]?.length).toBe(256);
});
it('medium = 1024 chars', () => {
const s = new OversizedStringStrategy('medium');
expect(s.values()[0]?.length).toBe(1024);
});
it('high = 10000+ chars', () => {
const s = new OversizedStringStrategy('high');
expect(s.values()[0]!.length).toBeGreaterThan(10000);
});
});
describe('SpecialCharsStrategy', () => {
const s = new SpecialCharsStrategy();
it('applies to text, email, search, textarea', () => {
expect(s.appliesTo('text')).toBe(true);
expect(s.appliesTo('email')).toBe(true);
expect(s.appliesTo('number')).toBe(false);
});
it('includes SQL injection payload', () => {
expect(s.values()).toContain("' OR 1=1 --");
});
it('includes XSS payload', () => {
expect(s.values()).toContain('<script>alert(1)</script>');
});
});
describe('TypeMismatchStrategy', () => {
const s = new TypeMismatchStrategy();
it('applies to typed fields', () => {
expect(s.appliesTo('email')).toBe(true);
expect(s.appliesTo('number')).toBe(true);
expect(s.appliesTo('text')).toBe(false);
});
it('returns mismatch for email', () => {
expect(s.values('email')).toContain('not-an-email');
});
it('returns mismatch for number', () => {
expect(s.values('number')).toContain('abc');
});
});
describe('BoundaryValueStrategy', () => {
const s = new BoundaryValueStrategy();
it('applies to number and date only', () => {
expect(s.appliesTo('number')).toBe(true);
expect(s.appliesTo('date')).toBe(true);
expect(s.appliesTo('text')).toBe(false);
});
it('returns boundary numbers', () => {
const vals = s.values('number');
expect(vals).toContain('0');
expect(vals).toContain('2147483647');
expect(vals).toContain('-2147483648');
});
it('returns boundary dates', () => {
const vals = s.values('date');
expect(vals).toContain('1900-01-01');
expect(vals).toContain('2099-12-31');
});
});
// ─── InputTypeDetector ────────────────────────────────────────────────────────
describe('detectInputType (module adapter)', () => {
it('detects email from inputType', () => {
expect(detectInputType({ inputType: 'email' })).toBe('email');
});
it('detects password', () => {
expect(detectInputType({ inputType: 'password' })).toBe('password');
});
it('detects textarea', () => {
expect(detectInputType({ tagName: 'textarea' })).toBe('textarea');
});
it('infers email from name', () => {
expect(detectInputType({ name: 'email_address' })).toBe('email');
});
it('falls back to text', () => {
expect(detectInputType({})).toBe('text');
});
});
// ─── FuzzingEngineAdapter ─────────────────────────────────────────────────────
describe('FuzzingEngineAdapter', () => {
it('generates fuzz actions from DOM with input fields', () => {
const engine = new FuzzingEngineAdapter({ intensity: 'low', seed: 42 });
const dom = `<form><input type="email" name="email" /><input type="password" name="pass" /></form>`;
const state = makeState(dom);
const actions = engine.generateFuzzActions(dom, state);
expect(actions.length).toBeGreaterThan(0);
expect(actions.every((a) => a.type === 'fill')).toBe(true);
expect(actions.every((a) => a.stateId === 'state1')).toBe(true);
});
it('generates more actions at high intensity', () => {
const low = new FuzzingEngineAdapter({ intensity: 'low', seed: 1 });
const high = new FuzzingEngineAdapter({ intensity: 'high', seed: 1 });
const dom = `<input type="text" name="q" />`;
const state = makeState(dom);
expect(high.generateFuzzActions(dom, state).length).toBeGreaterThan(
low.generateFuzzActions(dom, state).length
);
});
it('returns empty for DOM with no inputs', () => {
const engine = new FuzzingEngineAdapter({ intensity: 'medium', seed: 1 });
const dom = `<div><p>No forms here</p></div>`;
const state = makeState(dom);
expect(engine.generateFuzzActions(dom, state)).toHaveLength(0);
});
});