/** * FuzzingEngineAdapter — implements IFuzzerEngine port using the 5 fuzzing strategies. * Adapts the legacy FuzzingEngine logic to the hexagonal architecture. */ import * as crypto from 'crypto'; import { IAction, IState } from '../../../../core/interfaces'; import { IFuzzerEngine } from '../../domain/ports/IFuzzerEngine'; import { detectInputType, DetectedInputType } from './InputTypeDetector'; import { EmptyValueStrategy } from '../strategies/EmptyValueStrategy'; import { OversizedStringStrategy } from '../strategies/OversizedStringStrategy'; import { SpecialCharsStrategy } from '../strategies/SpecialCharsStrategy'; import { TypeMismatchStrategy } from '../strategies/TypeMismatchStrategy'; import { BoundaryValueStrategy } from '../strategies/BoundaryValueStrategy'; interface FormField { selector: string; tagName: string; inputType?: string; name?: string; placeholder?: string; ariaLabel?: string; } const INPUT_RE = /<(input|textarea|select)[^>]*>/gi; const ATTR_RE = (name: string): RegExp => new RegExp(`${name}="([^"]*)"`, 'i'); function extractFields(domSnapshot: string): FormField[] { const fields: FormField[] = []; let match: RegExpExecArray | null; while ((match = INPUT_RE.exec(domSnapshot)) !== null) { const tag = match[0] ?? ''; const tagName = match[1] ?? 'input'; const idMatch = ATTR_RE('id').exec(tag); const nameMatch = ATTR_RE('name').exec(tag); const typeMatch = ATTR_RE('type').exec(tag); const placeholderMatch = ATTR_RE('placeholder').exec(tag); const ariaMatch = ATTR_RE('aria-label').exec(tag); const selector = idMatch?.[1] ? `#${idMatch[1]}` : nameMatch?.[1] ? `[name="${nameMatch[1]}"]` : tagName; fields.push({ selector, tagName, inputType: typeMatch?.[1], name: nameMatch?.[1], placeholder: placeholderMatch?.[1], ariaLabel: ariaMatch?.[1], }); } return fields; } type FuzzingStrategy = { name: string; appliesTo(type: DetectedInputType): boolean; values(type?: DetectedInputType): string[]; }; export class FuzzingEngineAdapter implements IFuzzerEngine { private readonly intensity: 'low' | 'medium' | 'high'; private readonly seed: number; constructor(config: { intensity: 'low' | 'medium' | 'high'; seed: number }) { this.intensity = config.intensity; this.seed = config.seed; } generateFuzzActions(domSnapshot: string, state: IState): IAction[] { const fields = extractFields(domSnapshot); const actions: IAction[] = []; const now = Date.now(); const strategies = this.selectStrategies(); for (const field of fields) { const detectedType = detectInputType({ tagName: field.tagName, inputType: field.inputType, name: field.name, placeholder: field.placeholder, ariaLabel: field.ariaLabel, }); for (const strategy of strategies) { if (!strategy.appliesTo(detectedType)) continue; const values = strategy.values(detectedType); for (const value of values) { actions.push({ id: crypto.randomUUID(), type: 'fill', selector: field.selector, value, timestamp: now, seed: this.seed, stateId: state.id, }); } } } return actions; } private selectStrategies(): FuzzingStrategy[] { const empty = new EmptyValueStrategy(); const typeMismatch = new TypeMismatchStrategy(); const oversized = new OversizedStringStrategy(this.intensity); const boundary = new BoundaryValueStrategy(); const special = new SpecialCharsStrategy(); switch (this.intensity) { case 'low': return [empty, typeMismatch]; case 'medium': return [empty, typeMismatch, oversized, boundary]; case 'high': return [empty, typeMismatch, oversized, boundary, special]; } } }