@vulcn/engine 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/driver-manager.ts","../src/driver-types.ts","../src/plugin-manager.ts","../src/plugin-types.ts","../src/auth.ts","../src/session.ts"],"sourcesContent":["/**\n * Vulcn Driver Manager\n *\n * Handles driver loading, registration, and lifecycle.\n * Drivers are loaded from npm packages or local files.\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { parse, stringify } from \"yaml\";\nimport type {\n VulcnDriver,\n LoadedDriver,\n DriverSource,\n Session,\n Step,\n RunContext,\n RunResult,\n RunOptions,\n RecordOptions,\n CrawlOptions,\n RecordingHandle,\n DriverLogger,\n DRIVER_API_VERSION,\n} from \"./driver-types\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { ScanContext } from \"./plugin-types\";\n\n/**\n * Driver Manager - loads and manages recording/running drivers\n */\nexport class DriverManager {\n private drivers: Map<string, LoadedDriver> = new Map();\n private defaultDriver: string | null = null;\n\n /**\n * Register a driver\n */\n register(driver: VulcnDriver, source: DriverSource = \"builtin\"): void {\n this.validateDriver(driver);\n this.drivers.set(driver.name, { driver, source });\n\n // First registered driver becomes default\n if (this.drivers.size === 1) {\n this.defaultDriver = driver.name;\n }\n }\n\n /**\n * Load a driver from npm or local path\n */\n async load(nameOrPath: string): Promise<void> {\n let driver: VulcnDriver;\n let source: DriverSource;\n\n if (\n nameOrPath.startsWith(\"./\") ||\n nameOrPath.startsWith(\"../\") ||\n isAbsolute(nameOrPath)\n ) {\n // Local file\n const resolved = isAbsolute(nameOrPath)\n ? nameOrPath\n : resolve(process.cwd(), nameOrPath);\n const module = await import(resolved);\n driver = module.default || module;\n source = \"local\";\n } else {\n // npm package\n const module = await import(nameOrPath);\n driver = module.default || module;\n source = \"npm\";\n }\n\n this.register(driver, source);\n }\n\n /**\n * Get a loaded driver by name\n */\n get(name: string): VulcnDriver | undefined {\n return this.drivers.get(name)?.driver;\n }\n\n /**\n * Get the default driver\n */\n getDefault(): VulcnDriver | undefined {\n if (!this.defaultDriver) return undefined;\n return this.get(this.defaultDriver);\n }\n\n /**\n * Set the default driver\n */\n setDefault(name: string): void {\n if (!this.drivers.has(name)) {\n throw new Error(`Driver \"${name}\" is not registered`);\n }\n this.defaultDriver = name;\n }\n\n /**\n * Check if a driver is registered\n */\n has(name: string): boolean {\n return this.drivers.has(name);\n }\n\n /**\n * Get all registered drivers\n */\n list(): LoadedDriver[] {\n return Array.from(this.drivers.values());\n }\n\n /**\n * Get driver for a session\n */\n getForSession(session: Session): VulcnDriver {\n const driverName = session.driver;\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(\n `Driver \"${driverName}\" not found. Install @vulcn/driver-${driverName} or load it manually.`,\n );\n }\n\n return driver;\n }\n\n /**\n * Parse a YAML session string into a Session object.\n *\n * Handles both new driver-format sessions and legacy v1 sessions.\n * Legacy sessions (those with non-namespaced step types like \"click\",\n * \"input\", \"navigate\") are automatically converted to the driver format\n * (e.g., \"browser.click\", \"browser.input\", \"browser.navigate\").\n *\n * @param yaml - Raw YAML string\n * @param defaultDriver - Driver to assign for legacy sessions (default: \"browser\")\n */\n parseSession(yaml: string, defaultDriver = \"browser\"): Session {\n const data = parse(yaml) as Record<string, unknown>;\n\n // Already in driver format — has a `driver` field\n if (data.driver && typeof data.driver === \"string\") {\n return data as unknown as Session;\n }\n\n // Legacy format — convert to driver session\n const steps = (data.steps as Array<Record<string, unknown>>) ?? [];\n const convertedSteps: Step[] = steps.map((step) => {\n const type = step.type as string;\n\n // If step type is already namespaced (e.g. \"browser.click\"), keep it\n if (type.includes(\".\")) {\n return step as unknown as Step;\n }\n\n // Convert legacy type → namespaced type\n return {\n ...step,\n type: `${defaultDriver}.${type}`,\n } as unknown as Step;\n });\n\n return {\n name: (data.name as string) ?? \"Untitled Session\",\n driver: defaultDriver,\n driverConfig: {\n browser: data.browser ?? \"chromium\",\n viewport: data.viewport ?? { width: 1280, height: 720 },\n startUrl: data.startUrl as string,\n },\n steps: convertedSteps,\n metadata: {\n recordedAt: data.recordedAt as string,\n version: (data.version as string) ?? \"1\",\n },\n };\n }\n\n /**\n * Start recording with a driver\n */\n async startRecording(\n driverName: string,\n config: Record<string, unknown>,\n options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n return driver.recorder.start(config, options);\n }\n\n /**\n * Auto-crawl a URL using a driver.\n *\n * Uses the driver's optional crawl() method to automatically\n * discover forms and injection points, returning Session[] that\n * can be passed to execute().\n *\n * Not all drivers support this — only browser has crawl capability.\n * CLI and API drivers will throw.\n */\n async crawl(\n driverName: string,\n config: Record<string, unknown>,\n options: CrawlOptions = {},\n ): Promise<Session[]> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n if (!driver.recorder.crawl) {\n throw new Error(\n `Driver \"${driverName}\" does not support auto-crawl. Use manual recording instead.`,\n );\n }\n\n return driver.recorder.crawl(config, options);\n }\n\n /**\n * Execute a session\n * Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.\n * Plugin onRunStart is deferred until the driver signals the page is ready\n * via the onPageReady callback, ensuring plugins get a real page object.\n */\n async execute(\n session: Session,\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<RunResult> {\n const driver = this.getForSession(session);\n const findings: Finding[] = [];\n const logger = this.createLogger(driver.name);\n\n // Shared addFinding function — used by both internal RunContext and\n // plugin context. Ensures all findings (active + passive) flow through\n // the onFinding callback so consumers get notified consistently.\n const addFinding = (finding: Finding) => {\n findings.push(finding);\n pluginManager.addFinding(finding);\n options.onFinding?.(finding);\n };\n\n // Build a plugin context template for hooks (page is set in onPageReady)\n const pluginCtx = {\n session,\n page: null as unknown,\n headless: !!(options as Record<string, unknown>).headless,\n config: {} as Record<string, unknown>,\n engine: { version: \"0.3.0\", pluginApiVersion: 1 },\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n fetch: globalThis.fetch,\n };\n\n const ctx: RunContext = {\n session,\n pluginManager,\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n options: {\n ...options,\n // Provide onPageReady callback — fires plugin onRunStart hooks\n // with the real page object once the driver has created it\n onPageReady: async (page: unknown) => {\n pluginCtx.page = page;\n\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {\n try {\n await loaded.plugin.hooks.onRunStart({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(\n `Plugin ${loaded.plugin.name} onRunStart failed: ${err}`,\n );\n }\n }\n }\n },\n // Fires before browser closes — lets plugins flush pending async work\n onBeforeClose: async (_page: unknown) => {\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onBeforeClose) {\n try {\n await loaded.plugin.hooks.onBeforeClose({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(\n `Plugin ${loaded.plugin.name} onBeforeClose failed: ${err}`,\n );\n }\n }\n }\n },\n },\n };\n\n // Execute via driver runner\n // (runner calls ctx.options.onPageReady(page) after creating the page)\n let result = await driver.runner.execute(session, ctx);\n\n // Call onRunEnd hooks (e.g., report generation)\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {\n try {\n result = await loaded.plugin.hooks.onRunEnd(result, {\n ...pluginCtx,\n config: loaded.config,\n findings: result.findings,\n });\n } catch (err) {\n logger.warn(`Plugin ${loaded.plugin.name} onRunEnd failed: ${err}`);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Execute multiple sessions with a shared browser (scan-level orchestration).\n *\n * This is the preferred entry point for running a full scan. It:\n * 1. Launches ONE browser for the entire scan\n * 2. Passes the browser to each session's runner via options.browser\n * 3. Each session creates its own context (lightweight, isolated cookies)\n * 4. Aggregates results across all sessions\n * 5. Closes the browser once at the end\n *\n * This is 5-10x faster than calling execute() per session because\n * launching a browser takes 2-3 seconds.\n */\n async executeScan(\n sessions: Session[],\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<{\n results: RunResult[];\n aggregate: RunResult;\n }> {\n if (sessions.length === 0) {\n const empty: RunResult = {\n findings: [],\n stepsExecuted: 0,\n payloadsTested: 0,\n duration: 0,\n errors: [\"No sessions to execute\"],\n };\n return { results: [], aggregate: empty };\n }\n\n const startTime = Date.now();\n const results: RunResult[] = [];\n const allFindings: Finding[] = [];\n let totalSteps = 0;\n let totalPayloads = 0;\n const allErrors: string[] = [];\n\n // Launch shared browser via the first session's driver\n // (all sessions should use the same driver in a scan)\n const firstDriver = this.getForSession(sessions[0]);\n let sharedBrowser: unknown = null;\n\n // Only share browser for browser driver\n if (firstDriver.name === \"browser\") {\n try {\n // Dynamic import to avoid hard dependency on driver-browser\n // Use variable to bypass TS module resolution (engine doesn't depend on driver-browser)\n const driverPkg = \"@vulcn/driver-browser\";\n const { launchBrowser } = await import(/* @vite-ignore */ driverPkg);\n const browserType =\n (sessions[0].driverConfig.browser as string) ?? \"chromium\";\n const headless = options.headless ?? true;\n const result = await launchBrowser({\n browser: browserType as \"chromium\" | \"firefox\" | \"webkit\",\n headless,\n });\n sharedBrowser = result.browser;\n } catch {\n // If we can't launch a shared browser, fall back to per-session\n }\n }\n\n try {\n // Fire onScanStart hooks\n await pluginManager.callHook(\"onScanStart\", async (hook, ctx) => {\n const scanCtx: ScanContext = {\n ...ctx,\n sessions,\n headless: options.headless ?? true,\n sessionCount: sessions.length,\n };\n await (hook as (ctx: ScanContext) => Promise<void>)(scanCtx);\n });\n\n for (const session of sessions) {\n const sessionOptions: RunOptions = {\n ...options,\n ...(sharedBrowser ? { browser: sharedBrowser } : {}),\n };\n\n const result = await this.execute(\n session,\n pluginManager,\n sessionOptions,\n );\n results.push(result);\n allFindings.push(...result.findings);\n totalSteps += result.stepsExecuted;\n totalPayloads += result.payloadsTested;\n allErrors.push(...result.errors);\n }\n } finally {\n // Close the shared browser\n if (\n sharedBrowser &&\n typeof (sharedBrowser as { close: () => Promise<void> }).close ===\n \"function\"\n ) {\n await (sharedBrowser as { close: () => Promise<void> }).close();\n }\n }\n\n const aggregate: RunResult = {\n findings: allFindings,\n stepsExecuted: totalSteps,\n payloadsTested: totalPayloads,\n duration: Date.now() - startTime,\n errors: allErrors,\n };\n\n // Fire onScanEnd hooks — allows plugins to transform the aggregate result\n let finalAggregate = aggregate;\n finalAggregate = await pluginManager.callHookPipe(\n \"onScanEnd\",\n finalAggregate,\n async (hook, value, ctx) => {\n const scanCtx: ScanContext = {\n ...ctx,\n sessions,\n headless: options.headless ?? true,\n sessionCount: sessions.length,\n };\n return await (\n hook as (result: RunResult, ctx: ScanContext) => Promise<RunResult>\n )(value, scanCtx);\n },\n );\n\n return { results, aggregate: finalAggregate };\n }\n\n /**\n * Validate driver structure\n */\n private validateDriver(driver: unknown): asserts driver is VulcnDriver {\n if (!driver || typeof driver !== \"object\") {\n throw new Error(\"Driver must be an object\");\n }\n\n const d = driver as Record<string, unknown>;\n\n if (typeof d.name !== \"string\" || !d.name) {\n throw new Error(\"Driver must have a name\");\n }\n\n if (typeof d.version !== \"string\" || !d.version) {\n throw new Error(\"Driver must have a version\");\n }\n\n if (!Array.isArray(d.stepTypes) || d.stepTypes.length === 0) {\n throw new Error(\"Driver must define stepTypes\");\n }\n\n if (!d.recorder || typeof d.recorder !== \"object\") {\n throw new Error(\"Driver must have a recorder\");\n }\n\n if (!d.runner || typeof d.runner !== \"object\") {\n throw new Error(\"Driver must have a runner\");\n }\n }\n\n /**\n * Create a scoped logger for a driver\n */\n private createLogger(name: string): DriverLogger {\n const prefix = `[driver:${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n}\n\n/**\n * Default driver manager instance\n */\nexport const driverManager = new DriverManager();\n","/**\n * Vulcn Driver System\n *\n * Drivers handle recording and running sessions for different targets:\n * - browser: Web applications (Playwright)\n * - api: REST/HTTP APIs\n * - cli: Command-line tools\n *\n * Each driver implements RecorderDriver and RunnerDriver interfaces.\n */\n\nimport type { z } from \"zod\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { PluginManager } from \"./plugin-manager\";\n\n/**\n * Current driver API version\n */\nexport const DRIVER_API_VERSION = 1;\n\n/**\n * Generic step - drivers define their own step types\n */\nexport interface Step {\n /** Unique step ID */\n id: string;\n\n /** Step type (namespaced, e.g., \"browser.click\", \"api.request\") */\n type: string;\n\n /** Timestamp when step was recorded */\n timestamp: number;\n\n /** Step-specific data */\n [key: string]: unknown;\n}\n\n/**\n * Generic session format\n */\nexport interface Session {\n /** Session name */\n name: string;\n\n /** Driver that recorded this session */\n driver: string;\n\n /** Driver-specific configuration */\n driverConfig: Record<string, unknown>;\n\n /** Recorded steps */\n steps: Step[];\n\n /** Session metadata */\n metadata?: {\n recordedAt?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Recording context passed to drivers\n */\nexport interface RecordContext {\n /** Session being built */\n session: Partial<Session>;\n\n /** Add a step to the session */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n\n /** Logger */\n logger: DriverLogger;\n}\n\n/**\n * Running context passed to drivers\n */\nexport interface RunContext {\n /** Session being executed */\n session: Session;\n\n /** Plugin manager for calling hooks */\n pluginManager: PluginManager;\n\n /** Available payloads */\n payloads: RuntimePayload[];\n\n /** Collected findings */\n findings: Finding[];\n\n /** Add a finding */\n addFinding(finding: Finding): void;\n\n /** Logger */\n logger: DriverLogger;\n\n /** Running options */\n options: RunOptions;\n}\n\n/**\n * Options for recording\n */\nexport interface RecordOptions {\n /** Enable auto-crawl mode (driver discovers forms automatically) */\n auto?: boolean;\n\n /** Crawl options (only used when auto=true) */\n crawlOptions?: CrawlOptions;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Options for auto-crawl mode\n *\n * When a driver supports crawling, these options control how\n * the automated discovery works. Not all drivers support crawling —\n * it's optional and primarily used by the browser driver.\n */\nexport interface CrawlOptions {\n /** Maximum crawl depth (0 = only the given URL, default: 2) */\n maxDepth?: number;\n\n /** Maximum number of pages to visit (default: 20) */\n maxPages?: number;\n\n /** Timeout per page navigation in ms (default: 10000) */\n pageTimeout?: number;\n\n /** Only crawl pages under the same origin (default: true) */\n sameOrigin?: boolean;\n\n /** Playwright storage state JSON for authenticated crawling */\n storageState?: string;\n\n /** Callback when a page is crawled */\n onPageCrawled?: (url: string, formsFound: number) => void;\n}\n\n/**\n * Options for running\n */\nexport interface RunOptions {\n /** Run headless (for visual drivers) */\n headless?: boolean;\n\n /** Callback for findings */\n onFinding?: (finding: Finding) => void;\n\n /** Callback for step completion */\n onStepComplete?: (stepId: string, payloadCount: number) => void;\n\n /**\n * Called by the driver runner after the page/environment is ready.\n * The driver-manager uses this to fire plugin onRunStart hooks\n * with the real page object (instead of null).\n */\n onPageReady?: (page: unknown) => Promise<void>;\n\n /**\n * Called by the driver runner before closing the browser/environment.\n * The driver-manager uses this to fire plugin onBeforeClose hooks\n * so plugins can flush pending async work.\n */\n onBeforeClose?: (page: unknown) => Promise<void>;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Run result\n */\nexport interface RunResult {\n /** All findings */\n findings: Finding[];\n\n /** Steps executed */\n stepsExecuted: number;\n\n /** Payloads tested */\n payloadsTested: number;\n\n /** Duration in milliseconds */\n duration: number;\n\n /** Errors encountered */\n errors: string[];\n}\n\n/**\n * Driver logger\n */\nexport interface DriverLogger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\n/**\n * Recorder Driver Interface\n *\n * Implement this to add recording support for a target type.\n */\nexport interface RecorderDriver {\n /** Start recording and return control handle */\n start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle>;\n\n /**\n * Auto-crawl a URL and generate sessions.\n *\n * Optional — only drivers that support automated discovery\n * (e.g., browser) implement this. CLI and API drivers do not.\n *\n * When options.auto=true is passed to startRecording, the engine\n * calls this instead of start().\n */\n crawl?(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]>;\n}\n\n/**\n * Handle returned by RecorderDriver.start()\n */\nexport interface RecordingHandle {\n /** Stop recording and return the session */\n stop(): Promise<Session>;\n\n /** Abort recording without saving */\n abort(): Promise<void>;\n\n /** Get current steps (during recording) */\n getSteps(): Step[];\n\n /** Manually add a step */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n}\n\n/**\n * Runner Driver Interface\n *\n * Implement this to add running/replay support for a target type.\n */\nexport interface RunnerDriver {\n /** Execute a session with payloads */\n execute(session: Session, ctx: RunContext): Promise<RunResult>;\n}\n\n/**\n * Complete driver definition\n */\nexport interface VulcnDriver {\n /** Unique driver name (e.g., \"browser\", \"api\", \"cli\") */\n name: string;\n\n /** Driver version */\n version: string;\n\n /** Driver API version */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Configuration schema (Zod) */\n configSchema?: z.ZodSchema;\n\n /** Step types this driver handles */\n stepTypes: string[];\n\n /** Recorder implementation */\n recorder: RecorderDriver;\n\n /** Runner implementation */\n runner: RunnerDriver;\n}\n\n/**\n * Driver source for loading\n */\nexport type DriverSource = \"npm\" | \"local\" | \"builtin\";\n\n/**\n * Loaded driver with metadata\n */\nexport interface LoadedDriver {\n driver: VulcnDriver;\n source: DriverSource;\n}\n","/**\n * Vulcn Plugin Manager\n * Handles plugin loading, lifecycle, and hook execution\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve, isAbsolute } from \"node:path\";\nimport YAML from \"yaml\";\nimport { z } from \"zod\";\nimport type {\n VulcnPlugin,\n VulcnConfig,\n PluginConfig,\n LoadedPlugin,\n PluginContext,\n PluginSource,\n PluginLogger,\n EngineInfo,\n PluginHooks,\n} from \"./plugin-types\";\nimport { PLUGIN_API_VERSION } from \"./plugin-types\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n// Package version (injected at build time or read from package.json)\nconst ENGINE_VERSION = \"0.2.0\";\n\n/**\n * Config file schema\n */\nconst VulcnConfigSchema = z.object({\n version: z.string().default(\"1\"),\n plugins: z\n .array(\n z.object({\n name: z.string(),\n config: z.record(z.unknown()).optional(),\n enabled: z.boolean().default(true),\n }),\n )\n .optional(),\n settings: z\n .object({\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).optional(),\n headless: z.boolean().optional(),\n timeout: z.number().optional(),\n })\n .optional(),\n});\n\n/**\n * Plugin Manager - loads, configures, and orchestrates plugins\n */\nexport class PluginManager {\n private plugins: LoadedPlugin[] = [];\n private config: VulcnConfig | null = null;\n private initialized = false;\n\n /**\n * Shared context passed to all plugins\n */\n private sharedPayloads: RuntimePayload[] = [];\n private sharedFindings: Finding[] = [];\n\n /**\n * Load configuration from vulcn.config.yml\n */\n async loadConfig(configPath?: string): Promise<VulcnConfig> {\n const paths = configPath\n ? [configPath]\n : [\n \"vulcn.config.yml\",\n \"vulcn.config.yaml\",\n \"vulcn.config.json\",\n \".vulcnrc.yml\",\n \".vulcnrc.yaml\",\n \".vulcnrc.json\",\n ];\n\n for (const path of paths) {\n const resolved = isAbsolute(path) ? path : resolve(process.cwd(), path);\n if (existsSync(resolved)) {\n const content = await readFile(resolved, \"utf-8\");\n const parsed = path.endsWith(\".json\")\n ? JSON.parse(content)\n : YAML.parse(content);\n this.config = VulcnConfigSchema.parse(parsed);\n return this.config;\n }\n }\n\n // No config file - use defaults\n this.config = { version: \"1\", plugins: [], settings: {} };\n return this.config;\n }\n\n /**\n * Load all plugins from config\n */\n async loadPlugins(): Promise<void> {\n if (!this.config) {\n await this.loadConfig();\n }\n\n const pluginConfigs = this.config?.plugins || [];\n\n for (const pluginConfig of pluginConfigs) {\n if (pluginConfig.enabled === false) continue;\n\n try {\n const loaded = await this.loadPlugin(pluginConfig);\n this.plugins.push(loaded);\n } catch (err) {\n console.error(\n `Failed to load plugin ${pluginConfig.name}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n /**\n * Load a single plugin\n */\n private async loadPlugin(config: PluginConfig): Promise<LoadedPlugin> {\n const { name, config: pluginConfig = {} } = config;\n let plugin: VulcnPlugin;\n let source: PluginSource;\n\n // Determine plugin source and load\n if (name.startsWith(\"./\") || name.startsWith(\"../\") || isAbsolute(name)) {\n // Local file plugin\n const resolved = isAbsolute(name) ? name : resolve(process.cwd(), name);\n const module = await import(resolved);\n plugin = module.default || module;\n source = \"local\";\n } else if (name.startsWith(\"@vulcn/\")) {\n // Official plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n } else {\n // Community plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n }\n\n // Validate plugin structure\n this.validatePlugin(plugin);\n\n // Validate plugin config if schema provided\n let resolvedConfig = pluginConfig;\n if (plugin.configSchema) {\n try {\n resolvedConfig = plugin.configSchema.parse(pluginConfig);\n } catch (err) {\n throw new Error(\n `Invalid config for plugin ${name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return {\n plugin,\n config: resolvedConfig,\n source,\n enabled: true,\n };\n }\n\n /**\n * Validate plugin structure\n */\n private validatePlugin(plugin: unknown): asserts plugin is VulcnPlugin {\n if (!plugin || typeof plugin !== \"object\") {\n throw new Error(\"Plugin must be an object\");\n }\n\n const p = plugin as Record<string, unknown>;\n if (typeof p.name !== \"string\" || !p.name) {\n throw new Error(\"Plugin must have a name\");\n }\n if (typeof p.version !== \"string\" || !p.version) {\n throw new Error(\"Plugin must have a version\");\n }\n\n // Check API version compatibility\n const apiVersion = (p.apiVersion as number) || 1;\n if (apiVersion > PLUGIN_API_VERSION) {\n throw new Error(\n `Plugin requires API version ${apiVersion}, but engine supports ${PLUGIN_API_VERSION}`,\n );\n }\n }\n\n /**\n * Add a plugin programmatically (for testing or dynamic loading)\n */\n addPlugin(plugin: VulcnPlugin, config: Record<string, unknown> = {}): void {\n this.validatePlugin(plugin);\n this.plugins.push({\n plugin,\n config,\n source: \"custom\",\n enabled: true,\n });\n }\n\n /**\n * Initialize all plugins (call onInit hooks)\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Load payloads from plugins that provide them\n for (const loaded of this.plugins) {\n if (loaded.plugin.payloads) {\n const payloads =\n typeof loaded.plugin.payloads === \"function\"\n ? await loaded.plugin.payloads()\n : loaded.plugin.payloads;\n this.sharedPayloads.push(...payloads);\n }\n }\n\n // Call onInit hooks\n await this.callHook(\"onInit\", (hook, ctx) => hook(ctx));\n\n this.initialized = true;\n }\n\n /**\n * Destroy all plugins (call onDestroy hooks)\n */\n async destroy(): Promise<void> {\n await this.callHook(\"onDestroy\", (hook, ctx) => hook(ctx));\n this.plugins = [];\n this.sharedPayloads = [];\n this.sharedFindings = [];\n this.initialized = false;\n }\n\n /**\n * Get all loaded payloads\n */\n getPayloads(): RuntimePayload[] {\n return this.sharedPayloads;\n }\n\n /**\n * Get all collected findings\n */\n getFindings(): Finding[] {\n return this.sharedFindings;\n }\n\n /**\n * Add a finding (used by detectors)\n */\n addFinding(finding: Finding): void {\n this.sharedFindings.push(finding);\n }\n\n /**\n * Add payloads (used by loaders)\n */\n addPayloads(payloads: RuntimePayload[]): void {\n this.sharedPayloads.push(...payloads);\n }\n\n /**\n * Clear findings (for new run)\n */\n clearFindings(): void {\n this.sharedFindings = [];\n }\n\n /**\n * Get loaded plugins\n */\n getPlugins(): LoadedPlugin[] {\n return this.plugins;\n }\n\n /**\n * Check if a plugin is loaded by name\n */\n hasPlugin(name: string): boolean {\n return this.plugins.some((p) => p.plugin.name === name);\n }\n\n /**\n * Create base context for plugins\n */\n createContext(pluginConfig: Record<string, unknown>): PluginContext {\n const engineInfo: EngineInfo = {\n version: ENGINE_VERSION,\n pluginApiVersion: PLUGIN_API_VERSION,\n };\n\n return {\n config: pluginConfig,\n engine: engineInfo,\n payloads: this.sharedPayloads,\n findings: this.sharedFindings,\n addFinding: (finding: Finding) => {\n this.sharedFindings.push(finding);\n },\n logger: this.createLogger(\"plugin\"),\n fetch: globalThis.fetch,\n };\n }\n\n /**\n * Create scoped logger for a plugin\n */\n private createLogger(name: string): PluginLogger {\n const prefix = `[${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n\n /**\n * Call a hook on all plugins sequentially\n */\n async callHook<K extends keyof PluginHooks>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<unknown>,\n ): Promise<void> {\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n await executor(hook as NonNullable<PluginHooks[K]>, ctx);\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n }\n\n /**\n * Call a hook and collect results\n */\n async callHookCollect<K extends keyof PluginHooks, R>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<R | R[] | null>,\n ): Promise<R[]> {\n const results: R[] = [];\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n const result = await executor(\n hook as NonNullable<PluginHooks[K]>,\n ctx,\n );\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n results.push(...result);\n } else {\n results.push(result);\n }\n }\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return results;\n }\n\n /**\n * Call a hook that transforms a value through the pipeline\n */\n async callHookPipe<T>(\n hookName: keyof PluginHooks,\n initial: T,\n executor: (\n hook: NonNullable<PluginHooks[typeof hookName]>,\n value: T,\n ctx: PluginContext,\n ) => Promise<T>,\n ): Promise<T> {\n let value = initial;\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n value = await executor(\n hook as NonNullable<PluginHooks[typeof hookName]>,\n value,\n ctx,\n );\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return value;\n }\n}\n\n/**\n * Default shared plugin manager instance\n */\nexport const pluginManager = new PluginManager();\n","/**\n * Vulcn Plugin System Types\n * @module @vulcn/engine/plugin\n *\n * The plugin system is driver-agnostic. Detection plugins receive\n * a generic page interface rather than Playwright types directly.\n * This allows the same plugin to work across different driver types.\n */\n\nimport type { z } from \"zod\";\nimport type { Session, Step } from \"./driver-types\";\nimport type { Finding } from \"./types\";\nimport type { RunResult } from \"./driver-types\";\nimport type { RuntimePayload, PayloadCategory } from \"./payload-types\";\n\n// Re-export for plugin authors\nexport type {\n Session,\n Step,\n Finding,\n RunResult,\n RuntimePayload,\n PayloadCategory,\n};\n\n/**\n * Plugin API version - plugins declare compatibility\n */\nexport const PLUGIN_API_VERSION = 1;\n\n/**\n * Plugin source types for identification\n */\nexport type PluginSource = \"builtin\" | \"npm\" | \"local\" | \"custom\";\n\n/**\n * Main plugin interface\n */\nexport interface VulcnPlugin {\n /** Unique plugin name (e.g., \"@vulcn/plugin-payloads\") */\n name: string;\n\n /** Plugin version (semver) */\n version: string;\n\n /** Plugin API version this plugin targets */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Lifecycle hooks */\n hooks?: PluginHooks;\n\n /**\n * Payloads provided by this plugin (Loaders)\n * Can be static array or async function for lazy loading\n */\n payloads?: RuntimePayload[] | (() => Promise<RuntimePayload[]>);\n\n /**\n * Zod schema for plugin configuration validation\n */\n configSchema?: z.ZodSchema;\n}\n\n/**\n * Plugin lifecycle hooks\n *\n * Detection hooks (onDialog, onConsoleMessage, etc.) receive\n * Playwright types from the driver. Plugins that use these\n * should declare playwright as a peer/dev dependency.\n */\nexport interface PluginHooks {\n // ─────────────────────────────────────────────────────────────────\n // Initialization\n // ─────────────────────────────────────────────────────────────────\n\n /**\n * Called when plugin is loaded, before any operation\n * Use for setup, loading payloads, etc.\n */\n onInit?: (ctx: PluginContext) => Promise<void>;\n\n /**\n * Called when plugin is unloaded/cleanup\n */\n onDestroy?: (ctx: PluginContext) => Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────\n // Recording Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when recording starts */\n onRecordStart?: (ctx: RecordContext) => Promise<void>;\n\n /** Called for each recorded step, can transform */\n onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;\n\n /** Called when recording ends, can transform session */\n onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;\n\n // ─────────────────────────────────────────────────────────────────\n // Scan Phase (wraps all sessions)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called once when a scan starts (before any session is executed) */\n onScanStart?: (ctx: ScanContext) => Promise<void>;\n\n /** Called once when a scan ends (after all sessions have executed) */\n onScanEnd?: (result: RunResult, ctx: ScanContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Running Phase (per session)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when run starts */\n onRunStart?: (ctx: RunContext) => Promise<void>;\n\n /** Called before each payload is injected, can transform payload */\n onBeforePayload?: (\n payload: string,\n step: Step,\n ctx: RunContext,\n ) => Promise<string>;\n\n /** Called after payload injection, for detection */\n onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;\n\n /**\n * Called before the browser/driver is closed.\n * Plugins should await any pending async work here (e.g., flush\n * in-flight response handlers that need browser access).\n */\n onBeforeClose?: (ctx: PluginContext) => Promise<void>;\n\n /** Called when run ends, can transform results */\n onRunEnd?: (result: RunResult, ctx: RunContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Browser Event Hooks (Detection)\n // These receive driver-specific types (e.g. Playwright's Dialog)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when JavaScript alert/confirm/prompt appears */\n onDialog?: (dialog: unknown, ctx: DetectContext) => Promise<Finding | null>;\n\n /** Called on console.log/warn/error */\n onConsoleMessage?: (\n msg: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on page load/navigation */\n onPageLoad?: (page: unknown, ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called on network request */\n onNetworkRequest?: (\n request: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on network response */\n onNetworkResponse?: (\n response: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n}\n\n/**\n * Logger interface for plugins\n */\nexport interface PluginLogger {\n debug: (msg: string, ...args: unknown[]) => void;\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n error: (msg: string, ...args: unknown[]) => void;\n}\n\n/**\n * Engine information exposed to plugins\n */\nexport interface EngineInfo {\n version: string;\n pluginApiVersion: number;\n}\n\n/**\n * Base context available to all plugin hooks\n */\nexport interface PluginContext {\n /** Plugin-specific config from vulcn.config.yml */\n config: Record<string, unknown>;\n\n /** Engine information */\n engine: EngineInfo;\n\n /** Shared payload registry - loaders add payloads here */\n payloads: RuntimePayload[];\n\n /** Shared findings collection (read-only view, use addFinding to add) */\n findings: Finding[];\n\n /**\n * Add a finding through the proper callback chain.\n * Plugins should use this instead of pushing to findings[] directly,\n * so consumers (CLI, worker) get notified via onFinding callbacks.\n */\n addFinding: (finding: Finding) => void;\n\n /** Scoped logger */\n logger: PluginLogger;\n\n /** Fetch API for network requests */\n fetch: typeof fetch;\n}\n\n/**\n * Context for recording phase hooks\n */\nexport interface RecordContext extends PluginContext {\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n}\n\n/**\n * Context for running phase hooks\n */\nexport interface RunContext extends PluginContext {\n /** Session being executed */\n session: Session;\n\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n\n /** Whether running headless */\n headless: boolean;\n}\n\n/**\n * Context for scan-level hooks (wraps all sessions)\n */\nexport interface ScanContext extends PluginContext {\n /** All sessions in this scan */\n sessions: Session[];\n\n /** Whether running headless */\n headless: boolean;\n\n /** Total sessions count */\n sessionCount: number;\n}\n\n/**\n * Context for detection hooks\n */\nexport interface DetectContext extends RunContext {\n /** Current step being tested */\n step: Step;\n\n /** Current payload set being tested */\n payloadSet: RuntimePayload;\n\n /** Actual payload value injected */\n payloadValue: string;\n\n /** Step ID for reporting */\n stepId: string;\n}\n\n/**\n * Plugin configuration in vulcn.config.yml\n */\nexport interface PluginConfig {\n /** Plugin name/path */\n name: string;\n\n /** Plugin-specific configuration */\n config?: Record<string, unknown>;\n\n /** Whether plugin is enabled (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Vulcn configuration file schema\n */\nexport interface VulcnConfig {\n /** Config version */\n version: string;\n\n /** Plugins to load */\n plugins?: PluginConfig[];\n\n /** Global settings */\n settings?: {\n headless?: boolean;\n timeout?: number;\n };\n}\n\n/**\n * Loaded plugin instance with resolved config\n */\nexport interface LoadedPlugin {\n /** Plugin definition */\n plugin: VulcnPlugin;\n\n /** Resolved configuration */\n config: Record<string, unknown>;\n\n /** Source of the plugin */\n source: PluginSource;\n\n /** Whether plugin is enabled */\n enabled: boolean;\n}\n","/**\n * Vulcn Auth Module\n *\n * Handles credential encryption/decryption and auth state management.\n *\n * Security:\n * - AES-256-GCM encryption for credentials at rest\n * - PBKDF2 key derivation from passphrase\n * - Reads passphrase from VULCN_KEY env var (CI/CD) or interactive prompt\n * - Auth state (cookies, localStorage) encrypted separately\n */\n\nimport {\n randomBytes,\n createCipheriv,\n createDecipheriv,\n pbkdf2Sync,\n} from \"node:crypto\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\n/** Form-based login credentials */\nexport interface FormCredentials {\n type: \"form\";\n username: string;\n password: string;\n /** Custom login URL (if different from target) */\n loginUrl?: string;\n /** Custom CSS selector for username field */\n userSelector?: string;\n /** Custom CSS selector for password field */\n passSelector?: string;\n}\n\n/** Header-based authentication (API keys, Bearer tokens) */\nexport interface HeaderCredentials {\n type: \"header\";\n headers: Record<string, string>;\n}\n\n/** All credential types */\nexport type Credentials = FormCredentials | HeaderCredentials;\n\n/** Auth configuration for a scan */\nexport interface AuthConfig {\n /** Auth strategy */\n strategy: \"storage-state\" | \"header\";\n /** Login page URL */\n loginUrl?: string;\n /** Text that appears when logged in (e.g., \"Logout\") */\n loggedInIndicator?: string;\n /** Text that appears when logged out (e.g., \"Sign In\") */\n loggedOutIndicator?: string;\n /** Session expiry detection rules */\n sessionExpiry?: {\n /** HTTP status codes that indicate session expired */\n statusCodes?: number[];\n /** URL pattern that indicates redirect to login */\n redirectPattern?: string;\n /** Page content that indicates session expired */\n pageContent?: string;\n };\n}\n\n/** Encrypted payload structure (stored as JSON) */\ninterface EncryptedData {\n /** Format version */\n version: 1;\n /** PBKDF2 salt (hex) */\n salt: string;\n /** AES-256-GCM IV (hex) */\n iv: string;\n /** AES-256-GCM auth tag (hex) */\n tag: string;\n /** Encrypted data (hex) */\n data: string;\n /** PBKDF2 iterations */\n iterations: number;\n}\n\n// ── Constants ──────────────────────────────────────────────────────────\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32; // 256 bits\nconst IV_LENGTH = 16; // 128 bits\nconst SALT_LENGTH = 32; // 256 bits\nconst PBKDF2_ITERATIONS = 100_000;\nconst PBKDF2_DIGEST = \"sha512\";\n\n// ── Key Derivation ─────────────────────────────────────────────────────\n\n/**\n * Derive AES-256 key from passphrase using PBKDF2.\n */\nfunction deriveKey(passphrase: string, salt: Buffer): Buffer {\n return pbkdf2Sync(\n passphrase,\n salt,\n PBKDF2_ITERATIONS,\n KEY_LENGTH,\n PBKDF2_DIGEST,\n );\n}\n\n// ── Encryption ─────────────────────────────────────────────────────────\n\n/**\n * Encrypt data with AES-256-GCM.\n *\n * @param data - Plaintext data to encrypt\n * @param passphrase - Passphrase for key derivation\n * @returns JSON string of EncryptedData\n */\nexport function encrypt(data: string, passphrase: string): string {\n const salt = randomBytes(SALT_LENGTH);\n const iv = randomBytes(IV_LENGTH);\n const key = deriveKey(passphrase, salt);\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n let encrypted = cipher.update(data, \"utf8\", \"hex\");\n encrypted += cipher.final(\"hex\");\n const tag = cipher.getAuthTag();\n\n const payload: EncryptedData = {\n version: 1,\n salt: salt.toString(\"hex\"),\n iv: iv.toString(\"hex\"),\n tag: tag.toString(\"hex\"),\n data: encrypted,\n iterations: PBKDF2_ITERATIONS,\n };\n\n return JSON.stringify(payload);\n}\n\n/**\n * Decrypt data encrypted with encrypt().\n *\n * @param encrypted - JSON string from encrypt()\n * @param passphrase - Passphrase used during encryption\n * @returns Decrypted plaintext\n * @throws Error if passphrase is wrong or data is tampered\n */\nexport function decrypt(encrypted: string, passphrase: string): string {\n const payload: EncryptedData = JSON.parse(encrypted);\n\n if (payload.version !== 1) {\n throw new Error(`Unsupported encryption version: ${payload.version}`);\n }\n\n const salt = Buffer.from(payload.salt, \"hex\");\n const iv = Buffer.from(payload.iv, \"hex\");\n const tag = Buffer.from(payload.tag, \"hex\");\n const key = deriveKey(passphrase, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(tag);\n\n let decrypted = decipher.update(payload.data, \"hex\", \"utf8\");\n decrypted += decipher.final(\"utf8\");\n\n return decrypted;\n}\n\n// ── Credential Helpers ─────────────────────────────────────────────────\n\n/**\n * Encrypt credentials to a storable string.\n */\nexport function encryptCredentials(\n credentials: Credentials,\n passphrase: string,\n): string {\n return encrypt(JSON.stringify(credentials), passphrase);\n}\n\n/**\n * Decrypt credentials from a stored string.\n */\nexport function decryptCredentials(\n encrypted: string,\n passphrase: string,\n): Credentials {\n const json = decrypt(encrypted, passphrase);\n return JSON.parse(json) as Credentials;\n}\n\n/**\n * Encrypt browser storage state (cookies, localStorage, etc.).\n * The state is the JSON output from Playwright's context.storageState().\n */\nexport function encryptStorageState(\n storageState: string,\n passphrase: string,\n): string {\n return encrypt(storageState, passphrase);\n}\n\n/**\n * Decrypt browser storage state.\n */\nexport function decryptStorageState(\n encrypted: string,\n passphrase: string,\n): string {\n return decrypt(encrypted, passphrase);\n}\n\n// ── Passphrase Resolution ──────────────────────────────────────────────\n\n/**\n * Get passphrase from environment or throw.\n *\n * In CI/CD, set VULCN_KEY env var.\n * In interactive mode, the CLI should prompt and pass the value here.\n */\nexport function getPassphrase(interactive?: string): string {\n // Interactive passphrase takes priority\n if (interactive) return interactive;\n\n // Fall back to env var\n const envKey = process.env.VULCN_KEY;\n if (envKey) return envKey;\n\n throw new Error(\n \"No passphrase provided. Set VULCN_KEY environment variable or pass --passphrase.\",\n );\n}\n","/**\n * Vulcn Session Format v2\n *\n * Directory-based session format: `.vulcn/` or `<name>.vulcn/`\n *\n * Structure:\n * manifest.yml - scan config, session list, auth config\n * auth/config.yml - login strategy, indicators\n * auth/state.enc - encrypted storageState (cookies/localStorage)\n * sessions/*.yml - individual session files (one per form)\n * requests/*.json - captured HTTP metadata (for Tier 1 fast scan)\n */\n\nimport { readFile, writeFile, mkdir, readdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, basename, extname } from \"node:path\";\nimport { parse, stringify } from \"yaml\";\nimport type { Session } from \"./driver-types\";\nimport type { AuthConfig } from \"./auth\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\n/** Manifest file schema (manifest.yml) */\nexport interface ScanManifest {\n /** Format version */\n version: \"2\";\n /** Human-readable scan name */\n name: string;\n /** Target URL */\n target: string;\n /** When the scan was recorded */\n recordedAt: string;\n /** Driver name */\n driver: string;\n /** Driver configuration */\n driverConfig: Record<string, unknown>;\n /** Auth configuration (optional) */\n auth?: {\n strategy: string;\n configFile?: string;\n stateFile?: string;\n loggedInIndicator?: string;\n loggedOutIndicator?: string;\n reAuthOn?: Array<Record<string, unknown>>;\n };\n /** Session file references */\n sessions: SessionRef[];\n /** Scan configuration */\n scan?: {\n tier?: \"auto\" | \"http-only\" | \"browser-only\";\n parallel?: number;\n timeout?: number;\n };\n}\n\n/** Reference to a session file within the manifest */\nexport interface SessionRef {\n /** Relative path to session file */\n file: string;\n /** Whether this session has injectable inputs */\n injectable?: boolean;\n}\n\n/** HTTP request metadata for Tier 1 fast scanning */\nexport interface CapturedRequest {\n /** Request method */\n method: string;\n /** Full URL */\n url: string;\n /** Request headers */\n headers: Record<string, string>;\n /** Form data (for POST) */\n body?: string;\n /** Content type */\n contentType?: string;\n /** Which form field is injectable */\n injectableField?: string;\n /** Session name this request belongs to */\n sessionName: string;\n}\n\n// ── Read ──────────────────────────────────────────────────────────────\n\n/**\n * Load a v2 session directory into Session[] ready for execution.\n *\n * @param dirPath - Path to the .vulcn/ directory\n * @returns Array of sessions with manifest metadata attached\n */\nexport async function loadSessionDir(dirPath: string): Promise<{\n manifest: ScanManifest;\n sessions: Session[];\n authConfig?: AuthConfig;\n}> {\n // Read manifest\n const manifestPath = join(dirPath, \"manifest.yml\");\n if (!existsSync(manifestPath)) {\n throw new Error(\n `No manifest.yml found in ${dirPath}. Is this a v2 session directory?`,\n );\n }\n\n const manifestYaml = await readFile(manifestPath, \"utf-8\");\n const manifest = parse(manifestYaml) as ScanManifest;\n\n if (manifest.version !== \"2\") {\n throw new Error(\n `Unsupported session format version: ${manifest.version}. Expected \"2\".`,\n );\n }\n\n // Read auth config if present\n let authConfig: AuthConfig | undefined;\n if (manifest.auth?.configFile) {\n const authPath = join(dirPath, manifest.auth.configFile);\n if (existsSync(authPath)) {\n const authYaml = await readFile(authPath, \"utf-8\");\n authConfig = parse(authYaml) as AuthConfig;\n }\n }\n\n // Read session files\n const sessions: Session[] = [];\n\n for (const ref of manifest.sessions) {\n // Skip non-injectable sessions (e.g., login-only sessions)\n if (ref.injectable === false) continue;\n\n const sessionPath = join(dirPath, ref.file);\n if (!existsSync(sessionPath)) {\n console.warn(`Session file not found: ${sessionPath}, skipping`);\n continue;\n }\n\n const sessionYaml = await readFile(sessionPath, \"utf-8\");\n const sessionData = parse(sessionYaml) as Record<string, unknown>;\n\n // Build full session with manifest-level driver config\n const session: Session = {\n name: (sessionData.name as string) ?? basename(ref.file, \".yml\"),\n driver: manifest.driver,\n driverConfig: {\n ...manifest.driverConfig,\n startUrl: resolveUrl(\n manifest.target,\n sessionData.page as string | undefined,\n ),\n },\n steps: (sessionData.steps as Session[\"steps\"]) ?? [],\n metadata: {\n recordedAt: manifest.recordedAt,\n version: \"2\",\n manifestDir: dirPath,\n },\n };\n\n sessions.push(session);\n }\n\n return { manifest, sessions, authConfig };\n}\n\n/**\n * Check if a path is a v2 session directory.\n */\nexport function isSessionDir(path: string): boolean {\n return existsSync(join(path, \"manifest.yml\"));\n}\n\n/**\n * Check if a path looks like a v2 session directory (by extension).\n */\nexport function looksLikeSessionDir(path: string): boolean {\n return path.endsWith(\".vulcn\") || path.endsWith(\".vulcn/\");\n}\n\n// ── Write ─────────────────────────────────────────────────────────────\n\n/**\n * Save sessions to a v2 session directory.\n *\n * Creates the directory structure:\n * <dirPath>/\n * ├── manifest.yml\n * ├── sessions/\n * │ ├── <session-name>.yml\n * │ └── ...\n * └── requests/ (if HTTP metadata provided)\n * └── ...\n */\nexport async function saveSessionDir(\n dirPath: string,\n options: {\n name: string;\n target: string;\n driver: string;\n driverConfig: Record<string, unknown>;\n sessions: Session[];\n authConfig?: AuthConfig;\n encryptedState?: string;\n requests?: CapturedRequest[];\n },\n): Promise<void> {\n // Create directory structure\n await mkdir(join(dirPath, \"sessions\"), { recursive: true });\n\n const sessionRefs: SessionRef[] = [];\n\n // Write individual session files\n for (const session of options.sessions) {\n const safeName = slugify(session.name);\n const fileName = `sessions/${safeName}.yml`;\n const sessionPath = join(dirPath, fileName);\n\n // Extract the page-relative URL if it starts with the target\n const startUrl = session.driverConfig.startUrl as string | undefined;\n const page = startUrl\n ? startUrl.replace(options.target, \"\").replace(/^\\//, \"/\")\n : undefined;\n\n const sessionData: Record<string, unknown> = {\n name: session.name,\n ...(page ? { page } : {}),\n steps: session.steps,\n };\n\n await writeFile(sessionPath, stringify(sessionData), \"utf-8\");\n\n // Check if session has injectable inputs\n const hasInjectable = session.steps.some(\n (s) =>\n s.type === \"browser.input\" &&\n (s as Record<string, unknown>).injectable !== false,\n );\n\n sessionRefs.push({\n file: fileName,\n injectable: hasInjectable,\n });\n }\n\n // Write auth config if provided\n if (options.authConfig) {\n await mkdir(join(dirPath, \"auth\"), { recursive: true });\n await writeFile(\n join(dirPath, \"auth\", \"config.yml\"),\n stringify(options.authConfig),\n \"utf-8\",\n );\n }\n\n // Write encrypted auth state if provided\n if (options.encryptedState) {\n await mkdir(join(dirPath, \"auth\"), { recursive: true });\n await writeFile(\n join(dirPath, \"auth\", \"state.enc\"),\n options.encryptedState,\n \"utf-8\",\n );\n }\n\n // Write HTTP request metadata if provided\n if (options.requests && options.requests.length > 0) {\n await mkdir(join(dirPath, \"requests\"), { recursive: true });\n for (const req of options.requests) {\n const safeName = slugify(req.sessionName);\n await writeFile(\n join(dirPath, \"requests\", `${safeName}.json`),\n JSON.stringify(req, null, 2),\n \"utf-8\",\n );\n }\n }\n\n // Write manifest\n const manifest: ScanManifest = {\n version: \"2\",\n name: options.name,\n target: options.target,\n recordedAt: new Date().toISOString(),\n driver: options.driver,\n driverConfig: options.driverConfig,\n ...(options.authConfig\n ? {\n auth: {\n strategy: options.authConfig.strategy,\n configFile: \"auth/config.yml\",\n stateFile: options.encryptedState ? \"auth/state.enc\" : undefined,\n loggedInIndicator: options.authConfig.loggedInIndicator,\n loggedOutIndicator: options.authConfig.loggedOutIndicator,\n },\n }\n : {}),\n sessions: sessionRefs,\n scan: {\n tier: \"auto\",\n parallel: 1,\n timeout: 120000,\n },\n };\n\n await writeFile(join(dirPath, \"manifest.yml\"), stringify(manifest), \"utf-8\");\n}\n\n/**\n * Read encrypted auth state from a session directory.\n */\nexport async function readAuthState(dirPath: string): Promise<string | null> {\n const statePath = join(dirPath, \"auth\", \"state.enc\");\n if (!existsSync(statePath)) return null;\n return readFile(statePath, \"utf-8\");\n}\n\n/**\n * Read captured HTTP requests from a session directory.\n */\nexport async function readCapturedRequests(\n dirPath: string,\n): Promise<CapturedRequest[]> {\n const requestsDir = join(dirPath, \"requests\");\n if (!existsSync(requestsDir)) return [];\n\n const files = await readdir(requestsDir);\n const requests: CapturedRequest[] = [];\n\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n const content = await readFile(join(requestsDir, file), \"utf-8\");\n requests.push(JSON.parse(content) as CapturedRequest);\n }\n\n return requests;\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Resolve a page path against a target URL.\n */\nfunction resolveUrl(target: string, page?: string): string {\n if (!page) return target;\n if (page.startsWith(\"http\")) return page;\n\n // Ensure target doesn't end with / and page starts with /\n const base = target.replace(/\\/$/, \"\");\n const path = page.startsWith(\"/\") ? page : `/${page}`;\n return `${base}${path}`;\n}\n\n/**\n * Convert a string to a safe filename slug.\n */\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60);\n}\n"],"mappings":"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","names":["pluginManager","resolve","isAbsolute","isAbsolute","resolve","readFile","existsSync","parse","stringify"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/project.ts","../src/driver-manager.ts","../src/errors.ts","../src/driver-types.ts","../src/plugin-manager.ts","../src/plugin-types.ts","../src/auth.ts","../src/payload-types.ts"],"sourcesContent":["/**\n * Vulcn Project Configuration — `.vulcn.yml` schema\n *\n * Single source of truth for all Vulcn configuration.\n * The CLI is a thin layer that passes this config to the engine.\n * Plugin names never appear here — the engine maps flat keys to plugins internally.\n */\n\nimport { z } from \"zod\";\n\n// ── Scan settings ─────────────────────────────────────────────────────\n\nconst ScanConfigSchema = z\n .object({\n /** Browser engine to use */\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).default(\"chromium\"),\n /** Run in headless mode */\n headless: z.boolean().default(true),\n /** Per-step timeout in ms */\n timeout: z.number().positive().default(30_000),\n })\n .default({});\n\n// ── Payload settings ──────────────────────────────────────────────────\n\nconst PayloadsConfigSchema = z\n .object({\n /** Payload types to use */\n types: z\n .array(z.enum([\"xss\", \"sqli\", \"xxe\", \"cmd\", \"redirect\", \"traversal\"]))\n .default([\"xss\"]),\n /** Opt-in to PayloadsAllTheThings community payloads */\n payloadbox: z.boolean().default(false),\n /** Max payloads per type from PayloadBox */\n limit: z.number().positive().default(100),\n /** Path to custom payload YAML file (relative to project root) */\n custom: z.string().nullable().default(null),\n })\n .default({});\n\n// ── Detection settings ────────────────────────────────────────────────\n\nconst XssDetectionSchema = z\n .object({\n /** Monitor alert/confirm/prompt dialogs */\n dialogs: z.boolean().default(true),\n /** Monitor console.log markers */\n console: z.boolean().default(true),\n /** Console marker prefix */\n consoleMarker: z.string().default(\"VULCN_XSS:\"),\n /** Check for injected <script> elements */\n domMutation: z.boolean().default(false),\n /** Finding severity level */\n severity: z.enum([\"critical\", \"high\", \"medium\", \"low\"]).default(\"high\"),\n /** Text patterns to match in alert messages */\n alertPatterns: z\n .array(z.string())\n .default([\n \"XSS\",\n \"1\",\n \"document.domain\",\n \"document.cookie\",\n \"vulcn\",\n \"pwned\",\n ]),\n })\n .default({});\n\nconst ReflectionSeveritySchema = z\n .object({\n script: z.enum([\"critical\", \"high\", \"medium\", \"low\"]).default(\"critical\"),\n attribute: z.enum([\"critical\", \"high\", \"medium\", \"low\"]).default(\"medium\"),\n body: z.enum([\"critical\", \"high\", \"medium\", \"low\"]).default(\"low\"),\n })\n .default({});\n\nconst ReflectionContextsSchema = z\n .object({\n script: z.boolean().default(true),\n attribute: z.boolean().default(true),\n body: z.boolean().default(true),\n })\n .default({});\n\nconst ReflectionDetectionSchema = z\n .object({\n /** Enable reflection detection */\n enabled: z.boolean().default(true),\n /** Minimum payload length to check */\n minLength: z.number().positive().default(4),\n /** Which HTML contexts to check for reflections */\n contexts: ReflectionContextsSchema,\n /** Severity per context */\n severity: ReflectionSeveritySchema,\n })\n .default({});\n\nconst DetectionConfigSchema = z\n .object({\n /** XSS detection settings */\n xss: XssDetectionSchema,\n /** Reflection detection settings */\n reflection: ReflectionDetectionSchema,\n /** Enable passive security checks (headers, cookies, info-disclosure) */\n passive: z.boolean().default(true),\n })\n .default({});\n\n// ── Crawl settings ────────────────────────────────────────────────────\n\nconst CrawlConfigSchema = z\n .object({\n /** Maximum crawl depth */\n depth: z.number().nonnegative().default(2),\n /** Maximum pages to visit */\n maxPages: z.number().positive().default(20),\n /** Stay on same origin */\n sameOrigin: z.boolean().default(true),\n /** Per-page timeout in ms */\n timeout: z.number().positive().default(10_000),\n })\n .default({});\n\n// ── Report settings ───────────────────────────────────────────────────\n\nconst ReportConfigSchema = z\n .object({\n /** Report format to generate */\n format: z\n .enum([\"html\", \"json\", \"yaml\", \"sarif\", \"all\"])\n .nullable()\n .default(null),\n })\n .default({});\n\n// ── Auth settings ─────────────────────────────────────────────────────\n\nconst FormAuthSchema = z.object({\n strategy: z.literal(\"form\"),\n /** Login page URL */\n loginUrl: z.string().url().optional(),\n /** CSS selector for username field */\n userSelector: z.string().nullable().default(null),\n /** CSS selector for password field */\n passSelector: z.string().nullable().default(null),\n});\n\nconst HeaderAuthSchema = z.object({\n strategy: z.literal(\"header\"),\n /** Headers to include in requests */\n headers: z.record(z.string()),\n});\n\nconst AuthConfigSchema = z\n .discriminatedUnion(\"strategy\", [FormAuthSchema, HeaderAuthSchema])\n .nullable()\n .default(null);\n\n// ── Root config ───────────────────────────────────────────────────────\n\nexport const VulcnProjectConfigSchema = z.object({\n /** Target URL to scan */\n target: z.string().url().optional(),\n\n /** Scan settings (browser, headless, timeout) */\n scan: ScanConfigSchema,\n\n /** Payload configuration */\n payloads: PayloadsConfigSchema,\n\n /** Detection configuration */\n detection: DetectionConfigSchema,\n\n /** Crawl configuration */\n crawl: CrawlConfigSchema,\n\n /** Report configuration */\n report: ReportConfigSchema,\n\n /** Authentication configuration */\n auth: AuthConfigSchema,\n});\n\n/** Parsed and validated project config */\nexport type VulcnProjectConfig = z.infer<typeof VulcnProjectConfigSchema>;\n\n// ── Helpers ───────────────────────────────────────────────────────────\n\n/**\n * Parse and validate a raw config object (from YAML.parse).\n * All fields have defaults, so an empty object is valid.\n */\nexport function parseProjectConfig(raw: unknown): VulcnProjectConfig {\n return VulcnProjectConfigSchema.parse(raw);\n}\n\n/**\n * Default config for `vulcn init`.\n * Only includes fields that users are likely to customize.\n */\nexport const DEFAULT_PROJECT_CONFIG = {\n target: \"https://example.com\",\n\n scan: {\n browser: \"chromium\",\n headless: true,\n timeout: 30000,\n },\n\n payloads: {\n types: [\"xss\"],\n },\n\n detection: {\n xss: {\n dialogs: true,\n console: true,\n domMutation: false,\n severity: \"high\",\n },\n reflection: {\n enabled: true,\n },\n passive: true,\n },\n\n crawl: {\n depth: 2,\n maxPages: 20,\n sameOrigin: true,\n },\n\n report: {\n format: \"html\",\n },\n} as const;\n","/**\n * Vulcn Project Discovery & Resolution\n *\n * Finds `.vulcn.yml` by walking up from `cwd`, parses the config,\n * and resolves convention-based paths (sessions/, auth/, reports/).\n */\n\nimport { readFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve, dirname, join } from \"node:path\";\nimport YAML from \"yaml\";\nimport { parseProjectConfig, type VulcnProjectConfig } from \"./config\";\n\n// ── Constants ─────────────────────────────────────────────────────────\n\n/** Config filename — presence marks a directory as a Vulcn project */\nexport const CONFIG_FILENAME = \".vulcn.yml\";\n\n/** Convention-based subdirectory names */\nexport const DIRS = {\n sessions: \"sessions\",\n auth: \"auth\",\n reports: \"reports\",\n} as const;\n\n// ── Types ─────────────────────────────────────────────────────────────\n\n/** Resolved project paths */\nexport interface ProjectPaths {\n /** Absolute path to the project root (directory containing .vulcn.yml) */\n root: string;\n /** Absolute path to .vulcn.yml */\n config: string;\n /** Absolute path to sessions/ directory */\n sessions: string;\n /** Absolute path to auth/ directory */\n auth: string;\n /** Absolute path to reports/ directory */\n reports: string;\n}\n\n/** Loaded project — config + paths */\nexport interface VulcnProject {\n /** Parsed and validated config */\n config: VulcnProjectConfig;\n /** Resolved absolute paths */\n paths: ProjectPaths;\n}\n\n// ── Discovery ─────────────────────────────────────────────────────────\n\n/**\n * Find the project root by walking up from `startDir` looking for `.vulcn.yml`.\n *\n * @param startDir - Directory to start searching from (default: `cwd()`)\n * @returns Absolute path to the project root, or `null` if not found\n */\nexport function findProjectRoot(startDir?: string): string | null {\n let dir = resolve(startDir ?? process.cwd());\n\n // Walk up the directory tree\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n return dir;\n }\n\n const parent = dirname(dir);\n if (parent === dir) {\n // Reached filesystem root\n return null;\n }\n dir = parent;\n }\n}\n\n/**\n * Resolve convention-based paths from a project root.\n */\nexport function resolveProjectPaths(root: string): ProjectPaths {\n return {\n root,\n config: join(root, CONFIG_FILENAME),\n sessions: join(root, DIRS.sessions),\n auth: join(root, DIRS.auth),\n reports: join(root, DIRS.reports),\n };\n}\n\n// ── Loading ───────────────────────────────────────────────────────────\n\n/**\n * Load a Vulcn project from a directory.\n *\n * Finds `.vulcn.yml`, parses it, validates with Zod, and resolves paths.\n *\n * @param startDir - Directory to start searching from (default: `cwd()`)\n * @throws If `.vulcn.yml` is not found or invalid\n */\nexport async function loadProject(startDir?: string): Promise<VulcnProject> {\n const root = findProjectRoot(startDir);\n\n if (!root) {\n throw new Error(\n `No ${CONFIG_FILENAME} found. Run \\`vulcn init\\` to create one.`,\n );\n }\n\n const paths = resolveProjectPaths(root);\n const raw = await readFile(paths.config, \"utf-8\");\n\n let parsed: unknown;\n try {\n parsed = YAML.parse(raw);\n } catch (err) {\n throw new Error(\n `Invalid YAML in ${paths.config}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Empty file → empty object (all defaults)\n if (parsed === null || parsed === undefined) {\n parsed = {};\n }\n\n const config = parseProjectConfig(parsed);\n\n return { config, paths };\n}\n\n/**\n * Load project config from a specific file path (no discovery).\n * Useful for testing or when the path is already known.\n */\nexport async function loadProjectFromFile(\n configPath: string,\n): Promise<VulcnProject> {\n const absPath = resolve(configPath);\n const root = dirname(absPath);\n const paths = resolveProjectPaths(root);\n\n const raw = await readFile(absPath, \"utf-8\");\n\n let parsed: unknown;\n try {\n parsed = YAML.parse(raw);\n } catch (err) {\n throw new Error(\n `Invalid YAML in ${absPath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (parsed === null || parsed === undefined) {\n parsed = {};\n }\n\n const config = parseProjectConfig(parsed);\n\n return { config, paths };\n}\n\n// ── Initialization ────────────────────────────────────────────────────\n\n/**\n * Ensure convention directories exist (sessions/, auth/, reports/).\n * Called during init and before operations that write to these dirs.\n */\nexport async function ensureProjectDirs(\n paths: ProjectPaths,\n dirs: Array<keyof typeof DIRS> = [\"sessions\"],\n): Promise<void> {\n for (const dir of dirs) {\n const dirPath = paths[dir];\n if (!existsSync(dirPath)) {\n await mkdir(dirPath, { recursive: true });\n }\n }\n}\n","/**\n * Vulcn Driver Manager\n *\n * Handles driver loading, registration, and lifecycle.\n * Drivers are loaded from npm packages or local files.\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\nexport const { version: ENGINE_VERSION } = require(\"../package.json\");\nimport { parse, stringify } from \"yaml\";\nimport type {\n VulcnDriver,\n LoadedDriver,\n DriverSource,\n Session,\n RunContext,\n RunResult,\n RunOptions,\n RecordOptions,\n CrawlOptions,\n RecordingHandle,\n DriverLogger,\n DRIVER_API_VERSION,\n} from \"./driver-types\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { ScanContext } from \"./plugin-types\";\nimport { ErrorSeverity, VulcnError } from \"./errors\";\n\n/**\n * Driver Manager - loads and manages recording/running drivers\n */\nexport class DriverManager {\n private drivers: Map<string, LoadedDriver> = new Map();\n private defaultDriver: string | null = null;\n\n /**\n * Register a driver\n */\n register(driver: VulcnDriver, source: DriverSource = \"builtin\"): void {\n this.validateDriver(driver);\n this.drivers.set(driver.name, { driver, source });\n\n // First registered driver becomes default\n if (this.drivers.size === 1) {\n this.defaultDriver = driver.name;\n }\n }\n\n /**\n * Load a driver from npm or local path\n */\n async load(nameOrPath: string): Promise<void> {\n let driver: VulcnDriver;\n let source: DriverSource;\n\n if (\n nameOrPath.startsWith(\"./\") ||\n nameOrPath.startsWith(\"../\") ||\n isAbsolute(nameOrPath)\n ) {\n // Local file\n const resolved = isAbsolute(nameOrPath)\n ? nameOrPath\n : resolve(process.cwd(), nameOrPath);\n const module = await import(resolved);\n driver = module.default || module;\n source = \"local\";\n } else {\n // npm package\n const module = await import(nameOrPath);\n driver = module.default || module;\n source = \"npm\";\n }\n\n this.register(driver, source);\n }\n\n /**\n * Get a loaded driver by name\n */\n get(name: string): VulcnDriver | undefined {\n return this.drivers.get(name)?.driver;\n }\n\n /**\n * Get the default driver\n */\n getDefault(): VulcnDriver | undefined {\n if (!this.defaultDriver) return undefined;\n return this.get(this.defaultDriver);\n }\n\n /**\n * Set the default driver\n */\n setDefault(name: string): void {\n if (!this.drivers.has(name)) {\n throw new Error(`Driver \"${name}\" is not registered`);\n }\n this.defaultDriver = name;\n }\n\n /**\n * Check if a driver is registered\n */\n has(name: string): boolean {\n return this.drivers.has(name);\n }\n\n /**\n * Get all registered drivers\n */\n list(): LoadedDriver[] {\n return Array.from(this.drivers.values());\n }\n\n /**\n * Get driver for a session\n */\n getForSession(session: Session): VulcnDriver {\n const driverName = session.driver;\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(\n `Driver \"${driverName}\" not found. Install @vulcn/driver-${driverName} or load it manually.`,\n );\n }\n\n return driver;\n }\n\n /**\n * Parse a YAML session string into a Session object.\n *\n * Sessions must use the driver format with a `driver` field.\n *\n * @param yaml - Raw YAML string\n */\n parseSession(yaml: string): Session {\n const data = parse(yaml) as Record<string, unknown>;\n\n if (!data.driver || typeof data.driver !== \"string\") {\n throw new Error(\n \"Invalid session format: missing 'driver' field. Sessions must use the driver format.\",\n );\n }\n\n return data as unknown as Session;\n }\n\n /**\n * Start recording with a driver\n */\n async startRecording(\n driverName: string,\n config: Record<string, unknown>,\n options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n return driver.recorder.start(config, options);\n }\n\n /**\n * Auto-crawl a URL using a driver.\n *\n * Uses the driver's optional crawl() method to automatically\n * discover forms and injection points, returning Session[] that\n * can be passed to execute().\n *\n * Not all drivers support this — only browser has crawl capability.\n * CLI and API drivers will throw.\n */\n async crawl(\n driverName: string,\n config: Record<string, unknown>,\n options: CrawlOptions = {},\n ): Promise<Session[]> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n if (!driver.recorder.crawl) {\n throw new Error(\n `Driver \"${driverName}\" does not support auto-crawl. Use manual recording instead.`,\n );\n }\n\n return driver.recorder.crawl(config, options);\n }\n\n /**\n * Execute a session\n * Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.\n * Plugin onRunStart is deferred until the driver signals the page is ready\n * via the onPageReady callback, ensuring plugins get a real page object.\n */\n async execute(\n session: Session,\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<RunResult> {\n const driver = this.getForSession(session);\n const findings: Finding[] = [];\n const logger = this.createLogger(driver.name);\n\n // Shared addFinding function — used by both internal RunContext and\n // plugin context. Ensures all findings (active + passive) flow through\n // the onFinding callback so consumers get notified consistently.\n const addFinding = (finding: Finding) => {\n findings.push(finding);\n pluginManager.addFinding(finding);\n options.onFinding?.(finding);\n };\n\n // Build a plugin context template for hooks (page is set in onPageReady)\n const pluginCtx = {\n session,\n page: null as unknown,\n headless: !!(options as Record<string, unknown>).headless,\n config: {} as Record<string, unknown>,\n engine: { version: ENGINE_VERSION, pluginApiVersion: 1 },\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n errors: pluginManager.getErrorHandler(),\n fetch: globalThis.fetch,\n };\n\n const ctx: RunContext = {\n session,\n pluginManager,\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n errors: pluginManager.getErrorHandler(),\n options: {\n ...options,\n // Provide onPageReady callback — fires plugin onRunStart hooks\n // with the real page object once the driver has created it\n onPageReady: async (page: unknown) => {\n pluginCtx.page = page;\n\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {\n try {\n await loaded.plugin.hooks.onRunStart({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n pluginManager.getErrorHandler().catch(err, {\n severity: ErrorSeverity.ERROR,\n source: `plugin:${loaded.plugin.name}`,\n context: { hook: \"onRunStart\" },\n });\n }\n }\n }\n },\n // Fires before browser closes — lets plugins flush pending async work\n onBeforeClose: async (_page: unknown) => {\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onBeforeClose) {\n try {\n await loaded.plugin.hooks.onBeforeClose({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n pluginManager.getErrorHandler().catch(err, {\n severity: ErrorSeverity.WARN,\n source: `plugin:${loaded.plugin.name}`,\n context: { hook: \"onBeforeClose\" },\n });\n }\n }\n }\n },\n },\n };\n\n // Execute via driver runner\n // (runner calls ctx.options.onPageReady(page) after creating the page)\n let result = await driver.runner.execute(session, ctx);\n\n // Call onRunEnd hooks (e.g., report generation)\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {\n try {\n result = await loaded.plugin.hooks.onRunEnd(result, {\n ...pluginCtx,\n config: loaded.config,\n findings: result.findings,\n });\n } catch (err) {\n // onRunEnd is FATAL — report generation lives here.\n pluginManager.getErrorHandler().catch(err, {\n severity: ErrorSeverity.FATAL,\n source: `plugin:${loaded.plugin.name}`,\n context: { hook: \"onRunEnd\" },\n });\n }\n }\n }\n\n return result;\n }\n\n /**\n * Execute multiple sessions with a shared browser (scan-level orchestration).\n *\n * This is the preferred entry point for running a full scan. It:\n * 1. Launches ONE browser for the entire scan\n * 2. Passes the browser to each session's runner via options.browser\n * 3. Each session creates its own context (lightweight, isolated cookies)\n * 4. Aggregates results across all sessions\n * 5. Closes the browser once at the end\n *\n * This is 5-10x faster than calling execute() per session because\n * launching a browser takes 2-3 seconds.\n */\n async executeScan(\n sessions: Session[],\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<{\n results: RunResult[];\n aggregate: RunResult;\n }> {\n if (sessions.length === 0) {\n const empty: RunResult = {\n findings: [],\n stepsExecuted: 0,\n payloadsTested: 0,\n duration: 0,\n errors: [\"No sessions to execute\"],\n };\n return { results: [], aggregate: empty };\n }\n\n const startTime = Date.now();\n const results: RunResult[] = [];\n const allFindings: Finding[] = [];\n let totalSteps = 0;\n let totalPayloads = 0;\n const allErrors: string[] = [];\n\n // Launch shared browser via the first session's driver\n // (all sessions should use the same driver in a scan)\n const firstDriver = this.getForSession(sessions[0]);\n let sharedBrowser: unknown = null;\n\n // Only share browser/resource if the driver supports it\n if (typeof firstDriver.createSharedResource === \"function\") {\n try {\n // Use the first session's config as the baseline for the shared resource\n const driverConfig = sessions[0].driverConfig;\n sharedBrowser = await firstDriver.createSharedResource(\n driverConfig,\n options,\n );\n } catch (err) {\n // Can't share resource — warn and fall back to per-session launches\n pluginManager.getErrorHandler().catch(err, {\n severity: ErrorSeverity.WARN,\n source: `driver-manager:${firstDriver.name}`,\n context: { action: \"create-shared-resource\" },\n });\n }\n }\n\n try {\n // Auto-init plugins if not already initialized (idempotent)\n await pluginManager.initialize();\n\n // Fire onScanStart hooks\n await pluginManager.callHook(\"onScanStart\", async (hook, ctx) => {\n const scanCtx: ScanContext = {\n ...ctx,\n sessions,\n headless: options.headless ?? true,\n sessionCount: sessions.length,\n };\n await (hook as (ctx: ScanContext) => Promise<void>)(scanCtx);\n });\n\n for (let i = 0; i < sessions.length; i++) {\n const session = sessions[i];\n\n // Clear per-session state so findings don't leak across sessions\n pluginManager.clearFindings();\n\n // Notify consumers before session starts\n options.onSessionStart?.(session, i, sessions.length);\n\n const sessionOptions: RunOptions = {\n ...options,\n ...(sharedBrowser ? { browser: sharedBrowser } : {}),\n };\n\n let result: RunResult;\n\n if (options.timeout && options.timeout > 0) {\n // Race execution against timeout\n const execPromise = this.execute(\n session,\n pluginManager,\n sessionOptions,\n );\n const timeoutPromise = new Promise<RunResult>((_, reject) =>\n setTimeout(\n () =>\n reject(\n new Error(\n `Session \"${session.name}\" timed out after ${options.timeout}ms`,\n ),\n ),\n options.timeout,\n ),\n );\n\n try {\n result = await Promise.race([execPromise, timeoutPromise]);\n } catch (err) {\n // Timeout or execution error — record as failed session\n result = {\n findings: [],\n stepsExecuted: 0,\n payloadsTested: 0,\n duration: options.timeout!,\n errors: [err instanceof Error ? err.message : String(err)],\n };\n }\n\n // Prevent unhandled rejection from the losing promise in the race.\n // When timeout wins, execute() is still running — its eventual\n // rejection must be absorbed or Node exits with code 1.\n execPromise.catch(() => {});\n } else {\n try {\n result = await this.execute(session, pluginManager, sessionOptions);\n } catch (err) {\n // Execution error — record as failed session, continue with next\n result = {\n findings: [],\n stepsExecuted: 0,\n payloadsTested: 0,\n duration: 0,\n errors: [err instanceof Error ? err.message : String(err)],\n };\n }\n }\n\n results.push(result);\n allFindings.push(...result.findings);\n totalSteps += result.stepsExecuted;\n totalPayloads += result.payloadsTested;\n allErrors.push(...result.errors);\n\n // Notify consumers after session ends\n options.onSessionEnd?.(session, result, i, sessions.length);\n }\n } finally {\n // Close the shared browser\n if (\n sharedBrowser &&\n typeof (sharedBrowser as { close: () => Promise<void> }).close ===\n \"function\"\n ) {\n await (sharedBrowser as { close: () => Promise<void> }).close();\n }\n }\n\n const aggregate: RunResult = {\n findings: allFindings,\n stepsExecuted: totalSteps,\n payloadsTested: totalPayloads,\n duration: Date.now() - startTime,\n errors: allErrors,\n };\n\n // Fire onScanEnd hooks — allows plugins to transform the aggregate result\n // This MUST run even if sessions failed — it's how the report gets written.\n let finalAggregate = aggregate;\n finalAggregate = await pluginManager.callHookPipe(\n \"onScanEnd\",\n finalAggregate,\n async (hook, value, ctx) => {\n const scanCtx: ScanContext = {\n ...ctx,\n sessions,\n headless: options.headless ?? true,\n sessionCount: sessions.length,\n };\n return await (\n hook as (result: RunResult, ctx: ScanContext) => Promise<RunResult>\n )(value, scanCtx);\n },\n );\n\n return { results, aggregate: finalAggregate };\n }\n\n /**\n * Validate driver structure\n */\n private validateDriver(driver: unknown): asserts driver is VulcnDriver {\n if (!driver || typeof driver !== \"object\") {\n throw new Error(\"Driver must be an object\");\n }\n\n const d = driver as Record<string, unknown>;\n\n if (typeof d.name !== \"string\" || !d.name) {\n throw new Error(\"Driver must have a name\");\n }\n\n if (typeof d.version !== \"string\" || !d.version) {\n throw new Error(\"Driver must have a version\");\n }\n\n if (!Array.isArray(d.stepTypes) || d.stepTypes.length === 0) {\n throw new Error(\"Driver must define stepTypes\");\n }\n\n if (!d.recorder || typeof d.recorder !== \"object\") {\n throw new Error(\"Driver must have a recorder\");\n }\n\n if (!d.runner || typeof d.runner !== \"object\") {\n throw new Error(\"Driver must have a runner\");\n }\n }\n\n /**\n * Create a scoped logger for a driver\n */\n private createLogger(name: string): DriverLogger {\n const prefix = `[driver:${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n}\n\n/**\n * Default driver manager instance\n */\nexport const driverManager = new DriverManager();\n","/**\n * Vulcn Error System\n *\n * Centralized error handling with severity classification.\n * Components emit errors here instead of making local catch/swallow decisions.\n *\n * Severity levels:\n * FATAL — Stop execution immediately. The operation cannot continue.\n * Examples: report plugin fails to write, payload loading fails,\n * driver can't launch browser.\n *\n * ERROR — Something broke but execution can continue. Record it.\n * Examples: a single session times out, a plugin hook fails\n * on a non-critical lifecycle event.\n *\n * WARN — Expected or recoverable. Log and move on.\n * Examples: optional plugin not installed, browser page navigation\n * intermittently fails, auth state not found.\n */\n\n// ── Error Severity ─────────────────────────────────────────────────────\n\nexport enum ErrorSeverity {\n /** Stop execution — unrecoverable */\n FATAL = \"fatal\",\n /** Record and continue — something broke but others can proceed */\n ERROR = \"error\",\n /** Log and move on — expected or minor */\n WARN = \"warn\",\n}\n\n// ── Typed Error ────────────────────────────────────────────────────────\n\nexport class VulcnError extends Error {\n readonly severity: ErrorSeverity;\n readonly source: string;\n readonly context?: Record<string, unknown>;\n readonly timestamp: string;\n\n constructor(\n message: string,\n options: {\n severity: ErrorSeverity;\n source: string;\n cause?: unknown;\n context?: Record<string, unknown>;\n },\n ) {\n super(message, { cause: options.cause });\n this.name = \"VulcnError\";\n this.severity = options.severity;\n this.source = options.source;\n this.context = options.context;\n this.timestamp = new Date().toISOString();\n }\n\n /**\n * Wrap any caught error into a VulcnError.\n * If it's already a VulcnError, returns it as-is.\n */\n static from(\n err: unknown,\n defaults: {\n severity: ErrorSeverity;\n source: string;\n context?: Record<string, unknown>;\n },\n ): VulcnError {\n if (err instanceof VulcnError) return err;\n\n const message = err instanceof Error ? err.message : String(err);\n\n return new VulcnError(message, {\n severity: defaults.severity,\n source: defaults.source,\n cause: err,\n context: defaults.context,\n });\n }\n}\n\n// ── Convenience constructors ───────────────────────────────────────────\n\nexport function fatal(\n message: string,\n source: string,\n options?: { cause?: unknown; context?: Record<string, unknown> },\n): VulcnError {\n return new VulcnError(message, {\n severity: ErrorSeverity.FATAL,\n source,\n ...options,\n });\n}\n\nexport function error(\n message: string,\n source: string,\n options?: { cause?: unknown; context?: Record<string, unknown> },\n): VulcnError {\n return new VulcnError(message, {\n severity: ErrorSeverity.ERROR,\n source,\n ...options,\n });\n}\n\nexport function warn(\n message: string,\n source: string,\n options?: { cause?: unknown; context?: Record<string, unknown> },\n): VulcnError {\n return new VulcnError(message, {\n severity: ErrorSeverity.WARN,\n source,\n ...options,\n });\n}\n\n// ── Error Handler ──────────────────────────────────────────────────────\n\nexport type ErrorListener = (error: VulcnError) => void;\n\n/**\n * Central error handler for the Vulcn engine.\n *\n * - FATAL errors throw immediately (halt execution)\n * - ERROR errors are recorded and logged\n * - WARN errors are logged only\n *\n * At the end of a run/scan, call `getSummary()` to see everything that went wrong.\n */\nexport class ErrorHandler {\n private errors: VulcnError[] = [];\n private listeners: ErrorListener[] = [];\n\n /**\n * Handle an error based on its severity.\n *\n * - FATAL: logs, records, then THROWS (caller must not catch silently)\n * - ERROR: logs and records\n * - WARN: logs only\n */\n handle(err: VulcnError): void {\n // Always record\n this.errors.push(err);\n\n // Notify listeners (e.g., for streaming results to UI)\n for (const listener of this.listeners) {\n try {\n listener(err);\n } catch {\n // Listener errors must not cascade\n }\n }\n\n // Log based on severity\n const ctx = err.context ? ` ${JSON.stringify(err.context)}` : \"\";\n\n switch (err.severity) {\n case ErrorSeverity.FATAL:\n console.error(`❌ FATAL [${err.source}] ${err.message}${ctx}`);\n if (err.cause instanceof Error) {\n console.error(` Caused by: ${err.cause.message}`);\n }\n throw err; // ← This is the whole point. FATAL stops execution.\n\n case ErrorSeverity.ERROR:\n console.error(`⚠️ ERROR [${err.source}] ${err.message}${ctx}`);\n break;\n\n case ErrorSeverity.WARN:\n console.warn(`⚡ WARN [${err.source}] ${err.message}${ctx}`);\n break;\n }\n }\n\n /**\n * Convenience: wrap a caught error and handle it.\n */\n catch(\n err: unknown,\n defaults: {\n severity: ErrorSeverity;\n source: string;\n context?: Record<string, unknown>;\n },\n ): void {\n this.handle(VulcnError.from(err, defaults));\n }\n\n // ── Query ──────────────────────────────────────────────────────────\n\n /** All recorded errors (FATAL + ERROR + WARN) */\n getAll(): VulcnError[] {\n return [...this.errors];\n }\n\n /** Only ERROR and FATAL */\n getErrors(): VulcnError[] {\n return this.errors.filter(\n (e) =>\n e.severity === ErrorSeverity.ERROR ||\n e.severity === ErrorSeverity.FATAL,\n );\n }\n\n /** Were there any errors (not just warnings)? */\n hasErrors(): boolean {\n return this.errors.some(\n (e) =>\n e.severity === ErrorSeverity.ERROR ||\n e.severity === ErrorSeverity.FATAL,\n );\n }\n\n /** Count by severity */\n counts(): Record<ErrorSeverity, number> {\n const counts = {\n [ErrorSeverity.FATAL]: 0,\n [ErrorSeverity.ERROR]: 0,\n [ErrorSeverity.WARN]: 0,\n };\n for (const e of this.errors) {\n counts[e.severity]++;\n }\n return counts;\n }\n\n /** Human-readable summary for end-of-run reporting */\n getSummary(): string {\n if (this.errors.length === 0) return \"No errors.\";\n\n const c = this.counts();\n const lines: string[] = [\n `Error Summary: ${c.fatal} fatal, ${c.error} errors, ${c.warn} warnings`,\n ];\n\n for (const e of this.errors) {\n const icon =\n e.severity === ErrorSeverity.FATAL\n ? \"❌\"\n : e.severity === ErrorSeverity.ERROR\n ? \"⚠️ \"\n : \"⚡\";\n lines.push(` ${icon} [${e.source}] ${e.message}`);\n }\n\n return lines.join(\"\\n\");\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────\n\n /** Subscribe to errors as they happen */\n onError(listener: ErrorListener): () => void {\n this.listeners.push(listener);\n return () => {\n this.listeners = this.listeners.filter((l) => l !== listener);\n };\n }\n\n /** Reset for a new run */\n clear(): void {\n this.errors = [];\n }\n}\n","/**\n * Vulcn Driver System\n *\n * Drivers handle recording and running sessions for different targets:\n * - browser: Web applications (Playwright)\n * - api: REST/HTTP APIs\n * - cli: Command-line tools\n *\n * Each driver implements RecorderDriver and RunnerDriver interfaces.\n */\n\nimport type { z } from \"zod\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport type { ErrorHandler } from \"./errors\";\n\n/**\n * Current driver API version\n */\nexport const DRIVER_API_VERSION = 1;\n\n/**\n * Generic step - drivers define their own step types\n */\nexport interface Step {\n /** Unique step ID */\n id: string;\n\n /** Step type (namespaced, e.g., \"browser.click\", \"api.request\") */\n type: string;\n\n /** Timestamp when step was recorded */\n timestamp: number;\n\n /** Step-specific data */\n [key: string]: unknown;\n}\n\n/**\n * Generic session format\n */\nexport interface Session {\n /** Session name */\n name: string;\n\n /** Driver that recorded this session */\n driver: string;\n\n /** Driver-specific configuration */\n driverConfig: Record<string, unknown>;\n\n /** Recorded steps */\n steps: Step[];\n\n /** Session metadata */\n metadata?: {\n recordedAt?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Recording context passed to drivers\n */\nexport interface RecordContext {\n /** Session being built */\n session: Partial<Session>;\n\n /** Add a step to the session */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n\n /** Logger */\n logger: DriverLogger;\n}\n\n/**\n * Running context passed to drivers\n */\nexport interface RunContext {\n /** Session being executed */\n session: Session;\n\n /** Plugin manager for calling hooks */\n pluginManager: PluginManager;\n\n /** Available payloads */\n payloads: RuntimePayload[];\n\n /** Collected findings */\n findings: Finding[];\n\n /** Add a finding */\n addFinding(finding: Finding): void;\n\n /** Logger */\n logger: DriverLogger;\n\n /**\n * Centralized error handler.\n * Drivers MUST use this to surface errors:\n * ctx.errors.fatal(\"session data malformed\", \"driver:browser\")\n * ctx.errors.warn(\"page timeout\", \"driver:browser\")\n */\n errors: ErrorHandler;\n\n /** Running options */\n options: RunOptions;\n}\n\n/**\n * Options for recording\n */\nexport interface RecordOptions {\n /** Enable auto-crawl mode (driver discovers forms automatically) */\n auto?: boolean;\n\n /** Crawl options (only used when auto=true) */\n crawlOptions?: CrawlOptions;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Options for auto-crawl mode\n *\n * When a driver supports crawling, these options control how\n * the automated discovery works. Not all drivers support crawling —\n * it's optional and primarily used by the browser driver.\n */\nexport interface CrawlOptions {\n /** Maximum crawl depth (0 = only the given URL, default: 2) */\n maxDepth?: number;\n\n /** Maximum number of pages to visit (default: 20) */\n maxPages?: number;\n\n /** Timeout per page navigation in ms (default: 10000) */\n pageTimeout?: number;\n\n /** Only crawl pages under the same origin (default: true) */\n sameOrigin?: boolean;\n\n /** Playwright storage state JSON for authenticated crawling */\n storageState?: string;\n\n /** Callback when a page is crawled */\n onPageCrawled?: (url: string, formsFound: number) => void;\n}\n\n/**\n * Options for running\n */\nexport interface RunOptions {\n /** Run headless (for visual drivers) */\n headless?: boolean;\n\n /** Callback for findings */\n onFinding?: (finding: Finding) => void;\n\n /** Callback for step completion */\n onStepComplete?: (stepId: string, payloadCount: number) => void;\n\n /**\n * Called by executeScan before each session starts.\n * Provides the session name, index, and total count for progress tracking.\n */\n onSessionStart?: (session: Session, index: number, total: number) => void;\n\n /**\n * Called by executeScan after each session completes.\n * Provides the result for that session.\n */\n onSessionEnd?: (\n session: Session,\n result: RunResult,\n index: number,\n total: number,\n ) => void;\n\n /**\n * Called by the driver runner after the page/environment is ready.\n * The driver-manager uses this to fire plugin onRunStart hooks\n * with the real page object (instead of null).\n */\n onPageReady?: (page: unknown) => Promise<void>;\n\n /**\n * Called by the driver runner before closing the browser/environment.\n * The driver-manager uses this to fire plugin onBeforeClose hooks\n * so plugins can flush pending async work.\n */\n onBeforeClose?: (page: unknown) => Promise<void>;\n\n /**\n * Per-session timeout in milliseconds.\n * If a session exceeds this duration, it will be aborted with a timeout error.\n * Both CLI and Worker benefit from this when using `executeScan`.\n */\n timeout?: number;\n\n // ── Browser driver options ─────────────────────────────────────────\n\n /** Shared browser instance (passed by executeScan for persistent mode) */\n browser?: unknown;\n\n /** JSON-stringified browser storage state (cookies, localStorage) for authenticated scans */\n storageState?: string;\n\n /** Extra HTTP headers to inject into every request (for header-based auth) */\n extraHeaders?: Record<string, string>;\n\n /** Allow additional driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Run result\n */\nexport interface RunResult {\n /** All findings */\n findings: Finding[];\n\n /** Steps executed */\n stepsExecuted: number;\n\n /** Payloads tested */\n payloadsTested: number;\n\n /** Duration in milliseconds */\n duration: number;\n\n /** Errors encountered */\n errors: string[];\n}\n\n/**\n * Driver logger\n */\nexport interface DriverLogger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\n/**\n * Recorder Driver Interface\n *\n * Implement this to add recording support for a target type.\n */\nexport interface RecorderDriver {\n /** Start recording and return control handle */\n start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle>;\n\n /**\n * Auto-crawl a URL and generate sessions.\n *\n * Optional — only drivers that support automated discovery\n * (e.g., browser) implement this. CLI and API drivers do not.\n *\n * When options.auto=true is passed to startRecording, the engine\n * calls this instead of start().\n */\n crawl?(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]>;\n}\n\n/**\n * Handle returned by RecorderDriver.start()\n */\nexport interface RecordingHandle {\n /** Stop recording and return the session */\n stop(): Promise<Session>;\n\n /** Abort recording without saving */\n abort(): Promise<void>;\n\n /** Get current steps (during recording) */\n getSteps(): Step[];\n\n /** Manually add a step */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n}\n\n/**\n * Runner Driver Interface\n *\n * Implement this to add running/replay support for a target type.\n */\nexport interface RunnerDriver {\n /** Execute a session with payloads */\n execute(session: Session, ctx: RunContext): Promise<RunResult>;\n}\n\n/**\n * Complete driver definition\n */\nexport interface VulcnDriver {\n /** Unique driver name (e.g., \"browser\", \"api\", \"cli\") */\n name: string;\n\n /** Driver version */\n version: string;\n\n /** Driver API version */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Configuration schema (Zod) */\n configSchema?: z.ZodSchema;\n\n /** Step types this driver handles */\n stepTypes: string[];\n\n /** Recorder implementation */\n recorder: RecorderDriver;\n\n /** Runner implementation */\n runner: RunnerDriver;\n\n /**\n * Create a shared resource (e.g., a browser instance) that can be\n * passed to execute() via ctx.options.\n *\n * Used by executeScan() to improve performance by reusing resources\n * across multiple sessions.\n */\n createSharedResource?: (\n config: Record<string, unknown>,\n options: RunOptions,\n ) => Promise<unknown>;\n}\n\n/**\n * Driver source for loading\n */\nexport type DriverSource = \"npm\" | \"local\" | \"builtin\";\n\n/**\n * Loaded driver with metadata\n */\nexport interface LoadedDriver {\n driver: VulcnDriver;\n source: DriverSource;\n}\n","/**\n * Vulcn Plugin Manager\n * Handles plugin loading, lifecycle, and hook execution.\n *\n * The primary entry point is `loadFromConfig(config)` which takes\n * a flat `VulcnProjectConfig` (from `.vulcn.yml`) and maps it to\n * internal plugin configs automatically.\n */\n\nimport { createRequire } from \"node:module\";\nimport type {\n VulcnPlugin,\n LoadedPlugin,\n PluginContext,\n PluginSource,\n PluginLogger,\n EngineInfo,\n PluginHooks,\n} from \"./plugin-types\";\nimport { PLUGIN_API_VERSION } from \"./plugin-types\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport { ErrorHandler, ErrorSeverity, VulcnError } from \"./errors\";\nimport type { VulcnProjectConfig } from \"./config\";\n\nconst _require = createRequire(import.meta.url);\nconst { version: ENGINE_VERSION } = _require(\"../package.json\");\n\n/**\n * Plugin Manager - loads, configures, and orchestrates plugins\n */\nexport class PluginManager {\n private plugins: LoadedPlugin[] = [];\n private initialized = false;\n private errorHandler: ErrorHandler;\n\n /**\n * Shared context passed to all plugins\n */\n private sharedPayloads: RuntimePayload[] = [];\n private sharedFindings: Finding[] = [];\n\n constructor(errorHandler?: ErrorHandler) {\n this.errorHandler = errorHandler ?? new ErrorHandler();\n }\n\n /** Get the error handler for post-run inspection */\n getErrorHandler(): ErrorHandler {\n return this.errorHandler;\n }\n\n /**\n * Validate plugin structure\n */\n private validatePlugin(plugin: unknown): asserts plugin is VulcnPlugin {\n if (!plugin || typeof plugin !== \"object\") {\n throw new Error(\"Plugin must be an object\");\n }\n\n const p = plugin as Record<string, unknown>;\n if (typeof p.name !== \"string\" || !p.name) {\n throw new Error(\"Plugin must have a name\");\n }\n if (typeof p.version !== \"string\" || !p.version) {\n throw new Error(\"Plugin must have a version\");\n }\n\n // Check API version compatibility\n const apiVersion = (p.apiVersion as number) || 1;\n if (apiVersion > PLUGIN_API_VERSION) {\n throw new Error(\n `Plugin requires API version ${apiVersion}, but engine supports ${PLUGIN_API_VERSION}`,\n );\n }\n }\n\n /**\n * Add a plugin programmatically (for testing or dynamic loading)\n */\n addPlugin(plugin: VulcnPlugin, config: Record<string, unknown> = {}): void {\n this.validatePlugin(plugin);\n this.plugins.push({\n plugin,\n config,\n source: \"custom\",\n enabled: true,\n });\n }\n\n /**\n * Initialize all plugins (call onInit hooks)\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Load payloads from plugins that provide them\n for (const loaded of this.plugins) {\n if (loaded.plugin.payloads) {\n const payloads =\n typeof loaded.plugin.payloads === \"function\"\n ? await loaded.plugin.payloads()\n : loaded.plugin.payloads;\n this.sharedPayloads.push(...payloads);\n }\n }\n\n // Call onInit hooks\n await this.callHook(\"onInit\", (hook, ctx) => hook(ctx));\n\n this.initialized = true;\n }\n\n /**\n * Destroy all plugins (call onDestroy hooks)\n */\n async destroy(): Promise<void> {\n await this.callHook(\"onDestroy\", (hook, ctx) => hook(ctx));\n this.plugins = [];\n this.sharedPayloads = [];\n this.sharedFindings = [];\n this.initialized = false;\n }\n\n /**\n * Load the engine from a flat VulcnProjectConfig (from `.vulcn.yml`).\n *\n * This is the primary entry point for the new config system.\n * Maps user-facing config keys to internal plugin configs automatically.\n *\n * @param config - Parsed and validated VulcnProjectConfig\n */\n async loadFromConfig(config: VulcnProjectConfig): Promise<void> {\n const { payloads, detection } = config;\n\n // ── Load payloads ──────────────────────────────────────────────────\n\n // Custom payload file (resolve relative paths externally before passing in)\n if (payloads.custom) {\n try {\n const payloadPkg = \"@vulcn/plugin-payloads\";\n const { loadFromFile } = await import(/* @vite-ignore */ payloadPkg);\n const loaded = await loadFromFile(payloads.custom);\n this.addPayloads(loaded);\n } catch (err) {\n throw new Error(\n `Failed to load custom payloads from ${payloads.custom}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // Load payload types — curated first, then PayloadBox if enabled\n try {\n const payloadPkg = \"@vulcn/plugin-payloads\";\n const { getCuratedPayloads, loadPayloadBox } = await import(\n /* @vite-ignore */ payloadPkg\n );\n\n for (const name of payloads.types) {\n // Curated payloads (always, if available)\n const curated = getCuratedPayloads(name);\n if (curated) {\n this.addPayloads(curated);\n }\n\n // PayloadBox if enabled, or as fallback if no curated set\n if (payloads.payloadbox || !curated) {\n try {\n const payload = await loadPayloadBox(name, payloads.limit);\n this.addPayloads([payload]);\n } catch (err) {\n if (!curated) {\n throw new Error(\n `No payloads for \"${name}\": no curated set and PayloadBox failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n }\n } catch (err) {\n throw new Error(\n `Failed to load payloads: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // ── Auto-load detection plugins ────────────────────────────────────\n\n // XSS detection: map flat config → plugin config\n if (\n payloads.types.includes(\"xss\") &&\n !this.hasPlugin(\"@vulcn/plugin-detect-xss\")\n ) {\n try {\n const pkg = \"@vulcn/plugin-detect-xss\";\n const mod = await import(/* @vite-ignore */ pkg);\n this.addPlugin(mod.default, {\n detectDialogs: detection.xss.dialogs,\n detectConsole: detection.xss.console,\n consoleMarker: detection.xss.consoleMarker,\n detectDomMutation: detection.xss.domMutation,\n severity: detection.xss.severity,\n alertPatterns: detection.xss.alertPatterns,\n });\n } catch (err) {\n this.errorHandler.catch(err, {\n severity: ErrorSeverity.WARN,\n source: \"plugin-manager:loadFromConfig\",\n context: { plugin: \"@vulcn/plugin-detect-xss\" },\n });\n }\n }\n\n // SQLi detection\n const hasSqli = payloads.types.some((t: string) => {\n const lower = t.toLowerCase();\n return lower === \"sqli\" || lower.includes(\"sql\");\n });\n if (hasSqli && !this.hasPlugin(\"@vulcn/plugin-detect-sqli\")) {\n try {\n const pkg = \"@vulcn/plugin-detect-sqli\";\n const mod = await import(/* @vite-ignore */ pkg);\n this.addPlugin(mod.default);\n } catch (err) {\n this.errorHandler.catch(err, {\n severity: ErrorSeverity.WARN,\n source: \"plugin-manager:loadFromConfig\",\n context: { plugin: \"@vulcn/plugin-detect-sqli\" },\n });\n }\n }\n\n // Passive scanner\n if (detection.passive && !this.hasPlugin(\"@vulcn/plugin-passive\")) {\n try {\n const pkg = \"@vulcn/plugin-passive\";\n const mod = await import(/* @vite-ignore */ pkg);\n this.addPlugin(mod.default);\n } catch (err) {\n this.errorHandler.catch(err, {\n severity: ErrorSeverity.WARN,\n source: \"plugin-manager:loadFromConfig\",\n context: { plugin: \"@vulcn/plugin-passive\" },\n });\n }\n }\n }\n\n /**\n * Get all loaded payloads\n */\n getPayloads(): RuntimePayload[] {\n return this.sharedPayloads;\n }\n\n /**\n * Get all collected findings\n */\n getFindings(): Finding[] {\n return this.sharedFindings;\n }\n\n /**\n * Add a finding (used by detectors)\n */\n addFinding(finding: Finding): void {\n this.sharedFindings.push(finding);\n }\n\n /**\n * Add payloads (used by loaders)\n */\n addPayloads(payloads: RuntimePayload[]): void {\n this.sharedPayloads.push(...payloads);\n }\n\n /**\n * Clear findings (for new run)\n */\n clearFindings(): void {\n this.sharedFindings = [];\n }\n\n /**\n * Get loaded plugins\n */\n getPlugins(): LoadedPlugin[] {\n return this.plugins;\n }\n\n /**\n * Check if a plugin is loaded by name\n */\n hasPlugin(name: string): boolean {\n return this.plugins.some((p) => p.plugin.name === name);\n }\n\n /**\n * Create base context for plugins\n */\n createContext(\n pluginConfig: Record<string, unknown>,\n pluginName?: string,\n ): PluginContext {\n const engineInfo: EngineInfo = {\n version: ENGINE_VERSION,\n pluginApiVersion: PLUGIN_API_VERSION,\n };\n\n return {\n config: pluginConfig,\n engine: engineInfo,\n payloads: this.sharedPayloads,\n findings: this.sharedFindings,\n addFinding: (finding: Finding) => {\n console.log(\n `[DEBUG-PM] Plugin ${pluginName || \"?\"} adding finding: ${finding.type}`,\n );\n this.sharedFindings.push(finding);\n },\n logger: this.createLogger(pluginName || \"plugin\"),\n errors: this.errorHandler,\n fetch: globalThis.fetch,\n };\n }\n\n /**\n * Create scoped logger for a plugin\n */\n private createLogger(name: string): PluginLogger {\n const prefix = `[${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n\n // ── Hook severity classification ────────────────────────────────────\n //\n // Hooks that produce OUTPUT (reports, results) are FATAL on failure.\n // Hooks that set up state are ERROR. Everything else is WARN.\n //\n private static readonly FATAL_HOOKS: Set<keyof PluginHooks> = new Set([\n \"onRunEnd\",\n \"onScanEnd\",\n ]);\n\n private static readonly ERROR_HOOKS: Set<keyof PluginHooks> = new Set([\n \"onInit\",\n \"onRunStart\",\n \"onScanStart\",\n \"onAfterPayload\",\n ]);\n\n private hookSeverity(hookName: keyof PluginHooks): ErrorSeverity {\n if (PluginManager.FATAL_HOOKS.has(hookName)) return ErrorSeverity.FATAL;\n if (PluginManager.ERROR_HOOKS.has(hookName)) return ErrorSeverity.ERROR;\n return ErrorSeverity.WARN;\n }\n\n /**\n * Call a hook on all plugins sequentially\n */\n async callHook<K extends keyof PluginHooks>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<unknown>,\n ): Promise<void> {\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config, loaded.plugin.name);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n await executor(hook as NonNullable<PluginHooks[K]>, ctx);\n } catch (err) {\n this.errorHandler.catch(err, {\n severity: this.hookSeverity(hookName),\n source: `plugin:${loaded.plugin.name}`,\n context: { hook: hookName },\n });\n }\n }\n }\n }\n\n /**\n * Call a hook and collect results\n */\n async callHookCollect<K extends keyof PluginHooks, R>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<R | R[] | null>,\n ): Promise<R[]> {\n const results: R[] = [];\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config, loaded.plugin.name);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n const result = await executor(\n hook as NonNullable<PluginHooks[K]>,\n ctx,\n );\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n results.push(...result);\n } else {\n results.push(result);\n }\n }\n } catch (err) {\n this.errorHandler.catch(err, {\n severity: this.hookSeverity(hookName),\n source: `plugin:${loaded.plugin.name}`,\n context: { hook: hookName },\n });\n }\n }\n }\n\n return results;\n }\n\n /**\n * Call a hook that transforms a value through the pipeline\n */\n async callHookPipe<T>(\n hookName: keyof PluginHooks,\n initial: T,\n executor: (\n hook: NonNullable<PluginHooks[typeof hookName]>,\n value: T,\n ctx: PluginContext,\n ) => Promise<T>,\n ): Promise<T> {\n let value = initial;\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config, loaded.plugin.name);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n value = await executor(\n hook as NonNullable<PluginHooks[typeof hookName]>,\n value,\n ctx,\n );\n } catch (err) {\n this.errorHandler.catch(err, {\n severity: this.hookSeverity(hookName),\n source: `plugin:${loaded.plugin.name}`,\n context: { hook: hookName },\n });\n }\n }\n }\n\n return value;\n }\n}\n\n/**\n * Default shared plugin manager instance\n */\nexport const pluginManager = new PluginManager();\n","/**\n * Vulcn Plugin System Types\n * @module @vulcn/engine/plugin\n *\n * The plugin system is driver-agnostic. Detection plugins receive\n * a generic page interface rather than Playwright types directly.\n * This allows the same plugin to work across different driver types.\n */\n\nimport type { z } from \"zod\";\nimport type { Session, Step } from \"./driver-types\";\nimport type { Finding } from \"./types\";\nimport type { ErrorHandler } from \"./errors\";\nimport type { RunResult } from \"./driver-types\";\nimport type { RuntimePayload, PayloadCategory } from \"./payload-types\";\n\n// Re-export for plugin authors\nexport type {\n Session,\n Step,\n Finding,\n RunResult,\n RuntimePayload,\n PayloadCategory,\n};\n\n/**\n * Plugin API version - plugins declare compatibility\n */\nexport const PLUGIN_API_VERSION = 1;\n\n/**\n * Plugin source types for identification\n */\nexport type PluginSource = \"builtin\" | \"npm\" | \"local\" | \"custom\";\n\n/**\n * Main plugin interface\n */\nexport interface VulcnPlugin {\n /** Unique plugin name (e.g., \"@vulcn/plugin-payloads\") */\n name: string;\n\n /** Plugin version (semver) */\n version: string;\n\n /** Plugin API version this plugin targets */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Lifecycle hooks */\n hooks?: PluginHooks;\n\n /**\n * Payloads provided by this plugin (Loaders)\n * Can be static array or async function for lazy loading\n */\n payloads?: RuntimePayload[] | (() => Promise<RuntimePayload[]>);\n\n /**\n * Zod schema for plugin configuration validation\n */\n configSchema?: z.ZodSchema;\n}\n\n/**\n * Plugin lifecycle hooks\n *\n * Detection hooks (onDialog, onConsoleMessage, etc.) receive\n * Playwright types from the driver. Plugins that use these\n * should declare playwright as a peer/dev dependency.\n */\nexport interface PluginHooks {\n // ─────────────────────────────────────────────────────────────────\n // Initialization\n // ─────────────────────────────────────────────────────────────────\n\n /**\n * Called when plugin is loaded, before any operation\n * Use for setup, loading payloads, etc.\n */\n onInit?: (ctx: PluginContext) => Promise<void>;\n\n /**\n * Called when plugin is unloaded/cleanup\n */\n onDestroy?: (ctx: PluginContext) => Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────\n // Recording Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when recording starts */\n onRecordStart?: (ctx: RecordContext) => Promise<void>;\n\n /** Called for each recorded step, can transform */\n onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;\n\n /** Called when recording ends, can transform session */\n onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;\n\n // ─────────────────────────────────────────────────────────────────\n // Scan Phase (wraps all sessions)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called once when a scan starts (before any session is executed) */\n onScanStart?: (ctx: ScanContext) => Promise<void>;\n\n /** Called once when a scan ends (after all sessions have executed) */\n onScanEnd?: (result: RunResult, ctx: ScanContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Running Phase (per session)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when run starts */\n onRunStart?: (ctx: RunContext) => Promise<void>;\n\n /** Called before each payload is injected, can transform payload */\n onBeforePayload?: (\n payload: string,\n step: Step,\n ctx: RunContext,\n ) => Promise<string>;\n\n /** Called after payload injection, for detection */\n onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;\n\n /**\n * Called before the browser/driver is closed.\n * Plugins should await any pending async work here (e.g., flush\n * in-flight response handlers that need browser access).\n */\n onBeforeClose?: (ctx: PluginContext) => Promise<void>;\n\n /** Called when run ends, can transform results */\n onRunEnd?: (result: RunResult, ctx: RunContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Browser Event Hooks (Detection)\n // These receive driver-specific types (e.g. Playwright's Dialog)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when JavaScript alert/confirm/prompt appears */\n onDialog?: (dialog: unknown, ctx: DetectContext) => Promise<Finding | null>;\n\n /** Called on console.log/warn/error */\n onConsoleMessage?: (\n msg: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on page load/navigation */\n onPageLoad?: (page: unknown, ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called on network request */\n onNetworkRequest?: (\n request: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on network response */\n onNetworkResponse?: (\n response: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n}\n\n/**\n * Logger interface for plugins\n */\nexport interface PluginLogger {\n debug: (msg: string, ...args: unknown[]) => void;\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n error: (msg: string, ...args: unknown[]) => void;\n}\n\n/**\n * Engine information exposed to plugins\n */\nexport interface EngineInfo {\n version: string;\n pluginApiVersion: number;\n}\n\n/**\n * Base context available to all plugin hooks\n */\nexport interface PluginContext {\n /** Plugin-specific configuration */\n config: Record<string, unknown>;\n\n /** Engine information */\n engine: EngineInfo;\n\n /** Shared payload registry - loaders add payloads here */\n payloads: RuntimePayload[];\n\n /** Shared findings collection (read-only view, use addFinding to add) */\n findings: Finding[];\n\n /**\n * Add a finding through the proper callback chain.\n * Plugins should use this instead of pushing to findings[] directly,\n * so consumers (CLI, worker) get notified via onFinding callbacks.\n */\n addFinding: (finding: Finding) => void;\n\n /** Scoped logger */\n logger: PluginLogger;\n\n /**\n * Centralized error handler.\n * Plugins MUST use this to surface errors instead of swallowing them:\n * ctx.errors.fatal(\"can't write report\", \"plugin:report\", { cause: err })\n * ctx.errors.warn(\"optional feature unavailable\", \"plugin:passive\")\n */\n errors: ErrorHandler;\n\n /** Fetch API for network requests */\n fetch: typeof fetch;\n}\n\n/**\n * Context for recording phase hooks\n */\nexport interface RecordContext extends PluginContext {\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n}\n\n/**\n * Context for running phase hooks\n */\nexport interface RunContext extends PluginContext {\n /** Session being executed */\n session: Session;\n\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n\n /** Whether running headless */\n headless: boolean;\n}\n\n/**\n * Context for scan-level hooks (wraps all sessions)\n */\nexport interface ScanContext extends PluginContext {\n /** All sessions in this scan */\n sessions: Session[];\n\n /** Whether running headless */\n headless: boolean;\n\n /** Total sessions count */\n sessionCount: number;\n}\n\n/**\n * Context for detection hooks\n */\nexport interface DetectContext extends RunContext {\n /** Current step being tested */\n step: Step;\n\n /** Current payload set being tested */\n payloadSet: RuntimePayload;\n\n /** Actual payload value injected */\n payloadValue: string;\n\n /** Step ID for reporting */\n stepId: string;\n}\n\n/**\n * Loaded plugin instance with resolved config\n */\nexport interface LoadedPlugin {\n /** Plugin definition */\n plugin: VulcnPlugin;\n\n /** Resolved configuration */\n config: Record<string, unknown>;\n\n /** Source of the plugin */\n source: PluginSource;\n\n /** Whether plugin is enabled */\n enabled: boolean;\n}\n","/**\n * Vulcn Auth Module\n *\n * Handles credential encryption/decryption and auth state management.\n *\n * Security:\n * - AES-256-GCM encryption for credentials at rest\n * - PBKDF2 key derivation from passphrase\n * - Reads passphrase from VULCN_KEY env var (CI/CD) or interactive prompt\n * - Auth state (cookies, localStorage) encrypted separately\n */\n\nimport {\n randomBytes,\n createCipheriv,\n createDecipheriv,\n pbkdf2Sync,\n} from \"node:crypto\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\n/** Form-based login credentials */\nexport interface FormCredentials {\n type: \"form\";\n username: string;\n password: string;\n /** Custom login URL (if different from target) */\n loginUrl?: string;\n /** Custom CSS selector for username field */\n userSelector?: string;\n /** Custom CSS selector for password field */\n passSelector?: string;\n}\n\n/** Header-based authentication (API keys, Bearer tokens) */\nexport interface HeaderCredentials {\n type: \"header\";\n headers: Record<string, string>;\n}\n\n/** All credential types */\nexport type Credentials = FormCredentials | HeaderCredentials;\n\n/** Auth configuration for a scan */\nexport interface AuthConfig {\n /** Auth strategy */\n strategy: \"storage-state\" | \"header\";\n /** Login page URL */\n loginUrl?: string;\n /** Text that appears when logged in (e.g., \"Logout\") */\n loggedInIndicator?: string;\n /** Text that appears when logged out (e.g., \"Sign In\") */\n loggedOutIndicator?: string;\n /** Session expiry detection rules */\n sessionExpiry?: {\n /** HTTP status codes that indicate session expired */\n statusCodes?: number[];\n /** URL pattern that indicates redirect to login */\n redirectPattern?: string;\n /** Page content that indicates session expired */\n pageContent?: string;\n };\n}\n\n/** Encrypted payload structure (stored as JSON) */\ninterface EncryptedData {\n /** Format version */\n version: 1;\n /** PBKDF2 salt (hex) */\n salt: string;\n /** AES-256-GCM IV (hex) */\n iv: string;\n /** AES-256-GCM auth tag (hex) */\n tag: string;\n /** Encrypted data (hex) */\n data: string;\n /** PBKDF2 iterations */\n iterations: number;\n}\n\n// ── Constants ──────────────────────────────────────────────────────────\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32; // 256 bits\nconst IV_LENGTH = 16; // 128 bits\nconst SALT_LENGTH = 32; // 256 bits\nconst PBKDF2_ITERATIONS = 100_000;\nconst PBKDF2_DIGEST = \"sha512\";\n\n// ── Key Derivation ─────────────────────────────────────────────────────\n\n/**\n * Derive AES-256 key from passphrase using PBKDF2.\n */\nfunction deriveKey(passphrase: string, salt: Buffer): Buffer {\n return pbkdf2Sync(\n passphrase,\n salt,\n PBKDF2_ITERATIONS,\n KEY_LENGTH,\n PBKDF2_DIGEST,\n );\n}\n\n// ── Encryption ─────────────────────────────────────────────────────────\n\n/**\n * Encrypt data with AES-256-GCM.\n *\n * @param data - Plaintext data to encrypt\n * @param passphrase - Passphrase for key derivation\n * @returns JSON string of EncryptedData\n */\nexport function encrypt(data: string, passphrase: string): string {\n const salt = randomBytes(SALT_LENGTH);\n const iv = randomBytes(IV_LENGTH);\n const key = deriveKey(passphrase, salt);\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n let encrypted = cipher.update(data, \"utf8\", \"hex\");\n encrypted += cipher.final(\"hex\");\n const tag = cipher.getAuthTag();\n\n const payload: EncryptedData = {\n version: 1,\n salt: salt.toString(\"hex\"),\n iv: iv.toString(\"hex\"),\n tag: tag.toString(\"hex\"),\n data: encrypted,\n iterations: PBKDF2_ITERATIONS,\n };\n\n return JSON.stringify(payload);\n}\n\n/**\n * Decrypt data encrypted with encrypt().\n *\n * @param encrypted - JSON string from encrypt()\n * @param passphrase - Passphrase used during encryption\n * @returns Decrypted plaintext\n * @throws Error if passphrase is wrong or data is tampered\n */\nexport function decrypt(encrypted: string, passphrase: string): string {\n const payload: EncryptedData = JSON.parse(encrypted);\n\n if (payload.version !== 1) {\n throw new Error(`Unsupported encryption version: ${payload.version}`);\n }\n\n const salt = Buffer.from(payload.salt, \"hex\");\n const iv = Buffer.from(payload.iv, \"hex\");\n const tag = Buffer.from(payload.tag, \"hex\");\n const key = deriveKey(passphrase, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(tag);\n\n let decrypted = decipher.update(payload.data, \"hex\", \"utf8\");\n decrypted += decipher.final(\"utf8\");\n\n return decrypted;\n}\n\n// ── Credential Helpers ─────────────────────────────────────────────────\n\n/**\n * Encrypt credentials to a storable string.\n */\nexport function encryptCredentials(\n credentials: Credentials,\n passphrase: string,\n): string {\n return encrypt(JSON.stringify(credentials), passphrase);\n}\n\n/**\n * Decrypt credentials from a stored string.\n */\nexport function decryptCredentials(\n encrypted: string,\n passphrase: string,\n): Credentials {\n const json = decrypt(encrypted, passphrase);\n return JSON.parse(json) as Credentials;\n}\n\n/**\n * Encrypt browser storage state (cookies, localStorage, etc.).\n * The state is the JSON output from Playwright's context.storageState().\n */\nexport function encryptStorageState(\n storageState: string,\n passphrase: string,\n): string {\n return encrypt(storageState, passphrase);\n}\n\n/**\n * Decrypt browser storage state.\n */\nexport function decryptStorageState(\n encrypted: string,\n passphrase: string,\n): string {\n return decrypt(encrypted, passphrase);\n}\n\n// ── Passphrase Resolution ──────────────────────────────────────────────\n\n/**\n * Get passphrase from environment or throw.\n *\n * In CI/CD, set VULCN_KEY env var.\n * In interactive mode, the CLI should prompt and pass the value here.\n */\nexport function getPassphrase(interactive?: string): string {\n // Interactive passphrase takes priority\n if (interactive) return interactive;\n\n // Fall back to env var\n const envKey = process.env.VULCN_KEY;\n if (envKey) return envKey;\n\n throw new Error(\n \"No passphrase provided. Set VULCN_KEY environment variable or pass --passphrase.\",\n );\n}\n","/**\n * Payload Types for Vulcn\n * Core types used by the engine and plugins\n */\n\n/**\n * Valid payload categories\n */\nexport type PayloadCategory =\n | \"xss\"\n | \"sqli\"\n | \"ssrf\"\n | \"xxe\"\n | \"command-injection\"\n | \"path-traversal\"\n | \"open-redirect\"\n | \"reflection\"\n | \"security-misconfiguration\"\n | \"information-disclosure\"\n | \"custom\";\n\n/**\n * Payload source types\n */\nexport type PayloadSource = \"curated\" | \"custom\" | \"payloadbox\" | \"plugin\";\n\n/**\n * Runtime payload structure - used by plugins and the runner\n */\nexport interface RuntimePayload {\n /** Unique payload name */\n name: string;\n\n /** Vulnerability category */\n category: PayloadCategory;\n\n /** Human-readable description */\n description: string;\n\n /** Array of payload strings to inject */\n payloads: string[];\n\n /** Patterns to detect vulnerability (as RegExp) */\n detectPatterns: RegExp[];\n\n /** Where this payload came from */\n source: PayloadSource;\n}\n\n/**\n * Custom payload schema for YAML/JSON files (used by loader plugins)\n */\nexport interface CustomPayload {\n name: string;\n category: PayloadCategory;\n description?: string;\n payloads: string[];\n detectPatterns?: string[];\n}\n\n/**\n * Custom payload file schema\n */\nexport interface CustomPayloadFile {\n version?: string;\n payloads: CustomPayload[];\n}\n\n/**\n * Determine finding severity based on vulnerability category.\n *\n * Central mapping used by all drivers and plugins — single source of truth\n * so severity ratings remain consistent across Tier 1 (HTTP) and Tier 2 (browser) scans.\n */\nexport function getSeverity(\n category: PayloadCategory,\n): \"critical\" | \"high\" | \"medium\" | \"low\" | \"info\" {\n switch (category) {\n case \"sqli\":\n case \"command-injection\":\n case \"xxe\":\n return \"critical\";\n case \"xss\":\n case \"ssrf\":\n case \"path-traversal\":\n return \"high\";\n case \"open-redirect\":\n return \"medium\";\n case \"security-misconfiguration\":\n return \"low\";\n case \"information-disclosure\":\n return \"info\";\n default:\n return \"medium\";\n }\n}\n"],"mappings":"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","names":["resolve","ErrorSeverity","require","resolve","pluginManager","createRequire","createRequire","ENGINE_VERSION"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vulcn/engine",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "Fast, modern security testing engine — record browser sessions, replay with attack payloads, and detect vulnerabilities automatically. Pluggable driver and detection system for web application penetration testing.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -66,6 +66,7 @@
66
66
  "oxlint": "^0.15.0",
67
67
  "prettier": "^3.4.0",
68
68
  "tsup": "^8.5.1",
69
+ "tsx": "^4.21.0",
69
70
  "typescript": "^5.7.0",
70
71
  "vitest": "^2.1.0"
71
72
  },
@@ -87,6 +88,9 @@
87
88
  "changeset": "changeset",
88
89
  "release:version": "changeset version",
89
90
  "release:publish": "pnpm build && changeset publish",
90
- "release:check": "pnpm check && pnpm test:coverage"
91
+ "release:check": "pnpm check && pnpm test:coverage",
92
+ "benchmark": "tsx benchmarks/bench.ts",
93
+ "benchmark:verbose": "tsx benchmarks/bench.ts --verbose",
94
+ "benchmark:json": "tsx benchmarks/bench.ts --json benchmarks/results/latest.json --verbose"
91
95
  }
92
96
  }