@vulcn/driver-browser 0.3.0 → 0.4.0
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.cjs +262 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -3
- package/dist/index.d.ts +92 -3
- package/dist/index.js +260 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/recorder.ts","../src/runner.ts","../src/crawler.ts","../src/auth.ts"],"sourcesContent":["/**\n * @vulcn/driver-browser\n *\n * Browser recording driver for Vulcn.\n * Uses Playwright to record and replay web application interactions.\n *\n * Step types:\n * - browser.navigate - Navigate to a URL\n * - browser.click - Click an element\n * - browser.input - Type into an input field\n * - browser.keypress - Press a key\n * - browser.scroll - Scroll the page\n * - browser.wait - Wait for a duration\n */\n\nimport { z } from \"zod\";\nimport type {\n VulcnDriver,\n RecorderDriver,\n RunnerDriver,\n RecordingHandle,\n RecordOptions,\n CrawlOptions,\n Session,\n Step,\n RunContext,\n RunResult,\n} from \"@vulcn/engine\";\n\nimport { BrowserRecorder } from \"./recorder\";\nimport { BrowserRunner } from \"./runner\";\nimport { crawlAndBuildSessions } from \"./crawler\";\n\n/**\n * Browser driver configuration schema\n */\nexport const configSchema = z.object({\n /** Starting URL for recording */\n startUrl: z.string().url().optional(),\n\n /** Browser type */\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).default(\"chromium\"),\n\n /** Viewport size */\n viewport: z\n .object({\n width: z.number().default(1280),\n height: z.number().default(720),\n })\n .default({ width: 1280, height: 720 }),\n\n /** Run headless */\n headless: z.boolean().default(false),\n});\n\nexport type BrowserConfig = z.infer<typeof configSchema>;\n\n/**\n * Browser step types\n */\nexport const BROWSER_STEP_TYPES = [\n \"browser.navigate\",\n \"browser.click\",\n \"browser.input\",\n \"browser.keypress\",\n \"browser.scroll\",\n \"browser.wait\",\n] as const;\n\nexport type BrowserStepType = (typeof BROWSER_STEP_TYPES)[number];\n\n/**\n * Browser-specific step schemas\n */\nexport const BrowserStepSchema = z.discriminatedUnion(\"type\", [\n z.object({\n id: z.string(),\n type: z.literal(\"browser.navigate\"),\n url: z.string(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.click\"),\n selector: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.input\"),\n selector: z.string(),\n value: z.string(),\n injectable: z.boolean().default(true),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.keypress\"),\n key: z.string(),\n modifiers: z.array(z.string()).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.scroll\"),\n selector: z.string().optional(),\n position: z.object({ x: z.number(), y: z.number() }),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.wait\"),\n duration: z.number(),\n timestamp: z.number(),\n }),\n]);\n\nexport type BrowserStep = z.infer<typeof BrowserStepSchema>;\n\n/**\n * Browser recorder implementation\n */\nconst recorderDriver: RecorderDriver = {\n async start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle> {\n const parsedConfig = configSchema.parse(config);\n return BrowserRecorder.start(parsedConfig, options);\n },\n\n async crawl(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]> {\n const parsedConfig = configSchema.parse(config);\n return crawlAndBuildSessions(\n {\n startUrl: parsedConfig.startUrl ?? \"\",\n browser: parsedConfig.browser,\n headless: parsedConfig.headless,\n viewport: parsedConfig.viewport,\n },\n options,\n );\n },\n};\n\n/**\n * Browser runner implementation\n */\nconst runnerDriver: RunnerDriver = {\n async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n return BrowserRunner.execute(session, ctx);\n },\n};\n\n/**\n * Browser driver for Vulcn\n */\nconst browserDriver: VulcnDriver = {\n name: \"browser\",\n version: \"0.1.0\",\n apiVersion: 1,\n description: \"Browser recording driver using Playwright\",\n configSchema,\n stepTypes: [...BROWSER_STEP_TYPES],\n recorder: recorderDriver,\n runner: runnerDriver,\n};\n\nexport default browserDriver;\n\n// Re-export utilities\nexport { BrowserRecorder } from \"./recorder\";\nexport { BrowserRunner } from \"./runner\";\nexport { crawlAndBuildSessions } from \"./crawler\";\nexport { launchBrowser, checkBrowsers, installBrowsers } from \"./browser\";\nexport { detectLoginForm, performLogin, checkSessionAlive } from \"./auth\";\nexport type { LoginForm, LoginResult } from \"./auth\";\n","/**\n * Browser utilities for @vulcn/driver-browser\n * Smart browser launching with system browser fallback\n */\n\nimport { chromium, firefox, webkit, type Browser } from \"playwright\";\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\nexport type BrowserType = \"chromium\" | \"firefox\" | \"webkit\";\n\nexport interface LaunchOptions {\n browser?: BrowserType;\n headless?: boolean;\n}\n\nexport interface BrowserLaunchResult {\n browser: Browser;\n channel?: string;\n}\n\nexport class BrowserNotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BrowserNotFoundError\";\n }\n}\n\n/**\n * Launch a browser with smart fallback:\n * 1. Try system Chrome/Edge first (zero-install experience)\n * 2. Fall back to Playwright's bundled browsers\n */\nexport async function launchBrowser(\n options: LaunchOptions = {},\n): Promise<BrowserLaunchResult> {\n const browserType = options.browser ?? \"chromium\";\n const headless = options.headless ?? false;\n\n // For Chromium, try system browsers first\n if (browserType === \"chromium\") {\n // Try system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless,\n });\n return { browser, channel: \"chrome\" };\n } catch {\n // Chrome not available\n }\n\n // Try system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless,\n });\n return { browser, channel: \"msedge\" };\n } catch {\n // Edge not available\n }\n\n // Fall back to Playwright's bundled Chromium\n try {\n const browser = await chromium.launch({ headless });\n return { browser, channel: \"chromium\" };\n } catch {\n throw new BrowserNotFoundError(\n \"No Chromium browser found. Install Chrome or run: vulcn install chromium\",\n );\n }\n }\n\n // Firefox\n if (browserType === \"firefox\") {\n try {\n const browser = await firefox.launch({ headless });\n return { browser, channel: \"firefox\" };\n } catch {\n throw new BrowserNotFoundError(\n \"Firefox not found. Run: vulcn install firefox\",\n );\n }\n }\n\n // WebKit\n if (browserType === \"webkit\") {\n try {\n const browser = await webkit.launch({ headless });\n return { browser, channel: \"webkit\" };\n } catch {\n throw new BrowserNotFoundError(\n \"WebKit not found. Run: vulcn install webkit\",\n );\n }\n }\n\n throw new BrowserNotFoundError(`Unknown browser type: ${browserType}`);\n}\n\n/**\n * Install Playwright browsers\n */\nexport async function installBrowsers(\n browsers: BrowserType[] = [\"chromium\"],\n): Promise<void> {\n const browserArg = browsers.join(\" \");\n await execAsync(`npx playwright install ${browserArg}`);\n}\n\n/**\n * Check which browsers are available\n */\nexport async function checkBrowsers(): Promise<{\n systemChrome: boolean;\n systemEdge: boolean;\n playwrightChromium: boolean;\n playwrightFirefox: boolean;\n playwrightWebkit: boolean;\n}> {\n const results = {\n systemChrome: false,\n systemEdge: false,\n playwrightChromium: false,\n playwrightFirefox: false,\n playwrightWebkit: false,\n };\n\n // Check system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless: true,\n });\n await browser.close();\n results.systemChrome = true;\n } catch {\n // Not available\n }\n\n // Check system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless: true,\n });\n await browser.close();\n results.systemEdge = true;\n } catch {\n // Not available\n }\n\n // Check Playwright Chromium\n try {\n const browser = await chromium.launch({ headless: true });\n await browser.close();\n results.playwrightChromium = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright Firefox\n try {\n const browser = await firefox.launch({ headless: true });\n await browser.close();\n results.playwrightFirefox = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright WebKit\n try {\n const browser = await webkit.launch({ headless: true });\n await browser.close();\n results.playwrightWebkit = true;\n } catch {\n // Not installed\n }\n\n return results;\n}\n","/**\n * Browser Recorder Implementation\n *\n * Records browser interactions as replayable sessions.\n * Uses Playwright for browser automation.\n */\n\nimport type { Page } from \"playwright\";\nimport type {\n RecordingHandle,\n RecordOptions,\n Session,\n Step,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser } from \"./browser\";\nimport type { BrowserConfig, BrowserStep } from \"./index\";\n\n/**\n * Browser Recorder - captures browser interactions\n */\nexport class BrowserRecorder {\n /**\n * Start a new recording session\n */\n static async start(\n config: BrowserConfig,\n _options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const { startUrl, browser: browserType, viewport, headless } = config;\n\n if (!startUrl) {\n throw new Error(\"startUrl is required for browser recording\");\n }\n\n // Launch browser with smart fallback\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Navigate to start URL\n await page.goto(startUrl);\n\n // Track recording\n const startTime = Date.now();\n const steps: Step[] = [];\n let stepCounter = 0;\n\n const generateStepId = () => {\n stepCounter++;\n return `step_${String(stepCounter).padStart(3, \"0\")}`;\n };\n\n // Add initial navigation step\n steps.push({\n id: generateStepId(),\n type: \"browser.navigate\",\n url: startUrl,\n timestamp: 0,\n });\n\n // Attach event listeners\n BrowserRecorder.attachListeners(page, steps, startTime, generateStepId);\n\n return {\n async stop(): Promise<Session> {\n const session: Session = {\n name: `Recording ${new Date().toISOString()}`,\n driver: \"browser\",\n driverConfig: {\n browser: browserType,\n viewport,\n startUrl,\n },\n steps,\n metadata: {\n recordedAt: new Date().toISOString(),\n version: \"1\",\n },\n };\n\n await browser.close();\n return session;\n },\n\n async abort(): Promise<void> {\n await browser.close();\n },\n\n getSteps(): Step[] {\n return [...steps];\n },\n\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: Date.now() - startTime,\n } as Step);\n },\n };\n }\n\n /**\n * Attach event listeners to the page\n */\n private static attachListeners(\n page: Page,\n steps: Step[],\n startTime: number,\n generateStepId: () => string,\n ) {\n const getTimestamp = () => Date.now() - startTime;\n\n const addStep = (\n step: Omit<Step, \"id\" | \"timestamp\"> & { type: string },\n ) => {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: getTimestamp(),\n } as Step);\n };\n\n // Track navigation\n page.on(\"framenavigated\", (frame) => {\n if (frame === page.mainFrame()) {\n const url = frame.url();\n // Avoid duplicate nav steps for initial load\n const lastStep = steps[steps.length - 1];\n if (\n steps.length > 0 &&\n lastStep?.type === \"browser.navigate\" &&\n (lastStep as BrowserStep & { type: \"browser.navigate\" }).url === url\n ) {\n return;\n }\n addStep({\n type: \"browser.navigate\",\n url,\n });\n }\n });\n\n // Expose recording function to browser\n page.exposeFunction(\n \"__vulcn_record\",\n async (event: { type: string; data: Record<string, unknown> }) => {\n switch (event.type) {\n case \"click\": {\n const data = event.data as {\n selector: string;\n x: number;\n y: number;\n };\n addStep({\n type: \"browser.click\",\n selector: data.selector,\n position: { x: data.x, y: data.y },\n });\n break;\n }\n case \"input\": {\n const data = event.data as {\n selector: string;\n value: string;\n inputType: string | null;\n injectable: boolean;\n };\n addStep({\n type: \"browser.input\",\n selector: data.selector,\n value: data.value,\n injectable: data.injectable,\n });\n break;\n }\n case \"keypress\": {\n const data = event.data as { key: string; modifiers?: string[] };\n addStep({\n type: \"browser.keypress\",\n key: data.key,\n modifiers: data.modifiers,\n });\n break;\n }\n }\n },\n );\n\n // Inject recording script into every frame\n page.on(\"load\", async () => {\n await BrowserRecorder.injectRecordingScript(page);\n });\n\n // Inject into initial page\n BrowserRecorder.injectRecordingScript(page);\n }\n\n /**\n * Inject the recording script into the page\n */\n private static async injectRecordingScript(page: Page) {\n await page.evaluate(`\n (function() {\n if (window.__vulcn_injected) return;\n window.__vulcn_injected = true;\n\n var textInputTypes = ['text', 'password', 'email', 'search', 'url', 'tel', 'number'];\n\n function getSelector(el) {\n if (el.id) {\n return '#' + CSS.escape(el.id);\n }\n if (el.name) {\n var tag = el.tagName.toLowerCase();\n var nameSelector = tag + '[name=\"' + el.name + '\"]';\n if (document.querySelectorAll(nameSelector).length === 1) {\n return nameSelector;\n }\n }\n if (el.dataset && el.dataset.testid) {\n return '[data-testid=\"' + el.dataset.testid + '\"]';\n }\n if (el.tagName === 'INPUT' && el.type && el.name) {\n var inputSelector = 'input[type=\"' + el.type + '\"][name=\"' + el.name + '\"]';\n if (document.querySelectorAll(inputSelector).length === 1) {\n return inputSelector;\n }\n }\n if (el.className && typeof el.className === 'string') {\n var classes = el.className.trim().split(/\\\\s+/).filter(function(c) { return c.length > 0; });\n if (classes.length > 0) {\n var classSelector = el.tagName.toLowerCase() + '.' + classes.map(function(c) { return CSS.escape(c); }).join('.');\n if (document.querySelectorAll(classSelector).length === 1) {\n return classSelector;\n }\n }\n }\n var path = [];\n var current = el;\n while (current && current !== document.body) {\n var tag = current.tagName.toLowerCase();\n var parent = current.parentElement;\n if (parent) {\n var siblings = Array.from(parent.children).filter(function(c) { return c.tagName === current.tagName; });\n if (siblings.length > 1) {\n var index = siblings.indexOf(current) + 1;\n tag = tag + ':nth-of-type(' + index + ')';\n }\n }\n path.unshift(tag);\n current = parent;\n }\n return path.join(' > ');\n }\n\n function getInputType(el) {\n if (el.tagName === 'INPUT') return el.type || 'text';\n if (el.tagName === 'TEXTAREA') return 'textarea';\n if (el.tagName === 'SELECT') return 'select';\n return null;\n }\n\n function isTextInjectable(el) {\n var inputType = getInputType(el);\n if (!inputType) return false;\n if (inputType === 'textarea') return true;\n if (inputType === 'select') return false;\n return textInputTypes.indexOf(inputType) !== -1;\n }\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n window.__vulcn_record({\n type: 'click',\n data: {\n selector: getSelector(target),\n x: e.clientX,\n y: e.clientY\n }\n });\n }, true);\n\n document.addEventListener('change', function(e) {\n var target = e.target;\n if ('value' in target) {\n var inputType = getInputType(target);\n window.__vulcn_record({\n type: 'input',\n data: {\n selector: getSelector(target),\n value: target.value,\n inputType: inputType,\n injectable: isTextInjectable(target)\n }\n });\n }\n }, true);\n\n document.addEventListener('keydown', function(e) {\n if (e.ctrlKey || e.metaKey || e.altKey) {\n var modifiers = [];\n if (e.ctrlKey) modifiers.push('ctrl');\n if (e.metaKey) modifiers.push('meta');\n if (e.altKey) modifiers.push('alt');\n if (e.shiftKey) modifiers.push('shift');\n\n window.__vulcn_record({\n type: 'keypress',\n data: {\n key: e.key,\n modifiers: modifiers\n }\n });\n }\n }, true);\n })();\n `);\n }\n}\n","/**\n * Browser Runner Implementation\n *\n * Replays browser sessions with security payloads.\n * Uses plugin hooks for detection.\n *\n * v2: Persistent browser with in-page payload cycling.\n * - ONE browser for the entire scan (not per-session)\n * - Uses page.goBack() between payloads instead of full page.goto()\n * - Falls back to full navigation when goBack() fails\n * - 5-10x faster on SPAs, same speed on simple sites\n */\n\nimport type {\n Browser,\n BrowserContext,\n Page,\n Dialog,\n ConsoleMessage,\n} from \"playwright\";\nimport type {\n Session,\n Step,\n RunContext,\n RunResult,\n Finding,\n RuntimePayload,\n PayloadCategory,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser, type BrowserType } from \"./browser\";\nimport type { BrowserStep } from \"./index\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ninterface PayloadItem {\n payloadSet: RuntimePayload;\n value: string;\n}\n\ninterface CurrentPayloadInfo {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n}\n\n// ── Browser Runner ─────────────────────────────────────────────────────\n\n/**\n * Browser Runner - replays sessions with payloads\n *\n * Supports two modes:\n * 1. Self-managed browser: launches its own browser (backward compat)\n * 2. Shared browser: receives a browser instance via RunOptions\n *\n * In both modes, payload cycling uses goBack() for speed.\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads.\n *\n * If ctx.options.browser is provided, reuses that browser (persistent mode).\n * Otherwise, launches and closes its own browser (standalone mode).\n */\n static async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n const config = session.driverConfig;\n const browserType = (config.browser as BrowserType) ?? \"chromium\";\n const viewport = (config.viewport as { width: number; height: number }) ?? {\n width: 1280,\n height: 720,\n };\n const startUrl = config.startUrl as string;\n const headless = ctx.options.headless ?? true;\n\n const startTime = Date.now();\n const errors: string[] = [];\n let payloadsTested = 0;\n\n const payloads = ctx.payloads;\n if (payloads.length === 0) {\n return {\n findings: [],\n stepsExecuted: session.steps.length,\n payloadsTested: 0,\n duration: Date.now() - startTime,\n errors: [\n \"No payloads loaded. Add a payload plugin or configure payloads.\",\n ],\n };\n }\n\n // ── Browser lifecycle ────────────────────────────────────────────\n // Use shared browser from options if available (persistent mode),\n // otherwise launch our own (standalone / backward compat).\n const sharedBrowser = ctx.options.browser as Browser | undefined;\n const ownBrowser = sharedBrowser\n ? null\n : (await launchBrowser({ browser: browserType, headless })).browser;\n const browser = sharedBrowser ?? ownBrowser!;\n\n // Inject storageState from options if available (for auth)\n const storageState = ctx.options.storageState as string | undefined;\n const contextOptions: Record<string, unknown> = { viewport };\n if (storageState) {\n contextOptions.storageState = JSON.parse(storageState);\n }\n\n const context = await browser.newContext(contextOptions);\n const page = await context.newPage();\n\n // Signal that the page is ready — plugins (e.g. passive scanner)\n // can now attach event listeners to the real page object\n await ctx.options.onPageReady?.(page);\n\n // ── Event tracking ───────────────────────────────────────────────\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: CurrentPayloadInfo | null = null;\n\n const dialogHandler = createDialogHandler(\n page,\n eventFindings,\n () => currentPayloadInfo,\n );\n const consoleHandler = createConsoleHandler(\n eventFindings,\n () => currentPayloadInfo,\n );\n\n page.on(\"dialog\", dialogHandler);\n page.on(\"console\", consoleHandler);\n\n try {\n // Find injectable steps (browser.input with injectable=true)\n const injectableSteps = session.steps.filter(\n (\n step,\n ): step is Step & { type: \"browser.input\"; injectable?: boolean } =>\n step.type === \"browser.input\" &&\n (step as BrowserStep & { type: \"browser.input\" }).injectable !==\n false,\n );\n\n // Build interleaved payload list (round-robin across categories)\n const allPayloads = interleavePayloads(payloads);\n\n // Track confirmed vulnerability types per-step (dedup)\n const confirmedTypes = new Set<string>();\n\n // ── Per-step payload cycling ───────────────────────────────────\n for (const injectableStep of injectableSteps) {\n let isFirstPayload = true;\n // Track the URL we navigate back to for goBack fallback\n let formPageUrl: string | null = null;\n\n for (const { payloadSet, value } of allPayloads) {\n // Skip if this vuln type is already confirmed for this step\n const stepTypeKey = `${injectableStep.id}::${payloadSet.category}`;\n if (confirmedTypes.has(stepTypeKey)) {\n continue;\n }\n\n try {\n currentPayloadInfo = {\n stepId: injectableStep.id,\n payloadSet,\n payloadValue: value,\n };\n\n if (isFirstPayload) {\n // First payload: full navigation + replay all steps\n await replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n isFirstPayload = false;\n // Capture the form page URL for fallback navigation\n formPageUrl = startUrl;\n } else {\n // Subsequent payloads: try goBack() first (fast path)\n const cycled = await cyclePayload(\n page,\n session,\n injectableStep,\n value,\n formPageUrl ?? startUrl,\n );\n if (!cycled) {\n // goBack failed — fall back to full replay\n await replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n }\n }\n\n // Check for reflection\n const reflectionFinding = await checkReflection(\n page,\n injectableStep,\n payloadSet,\n value,\n );\n\n // Collect all findings from this payload\n const allFindings = [...eventFindings];\n if (reflectionFinding) {\n allFindings.push(reflectionFinding);\n }\n\n // Deduplicate: only add findings we haven't already reported\n const seenKeys = new Set<string>();\n for (const finding of allFindings) {\n const dedupKey = `${finding.type}::${finding.stepId}::${finding.title}`;\n if (!seenKeys.has(dedupKey)) {\n seenKeys.add(dedupKey);\n ctx.addFinding(finding);\n }\n }\n\n // If we got any finding, mark as confirmed and skip remaining\n if (allFindings.length > 0) {\n confirmedTypes.add(stepTypeKey);\n }\n\n // Clear event findings for next iteration\n eventFindings.length = 0;\n payloadsTested++;\n\n // Report progress\n ctx.options.onStepComplete?.(injectableStep.id, payloadsTested);\n } catch (err) {\n errors.push(`${injectableStep.id}: ${String(err)}`);\n // If an error occurred, reset so next payload does full navigation\n isFirstPayload = true;\n }\n }\n }\n } finally {\n page.off(\"dialog\", dialogHandler);\n page.off(\"console\", consoleHandler);\n currentPayloadInfo = null;\n\n // Let plugins flush pending async work before browser closes\n await ctx.options.onBeforeClose?.(page);\n\n // Close context (lightweight — doesn't close the browser)\n await context.close();\n\n // Only close browser if we launched it ourselves\n if (ownBrowser) {\n await ownBrowser.close();\n }\n }\n\n return {\n findings: ctx.findings,\n stepsExecuted: session.steps.length,\n payloadsTested,\n duration: Date.now() - startTime,\n errors,\n };\n }\n}\n\n// ── Dialog / Console Handlers ──────────────────────────────────────────\n\nfunction createDialogHandler(\n page: Page,\n eventFindings: Finding[],\n getPayloadInfo: () => CurrentPayloadInfo | null,\n) {\n return async (dialog: Dialog) => {\n const info = getPayloadInfo();\n if (info) {\n const message = dialog.message();\n const dialogType = dialog.type();\n\n // Skip beforeunload dialogs (not XSS-related)\n if (dialogType !== \"beforeunload\") {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: `XSS Confirmed - ${dialogType}() triggered`,\n description: `JavaScript ${dialogType}() dialog was triggered by payload injection. Message: \"${message}\"`,\n stepId: info.stepId,\n payload: info.payloadValue,\n url: page.url(),\n evidence: `Dialog type: ${dialogType}, Message: ${message}`,\n metadata: {\n dialogType,\n dialogMessage: message,\n detectionMethod: \"dialog\",\n },\n });\n }\n }\n // Always dismiss dialogs to prevent blocking\n try {\n await dialog.dismiss();\n } catch {\n // Dialog may have already been handled\n }\n };\n}\n\nfunction createConsoleHandler(\n eventFindings: Finding[],\n getPayloadInfo: () => CurrentPayloadInfo | null,\n) {\n return async (msg: ConsoleMessage) => {\n const info = getPayloadInfo();\n if (info && msg.type() === \"log\") {\n const text = msg.text();\n if (text.includes(\"vulcn\") || text.includes(info.payloadValue)) {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: \"XSS Confirmed - Console Output\",\n description: `JavaScript console.log was triggered by payload injection`,\n stepId: info.stepId,\n payload: info.payloadValue,\n url: \"\",\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\n };\n}\n\n// ── Payload Cycling ────────────────────────────────────────────────────\n\n/**\n * Fast-path payload cycling using goBack().\n *\n * After a form submission causes navigation, goBack() returns to the form\n * page much faster than a full page.goto() — especially for SPAs where\n * the client-side router handles the back navigation without a full reload.\n *\n * Falls back to full navigation if goBack doesn't land on a page with\n * the expected form.\n *\n * Returns true if cycling succeeded, false if caller should fall back\n * to full replay.\n */\nasync function cyclePayload(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n payloadValue: string,\n formPageUrl: string,\n): Promise<boolean> {\n try {\n // Try goBack first — fast on SPAs, cheap on static sites\n await page.goBack({ waitUntil: \"domcontentloaded\", timeout: 5000 });\n\n // Verify the form element is still present\n const targetSelector = (\n targetStep as BrowserStep & { type: \"browser.input\" }\n ).selector;\n const formPresent = await page\n .waitForSelector(targetSelector, { timeout: 3000 })\n .then(() => true)\n .catch(() => false);\n\n if (!formPresent) {\n // goBack didn't land on the form page — try direct navigation\n await page.goto(formPageUrl, {\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n });\n\n // Wait for the form element after navigation\n const formPresentAfterNav = await page\n .waitForSelector(targetSelector, { timeout: 3000 })\n .then(() => true)\n .catch(() => false);\n\n if (!formPresentAfterNav) {\n // Even direct nav failed — fall back to full replay\n return false;\n }\n }\n\n // Fill the injectable step with the new payload\n await page.fill(targetSelector, payloadValue, { timeout: 5000 });\n\n // Execute remaining steps after the injectable step (click submit, etc.)\n await replayStepsAfter(page, session, targetStep);\n\n // Wait for scripts to execute\n await page.waitForTimeout(500);\n\n return true;\n } catch {\n return false;\n }\n}\n\n// ── Full Replay ────────────────────────────────────────────────────────\n\n/**\n * Replay session steps with payload injected at target step.\n *\n * IMPORTANT: We replay ALL steps, not just up to the injectable step.\n * The injection replaces the input value, but subsequent steps (like\n * clicking submit) must still execute so the payload reaches the server\n * and gets reflected back in the response.\n */\nasync function replayWithPayload(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n payloadValue: string,\n startUrl: string,\n): Promise<void> {\n // Navigate to start\n await page.goto(startUrl, { waitUntil: \"domcontentloaded\" });\n\n let injected = false;\n\n for (const step of session.steps) {\n const browserStep = step as BrowserStep;\n\n try {\n switch (browserStep.type) {\n case \"browser.navigate\":\n if (injected && browserStep.url.includes(\"sid=\")) {\n continue;\n }\n await page.goto(browserStep.url, { waitUntil: \"domcontentloaded\" });\n break;\n\n case \"browser.click\":\n if (injected) {\n await Promise.all([\n page\n .waitForNavigation({\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n })\n .catch(() => {}),\n page.click(browserStep.selector, { timeout: 5000 }),\n ]);\n } else {\n await page.click(browserStep.selector, { timeout: 5000 });\n }\n break;\n\n case \"browser.input\": {\n const value =\n step.id === targetStep.id ? payloadValue : browserStep.value;\n await page.fill(browserStep.selector, value, { timeout: 5000 });\n if (step.id === targetStep.id) {\n injected = true;\n }\n break;\n }\n\n case \"browser.keypress\": {\n const modifiers = browserStep.modifiers ?? [];\n for (const mod of modifiers) {\n await page.keyboard.down(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n await page.keyboard.press(browserStep.key);\n for (const mod of modifiers.reverse()) {\n await page.keyboard.up(mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\");\n }\n break;\n }\n\n case \"browser.scroll\":\n if (browserStep.selector) {\n await page.locator(browserStep.selector).evaluate((el, pos) => {\n el.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n } else {\n await page.evaluate((pos) => {\n window.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n }\n break;\n\n case \"browser.wait\":\n await page.waitForTimeout(browserStep.duration);\n break;\n }\n } catch {\n // Step failed, continue to next\n }\n }\n\n // Wait for any scripts to execute after all steps complete\n await page.waitForTimeout(500);\n}\n\n/**\n * Replay only the steps AFTER the injectable step.\n *\n * Used by cyclePayload() — the injectable step has already been filled,\n * so we just need to execute the remaining steps (click submit, etc.)\n */\nasync function replayStepsAfter(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n): Promise<void> {\n let pastTarget = false;\n\n for (const step of session.steps) {\n if (step.id === targetStep.id) {\n pastTarget = true;\n continue; // skip the injectable step itself (already filled)\n }\n\n if (!pastTarget) continue;\n\n const browserStep = step as BrowserStep;\n\n try {\n switch (browserStep.type) {\n case \"browser.navigate\":\n // Skip post-submission navigates\n break;\n\n case \"browser.click\":\n await Promise.all([\n page\n .waitForNavigation({\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n })\n .catch(() => {}),\n page.click(browserStep.selector, { timeout: 5000 }),\n ]);\n break;\n\n case \"browser.input\":\n await page.fill(browserStep.selector, browserStep.value, {\n timeout: 5000,\n });\n break;\n\n case \"browser.keypress\": {\n const modifiers = browserStep.modifiers ?? [];\n for (const mod of modifiers) {\n await page.keyboard.down(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n await page.keyboard.press(browserStep.key);\n for (const mod of modifiers.reverse()) {\n await page.keyboard.up(mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\");\n }\n break;\n }\n\n case \"browser.scroll\":\n break; // skip scrolls in fast path\n\n case \"browser.wait\":\n break; // skip waits in fast path\n }\n } catch {\n // Step failed, continue\n }\n }\n\n // Wait for scripts to execute\n await page.waitForTimeout(500);\n}\n\n// ── Detection Helpers ──────────────────────────────────────────────────\n\n/**\n * Check for payload reflection in page content\n */\nasync function checkReflection(\n page: Page,\n step: Step & { type: \"browser.input\" },\n payloadSet: RuntimePayload,\n payloadValue: string,\n): Promise<Finding | undefined> {\n const content = await page.content();\n\n // Check for reflection patterns\n for (const pattern of payloadSet.detectPatterns) {\n if (pattern.test(content)) {\n return {\n type: payloadSet.category,\n severity: getSeverity(payloadSet.category),\n title: `${payloadSet.category.toUpperCase()} vulnerability detected`,\n description: `Payload pattern was reflected in page content`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n evidence: content.match(pattern)?.[0]?.slice(0, 200),\n };\n }\n }\n\n // Check if payload appears verbatim\n if (content.includes(payloadValue)) {\n return {\n type: payloadSet.category,\n severity: \"medium\",\n title: `Potential ${payloadSet.category.toUpperCase()} - payload reflection`,\n description: `Payload was reflected in page without encoding`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n };\n }\n\n return undefined;\n}\n\n/**\n * Determine severity based on vulnerability category\n */\nfunction 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 default:\n return \"medium\";\n }\n}\n\n// ── Payload Interleaving ───────────────────────────────────────────────\n\n/**\n * Build flat list of payloads, interleaved by category (round-robin).\n *\n * This ensures we test at least one payload from each category quickly,\n * so the dedup early-break fires sooner on slow SPAs.\n *\n * Before: [sqli1, sqli2, ..., sqli50, xss1, xss2, ..., xss50]\n * After: [sqli1, xss1, sqli2, xss2, ..., sqli50, xss50]\n */\nfunction interleavePayloads(payloads: RuntimePayload[]): PayloadItem[] {\n const result: PayloadItem[] = [];\n const payloadsByCategory = payloads.map((ps) =>\n ps.payloads.map((value) => ({ payloadSet: ps, value })),\n );\n const maxLen = Math.max(...payloadsByCategory.map((c) => c.length));\n for (let i = 0; i < maxLen; i++) {\n for (const category of payloadsByCategory) {\n if (i < category.length) {\n result.push(category[i]);\n }\n }\n }\n return result;\n}\n","/**\n * Browser Crawler\n *\n * Automated session generator for the browser driver.\n * Crawls a web application using Playwright to discover:\n * - Forms with input fields and submit buttons\n * - Links to follow for deeper crawling\n *\n * Outputs Session[] that are directly compatible with BrowserRunner.\n *\n * This is the \"auto-record\" mode — instead of a human clicking around,\n * the crawler automatically discovers injection points.\n */\n\nimport type { Page, Browser, BrowserContext } from \"playwright\";\nimport type { Session, Step, CrawlOptions } from \"@vulcn/engine\";\nimport { launchBrowser, type BrowserType } from \"./browser\";\n\n// ── Internal Types ─────────────────────────────────────────────────────\n\ninterface DiscoveredInput {\n selector: string;\n type: string;\n name: string;\n injectable: boolean;\n placeholder?: string;\n}\n\ninterface DiscoveredForm {\n pageUrl: string;\n formSelector: string;\n action: string;\n method: string;\n inputs: DiscoveredInput[];\n submitSelector: string | null;\n}\n\n// Input types that accept text and can be injected with payloads\nconst INJECTABLE_INPUT_TYPES = new Set([\n \"text\",\n \"search\",\n \"url\",\n \"email\",\n \"tel\",\n \"password\",\n \"textarea\",\n \"\",\n]);\n\nconst CRAWL_DEFAULTS = {\n maxDepth: 2,\n maxPages: 20,\n pageTimeout: 10000,\n sameOrigin: true,\n};\n\n// ── Public API ─────────────────────────────────────────────────────────\n\nexport interface BrowserCrawlConfig {\n /** Starting URL */\n startUrl: string;\n /** Browser type */\n browser?: BrowserType;\n /** Run headless */\n headless?: boolean;\n /** Viewport */\n viewport?: { width: number; height: number };\n}\n\n/**\n * Crawl a URL and generate sessions.\n *\n * This is called by the browser driver's recorder.crawl() method.\n */\nexport async function crawlAndBuildSessions(\n config: BrowserCrawlConfig,\n options: CrawlOptions = {},\n): Promise<Session[]> {\n const opts = { ...CRAWL_DEFAULTS, ...options } as typeof CRAWL_DEFAULTS &\n CrawlOptions;\n const startUrl = config.startUrl;\n\n let normalizedUrl: URL;\n try {\n normalizedUrl = new URL(startUrl);\n } catch {\n throw new Error(`Invalid URL: ${startUrl}`);\n }\n\n const origin = normalizedUrl.origin;\n const visited = new Set<string>();\n const allForms: DiscoveredForm[] = [];\n\n // BFS queue: [url, depth]\n const queue: [string, number][] = [[normalizedUrl.href, 0]];\n\n // Use the shared browser launcher\n const { browser } = await launchBrowser({\n browser: config.browser ?? \"chromium\",\n headless: config.headless ?? true,\n });\n\n const context: BrowserContext = await browser.newContext({\n viewport: config.viewport ?? { width: 1280, height: 720 },\n ...(options.storageState\n ? { storageState: JSON.parse(options.storageState) }\n : {}),\n });\n\n try {\n while (queue.length > 0 && visited.size < opts.maxPages) {\n const [url, depth] = queue.shift()!;\n const normalizedPageUrl = normalizeUrl(url);\n\n if (visited.has(normalizedPageUrl)) continue;\n visited.add(normalizedPageUrl);\n\n console.log(`[crawler] [depth=${depth}] Crawling: ${normalizedPageUrl}`);\n\n const page: Page = await context.newPage();\n\n try {\n await page.goto(normalizedPageUrl, {\n waitUntil: \"domcontentloaded\",\n timeout: opts.pageTimeout,\n });\n\n // Wait for JS-rendered content\n await page.waitForTimeout(1000);\n\n // Discover forms\n const forms = await discoverForms(page, normalizedPageUrl);\n allForms.push(...forms);\n\n const injectableCount = forms.reduce(\n (s, f) => s + f.inputs.filter((i) => i.injectable).length,\n 0,\n );\n console.log(\n `[crawler] Found ${forms.length} form(s), ${injectableCount} injectable input(s)`,\n );\n\n opts.onPageCrawled?.(normalizedPageUrl, forms.length);\n\n // Follow links\n if (depth < opts.maxDepth) {\n const links = await discoverLinks(page, origin, opts.sameOrigin);\n for (const link of links) {\n const normalizedLink = normalizeUrl(link);\n if (!visited.has(normalizedLink)) {\n queue.push([normalizedLink, depth + 1]);\n }\n }\n console.log(`[crawler] Found ${links.length} link(s) to follow`);\n }\n } catch (err) {\n console.warn(\n `[crawler] Failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n await page.close();\n }\n }\n } finally {\n await browser.close();\n }\n\n console.log(\n `[crawler] Complete: ${visited.size} page(s), ${allForms.length} form(s)`,\n );\n\n // Convert discovered forms to sessions\n return buildSessions(allForms);\n}\n\n// ── Form Discovery ─────────────────────────────────────────────────────\n\nasync function discoverForms(\n page: Page,\n pageUrl: string,\n): Promise<DiscoveredForm[]> {\n const forms: DiscoveredForm[] = [];\n\n // 1. Explicit <form> elements\n const explicitForms = await page.evaluate(() => {\n const results: Array<{\n formIndex: number;\n action: string;\n method: string;\n inputs: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n }>;\n submitSelector: string | null;\n }> = [];\n\n const formElements = document.querySelectorAll(\"form\");\n\n formElements.forEach((form, formIndex) => {\n const inputs: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n }> = [];\n\n const inputEls = form.querySelectorAll(\n 'input, textarea, [contenteditable=\"true\"]',\n );\n\n inputEls.forEach((input, inputIndex) => {\n const el = input as HTMLInputElement;\n const type =\n el.tagName.toLowerCase() === \"textarea\"\n ? \"textarea\"\n : el.getAttribute(\"type\") || \"text\";\n const name = el.name || el.id || `input-${inputIndex}`;\n\n let selector = \"\";\n if (el.id) {\n selector = `#${CSS.escape(el.id)}`;\n } else if (el.name) {\n selector = `form:nth-of-type(${formIndex + 1}) [name=\"${CSS.escape(el.name)}\"]`;\n } else {\n selector = `form:nth-of-type(${formIndex + 1}) ${el.tagName.toLowerCase()}:nth-of-type(${inputIndex + 1})`;\n }\n\n inputs.push({\n selector,\n type,\n name,\n placeholder: el.placeholder || \"\",\n });\n });\n\n // Find submit trigger\n let submitSelector: string | null = null;\n const submitBtn =\n form.querySelector('button[type=\"submit\"], input[type=\"submit\"]') ||\n form.querySelector(\"button:not([type])\") ||\n form.querySelector('button, input[type=\"button\"]');\n\n if (submitBtn) {\n const btn = submitBtn as HTMLElement;\n if (btn.id) {\n submitSelector = `#${CSS.escape(btn.id)}`;\n } else {\n const tag = btn.tagName.toLowerCase();\n const type = btn.getAttribute(\"type\");\n if (type) {\n submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}[type=\"${type}\"]`;\n } else {\n submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}`;\n }\n }\n }\n\n results.push({\n formIndex,\n action: form.action || \"\",\n method: (form.method || \"GET\").toUpperCase(),\n inputs,\n submitSelector,\n });\n });\n\n return results;\n });\n\n for (const form of explicitForms) {\n if (form.inputs.length === 0) continue;\n\n forms.push({\n pageUrl,\n formSelector: `form:nth-of-type(${form.formIndex + 1})`,\n action: form.action,\n method: form.method,\n inputs: form.inputs.map((input) => ({\n selector: input.selector,\n type: input.type,\n name: input.name,\n injectable: INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase()),\n placeholder: input.placeholder || undefined,\n })),\n submitSelector: form.submitSelector,\n });\n }\n\n // 2. Standalone inputs NOT inside a <form>\n const standaloneInputs = await page.evaluate(() => {\n const results: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n nearbyButtonSelector: string | null;\n }> = [];\n\n const allInputs = document.querySelectorAll(\n 'input:not(form input), textarea:not(form textarea), [contenteditable=\"true\"]:not(form [contenteditable])',\n );\n\n allInputs.forEach((input) => {\n const el = input as HTMLInputElement;\n const type =\n el.tagName.toLowerCase() === \"textarea\"\n ? \"textarea\"\n : el.getAttribute(\"type\") || \"text\";\n const name = el.name || el.id || \"\";\n\n let selector = \"\";\n if (el.id) {\n selector = `#${CSS.escape(el.id)}`;\n } else if (el.name) {\n selector = `[name=\"${CSS.escape(el.name)}\"]`;\n } else {\n selector = `${el.tagName.toLowerCase()}[type=\"${type}\"]`;\n }\n\n // Look for nearby button\n let nearbyButtonSelector: string | null = null;\n const parent = el.parentElement;\n if (parent) {\n const btn =\n parent.querySelector(\"button\") ||\n parent.querySelector('input[type=\"submit\"]') ||\n parent.querySelector('input[type=\"button\"]');\n if (btn) {\n const btnEl = btn as HTMLElement;\n if (btnEl.id) {\n nearbyButtonSelector = `#${CSS.escape(btnEl.id)}`;\n }\n }\n }\n\n results.push({\n selector,\n type,\n name,\n placeholder: el.placeholder || \"\",\n nearbyButtonSelector,\n });\n });\n\n return results;\n });\n\n for (const input of standaloneInputs) {\n if (!INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase())) continue;\n\n forms.push({\n pageUrl,\n formSelector: \"(standalone)\",\n action: pageUrl,\n method: \"GET\",\n inputs: [\n {\n selector: input.selector,\n type: input.type,\n name: input.name,\n injectable: true,\n placeholder: input.placeholder || undefined,\n },\n ],\n submitSelector: input.nearbyButtonSelector,\n });\n }\n\n return forms;\n}\n\n// ── Link Discovery ─────────────────────────────────────────────────────\n\n/** Query parameter names commonly used for external redirects */\nconst REDIRECT_PARAMS = new Set([\n \"to\",\n \"url\",\n \"redirect\",\n \"redirect_uri\",\n \"redirect_url\",\n \"return\",\n \"return_url\",\n \"returnto\",\n \"next\",\n \"goto\",\n \"dest\",\n \"destination\",\n \"continue\",\n \"target\",\n \"rurl\",\n \"out\",\n \"link\",\n \"forward\",\n]);\n\n/**\n * Check if a link is a same-origin redirect that points to an external URL.\n * Example: /redirect?to=https://github.com/...\n */\nfunction isExternalRedirectLink(link: string, origin: string): boolean {\n try {\n const parsed = new URL(link);\n // Only check links on our origin\n if (parsed.origin !== origin) return false;\n\n for (const [key, value] of parsed.searchParams) {\n if (REDIRECT_PARAMS.has(key.toLowerCase())) {\n // If the param value looks like an external URL, skip this link\n try {\n const targetUrl = new URL(value);\n if (targetUrl.origin !== origin) return true;\n } catch {\n // Not a URL — that's fine\n }\n }\n }\n return false;\n } catch {\n return false;\n }\n}\n\nasync function discoverLinks(\n page: Page,\n origin: string,\n sameOrigin: boolean,\n): Promise<string[]> {\n const links = await page.evaluate(() => {\n return Array.from(document.querySelectorAll(\"a[href]\"))\n .map((a) => (a as HTMLAnchorElement).href)\n .filter((href) => href.startsWith(\"http\"));\n });\n\n return links.filter((link) => {\n try {\n const linkOrigin = new URL(link).origin;\n // Filter out links to different origins\n if (sameOrigin && linkOrigin !== origin) return false;\n // Filter out redirect links that point to external URLs\n if (isExternalRedirectLink(link, origin)) return false;\n return true;\n } catch {\n return false;\n }\n });\n}\n\n// ── Session Builder ────────────────────────────────────────────────────\n\n/**\n * Convert discovered forms into Vulcn sessions.\n *\n * Each form with injectable inputs becomes one session:\n * navigate → fill input(s) → click submit / press Enter\n */\nfunction buildSessions(forms: DiscoveredForm[]): Session[] {\n const targetForms = forms.filter((f) => f.inputs.some((i) => i.injectable));\n\n return targetForms.map((form, idx) => buildSessionForForm(form, idx));\n}\n\nfunction buildSessionForForm(form: DiscoveredForm, index: number): Session {\n const steps: Step[] = [];\n let stepNum = 1;\n\n // Step 1: Navigate\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.navigate\",\n url: form.pageUrl,\n timestamp: Date.now(),\n } as Step);\n\n // Steps 2+: Fill each injectable input\n const injectableInputs = form.inputs.filter((i) => i.injectable);\n\n for (const input of injectableInputs) {\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.input\",\n selector: input.selector,\n value: \"test\",\n injectable: true,\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n }\n\n // Final step: Submit\n if (form.submitSelector) {\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.click\",\n selector: form.submitSelector,\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n } else {\n // No submit button — press Enter\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.keypress\",\n key: \"Enter\",\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n }\n\n const inputNames = injectableInputs.map((i) => i.name || i.type).join(\", \");\n const pagePath = (() => {\n try {\n return new URL(form.pageUrl).pathname;\n } catch {\n return form.pageUrl;\n }\n })();\n\n return {\n name: `Crawl: ${pagePath} — form ${index + 1} (${inputNames})`,\n driver: \"browser\",\n driverConfig: {\n startUrl: form.pageUrl,\n browser: \"chromium\",\n headless: true,\n viewport: { width: 1280, height: 720 },\n },\n steps,\n metadata: {\n recordedAt: new Date().toISOString(),\n version: \"0.3.0\",\n source: \"crawler\",\n formAction: form.action,\n formMethod: form.method,\n },\n };\n}\n\n// ── Utilities ──────────────────────────────────────────────────────────\n\nfunction normalizeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.hash = \"\";\n if (parsed.pathname !== \"/\" && parsed.pathname.endsWith(\"/\")) {\n parsed.pathname = parsed.pathname.slice(0, -1);\n }\n return parsed.href;\n } catch {\n return url;\n }\n}\n","/**\n * Login Form Auto-Detection & Auth Replay\n *\n * Detects login forms on a page and fills them with credentials.\n * After login, captures the browser storage state (cookies + localStorage)\n * for re-use in subsequent scans.\n *\n * Detection strategy:\n * 1. Find forms with a password input (strongest signal)\n * 2. Find username field via heuristics (name, id, autocomplete, type)\n * 3. Find submit button\n * 4. Fall back to custom selectors from credentials\n */\n\nimport type { Page, BrowserContext } from \"playwright\";\nimport type { FormCredentials } from \"@vulcn/engine\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface LoginForm {\n /** Username input selector */\n usernameSelector: string;\n /** Password input selector */\n passwordSelector: string;\n /** Submit button selector (may be null if not found) */\n submitSelector: string | null;\n /** Whether the form was detected automatically */\n autoDetected: boolean;\n}\n\nexport interface LoginResult {\n /** Whether login succeeded */\n success: boolean;\n /** Message for logging */\n message: string;\n /** Playwright storage state JSON (cookies + localStorage) */\n storageState?: string;\n}\n\n// ── Detection ──────────────────────────────────────────────────────────\n\n/**\n * Auto-detect a login form on the current page.\n *\n * Strategy:\n * 1. Find `<form>` elements containing an `input[type=\"password\"]`\n * 2. Within that form, find the username field using heuristics\n * 3. Find the submit button\n *\n * Falls back to page-wide search if no enclosing <form> is found.\n */\nexport async function detectLoginForm(page: Page): Promise<LoginForm | null> {\n return page.evaluate(() => {\n // Helper: find the best username input in a container\n function findUsernameInput(container: Element): HTMLInputElement | null {\n // Priority-ordered selectors for username fields\n const selectors = [\n 'input[autocomplete=\"username\"]',\n 'input[autocomplete=\"email\"]',\n 'input[type=\"email\"]',\n 'input[name*=\"user\" i]',\n 'input[name*=\"login\" i]',\n 'input[name*=\"email\" i]',\n 'input[id*=\"user\" i]',\n 'input[id*=\"login\" i]',\n 'input[id*=\"email\" i]',\n 'input[name*=\"name\" i]',\n 'input[type=\"text\"]',\n ];\n\n for (const sel of selectors) {\n const el = container.querySelector(sel) as HTMLInputElement | null;\n if (el && el.type !== \"password\" && el.type !== \"hidden\") {\n return el;\n }\n }\n\n return null;\n }\n\n // Helper: find submit button in a container\n function findSubmitButton(container: Element): HTMLElement | null {\n const selectors = [\n 'button[type=\"submit\"]',\n 'input[type=\"submit\"]',\n \"button:not([type])\",\n 'button[type=\"button\"]',\n ];\n\n for (const sel of selectors) {\n const el = container.querySelector(sel) as HTMLElement | null;\n if (el) return el;\n }\n\n return null;\n }\n\n // Helper: get a unique CSS selector for an element\n function getSelector(el: Element): string {\n if (el.id) return `#${CSS.escape(el.id)}`;\n if (el.getAttribute(\"name\"))\n return `${el.tagName.toLowerCase()}[name=\"${CSS.escape(el.getAttribute(\"name\")!)}\"]`;\n if (el.getAttribute(\"type\") && el.tagName === \"INPUT\")\n return `input[type=\"${el.getAttribute(\"type\")}\"]`;\n\n // Fall back to nth-child\n const parent = el.parentElement;\n if (!parent) return el.tagName.toLowerCase();\n const siblings = Array.from(parent.children);\n const index = siblings.indexOf(el) + 1;\n return `${parent.tagName.toLowerCase()} > ${el.tagName.toLowerCase()}:nth-child(${index})`;\n }\n\n // Strategy 1: Find <form> containing password input\n const forms = document.querySelectorAll(\"form\");\n for (const form of forms) {\n const passwordInput = form.querySelector(\n 'input[type=\"password\"]',\n ) as HTMLInputElement | null;\n if (!passwordInput) continue;\n\n const usernameInput = findUsernameInput(form);\n const submitButton = findSubmitButton(form);\n\n return {\n usernameSelector: usernameInput\n ? getSelector(usernameInput)\n : 'input[type=\"text\"]',\n passwordSelector: getSelector(passwordInput),\n submitSelector: submitButton ? getSelector(submitButton) : null,\n autoDetected: true,\n };\n }\n\n // Strategy 2: No <form> — look for password input anywhere on page\n const passwordInput = document.querySelector(\n 'input[type=\"password\"]',\n ) as HTMLInputElement | null;\n if (passwordInput) {\n const usernameInput = findUsernameInput(document.body);\n const submitButton = findSubmitButton(document.body);\n\n return {\n usernameSelector: usernameInput\n ? getSelector(usernameInput)\n : 'input[type=\"text\"]',\n passwordSelector: getSelector(passwordInput),\n submitSelector: submitButton ? getSelector(submitButton) : null,\n autoDetected: true,\n };\n }\n\n return null;\n });\n}\n\n// ── Login ──────────────────────────────────────────────────────────────\n\n/**\n * Perform login using detected form or custom selectors.\n *\n * Flow:\n * 1. Navigate to login URL (or target URL)\n * 2. Detect login form (or use custom selectors from credentials)\n * 3. Fill username + password\n * 4. Submit form\n * 5. Wait for navigation\n * 6. Check for logged-in indicator\n * 7. Capture storage state\n */\nexport async function performLogin(\n page: Page,\n context: BrowserContext,\n credentials: FormCredentials,\n options: {\n targetUrl: string;\n loggedInIndicator?: string;\n loggedOutIndicator?: string;\n },\n): Promise<LoginResult> {\n const loginUrl = credentials.loginUrl ?? options.targetUrl;\n\n // Navigate to login page\n await page.goto(loginUrl, { waitUntil: \"domcontentloaded\", timeout: 15000 });\n\n // Determine selectors — custom overrides or auto-detect\n let usernameSelector: string;\n let passwordSelector: string;\n let submitSelector: string | null;\n\n if (credentials.userSelector && credentials.passSelector) {\n // Use custom selectors\n usernameSelector = credentials.userSelector;\n passwordSelector = credentials.passSelector;\n submitSelector = null; // Will try to find automatically\n } else {\n // Auto-detect\n const form = await detectLoginForm(page);\n\n if (!form) {\n return {\n success: false,\n message: `No login form detected on ${loginUrl}. Use --user-field and --pass-field to specify selectors.`,\n };\n }\n\n usernameSelector = form.usernameSelector;\n passwordSelector = form.passwordSelector;\n submitSelector = form.submitSelector;\n }\n\n // Fill credentials\n try {\n await page.fill(usernameSelector, credentials.username, { timeout: 5000 });\n } catch {\n return {\n success: false,\n message: `Could not find username field: ${usernameSelector}`,\n };\n }\n\n try {\n await page.fill(passwordSelector, credentials.password, { timeout: 5000 });\n } catch {\n return {\n success: false,\n message: `Could not find password field: ${passwordSelector}`,\n };\n }\n\n // Submit form\n try {\n if (submitSelector) {\n await Promise.all([\n page\n .waitForNavigation({ waitUntil: \"domcontentloaded\", timeout: 10000 })\n .catch(() => {}),\n page.click(submitSelector, { timeout: 5000 }),\n ]);\n } else {\n // Try pressing Enter on the password field\n await Promise.all([\n page\n .waitForNavigation({ waitUntil: \"domcontentloaded\", timeout: 10000 })\n .catch(() => {}),\n page.press(passwordSelector, \"Enter\"),\n ]);\n }\n } catch {\n return {\n success: false,\n message: \"Failed to submit login form\",\n };\n }\n\n // Wait for page to settle\n await page.waitForTimeout(1000);\n\n // Check if login succeeded\n const bodyText = await page.textContent(\"body\").catch(() => \"\");\n\n if (\n options.loggedOutIndicator &&\n bodyText?.includes(options.loggedOutIndicator)\n ) {\n return {\n success: false,\n message: `Login failed — \"${options.loggedOutIndicator}\" still visible on page`,\n };\n }\n\n if (\n options.loggedInIndicator &&\n !bodyText?.includes(options.loggedInIndicator)\n ) {\n return {\n success: false,\n message: `Login uncertain — \"${options.loggedInIndicator}\" not found on page`,\n };\n }\n\n // Capture storage state (cookies + localStorage)\n const storageState = JSON.stringify(await context.storageState());\n\n return {\n success: true,\n message: \"Login successful\",\n storageState,\n };\n}\n\n// ── Session Expiry Detection ───────────────────────────────────────────\n\n/**\n * Check if the current session is still alive.\n *\n * Used during long-running scans to detect session expiry\n * and trigger re-authentication.\n */\nexport async function checkSessionAlive(\n page: Page,\n config: {\n loggedInIndicator?: string;\n loggedOutIndicator?: string;\n },\n): Promise<boolean> {\n try {\n const bodyText = await page.textContent(\"body\");\n if (!bodyText) return true; // Can't determine, assume alive\n\n // Check for logged-out indicator (strongest signal of expiry)\n if (\n config.loggedOutIndicator &&\n bodyText.includes(config.loggedOutIndicator)\n ) {\n return false;\n }\n\n // Check for missing logged-in indicator\n if (\n config.loggedInIndicator &&\n !bodyText.includes(config.loggedInIndicator)\n ) {\n return false;\n }\n\n return true;\n } catch {\n // Page error — assume session is dead\n return false;\n }\n}\n"],"mappings":"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","names":["passwordInput"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/recorder.ts","../src/runner.ts","../src/http-scanner.ts","../src/crawler.ts","../src/auth.ts"],"sourcesContent":["/**\n * @vulcn/driver-browser\n *\n * Browser recording driver for Vulcn.\n * Uses Playwright to record and replay web application interactions.\n *\n * Step types:\n * - browser.navigate - Navigate to a URL\n * - browser.click - Click an element\n * - browser.input - Type into an input field\n * - browser.keypress - Press a key\n * - browser.scroll - Scroll the page\n * - browser.wait - Wait for a duration\n */\n\nimport { z } from \"zod\";\nimport type {\n VulcnDriver,\n RecorderDriver,\n RunnerDriver,\n RecordingHandle,\n RecordOptions,\n CrawlOptions,\n Session,\n Step,\n RunContext,\n RunResult,\n} from \"@vulcn/engine\";\n\nimport { BrowserRecorder } from \"./recorder\";\nimport { BrowserRunner } from \"./runner\";\nimport { crawlAndBuildSessions } from \"./crawler\";\n\n/**\n * Browser driver configuration schema\n */\nexport const configSchema = z.object({\n /** Starting URL for recording */\n startUrl: z.string().url().optional(),\n\n /** Browser type */\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).default(\"chromium\"),\n\n /** Viewport size */\n viewport: z\n .object({\n width: z.number().default(1280),\n height: z.number().default(720),\n })\n .default({ width: 1280, height: 720 }),\n\n /** Run headless */\n headless: z.boolean().default(false),\n});\n\nexport type BrowserConfig = z.infer<typeof configSchema>;\n\n/**\n * Browser step types\n */\nexport const BROWSER_STEP_TYPES = [\n \"browser.navigate\",\n \"browser.click\",\n \"browser.input\",\n \"browser.keypress\",\n \"browser.scroll\",\n \"browser.wait\",\n] as const;\n\nexport type BrowserStepType = (typeof BROWSER_STEP_TYPES)[number];\n\n/**\n * Browser-specific step schemas\n */\nexport const BrowserStepSchema = z.discriminatedUnion(\"type\", [\n z.object({\n id: z.string(),\n type: z.literal(\"browser.navigate\"),\n url: z.string(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.click\"),\n selector: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.input\"),\n selector: z.string(),\n value: z.string(),\n injectable: z.boolean().default(true),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.keypress\"),\n key: z.string(),\n modifiers: z.array(z.string()).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.scroll\"),\n selector: z.string().optional(),\n position: z.object({ x: z.number(), y: z.number() }),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.wait\"),\n duration: z.number(),\n timestamp: z.number(),\n }),\n]);\n\nexport type BrowserStep = z.infer<typeof BrowserStepSchema>;\n\n/**\n * Browser recorder implementation\n */\nconst recorderDriver: RecorderDriver = {\n async start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle> {\n const parsedConfig = configSchema.parse(config);\n return BrowserRecorder.start(parsedConfig, options);\n },\n\n async crawl(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]> {\n const parsedConfig = configSchema.parse(config);\n const result = await crawlAndBuildSessions(\n {\n startUrl: parsedConfig.startUrl ?? \"\",\n browser: parsedConfig.browser,\n headless: parsedConfig.headless,\n viewport: parsedConfig.viewport,\n },\n options,\n );\n // The driver interface expects Session[] — CapturedRequests are\n // available separately via crawlAndBuildSessions() for Tier 1.\n return result.sessions;\n },\n};\n\n/**\n * Browser runner implementation\n */\nconst runnerDriver: RunnerDriver = {\n async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n return BrowserRunner.execute(session, ctx);\n },\n};\n\n/**\n * Browser driver for Vulcn\n */\nconst browserDriver: VulcnDriver = {\n name: \"browser\",\n version: \"0.1.0\",\n apiVersion: 1,\n description: \"Browser recording driver using Playwright\",\n configSchema,\n stepTypes: [...BROWSER_STEP_TYPES],\n recorder: recorderDriver,\n runner: runnerDriver,\n};\n\nexport default browserDriver;\n\n// Re-export utilities\nexport { BrowserRecorder } from \"./recorder\";\nexport { BrowserRunner } from \"./runner\";\nexport { crawlAndBuildSessions } from \"./crawler\";\nexport type { CrawlResult } from \"./crawler\";\nexport { httpScan, buildCapturedRequests } from \"./http-scanner\";\nexport type {\n HttpScanResult,\n HttpScanOptions,\n ReflectedRequest,\n} from \"./http-scanner\";\nexport { launchBrowser, checkBrowsers, installBrowsers } from \"./browser\";\nexport { detectLoginForm, performLogin, checkSessionAlive } from \"./auth\";\nexport type { LoginForm, LoginResult } from \"./auth\";\n","/**\n * Browser utilities for @vulcn/driver-browser\n * Smart browser launching with system browser fallback\n */\n\nimport { chromium, firefox, webkit, type Browser } from \"playwright\";\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\nexport type BrowserType = \"chromium\" | \"firefox\" | \"webkit\";\n\nexport interface LaunchOptions {\n browser?: BrowserType;\n headless?: boolean;\n}\n\nexport interface BrowserLaunchResult {\n browser: Browser;\n channel?: string;\n}\n\nexport class BrowserNotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BrowserNotFoundError\";\n }\n}\n\n/**\n * Launch a browser with smart fallback:\n * 1. Try system Chrome/Edge first (zero-install experience)\n * 2. Fall back to Playwright's bundled browsers\n */\nexport async function launchBrowser(\n options: LaunchOptions = {},\n): Promise<BrowserLaunchResult> {\n const browserType = options.browser ?? \"chromium\";\n const headless = options.headless ?? false;\n\n // For Chromium, try system browsers first\n if (browserType === \"chromium\") {\n // Try system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless,\n });\n return { browser, channel: \"chrome\" };\n } catch {\n // Chrome not available\n }\n\n // Try system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless,\n });\n return { browser, channel: \"msedge\" };\n } catch {\n // Edge not available\n }\n\n // Fall back to Playwright's bundled Chromium\n try {\n const browser = await chromium.launch({ headless });\n return { browser, channel: \"chromium\" };\n } catch {\n throw new BrowserNotFoundError(\n \"No Chromium browser found. Install Chrome or run: vulcn install chromium\",\n );\n }\n }\n\n // Firefox\n if (browserType === \"firefox\") {\n try {\n const browser = await firefox.launch({ headless });\n return { browser, channel: \"firefox\" };\n } catch {\n throw new BrowserNotFoundError(\n \"Firefox not found. Run: vulcn install firefox\",\n );\n }\n }\n\n // WebKit\n if (browserType === \"webkit\") {\n try {\n const browser = await webkit.launch({ headless });\n return { browser, channel: \"webkit\" };\n } catch {\n throw new BrowserNotFoundError(\n \"WebKit not found. Run: vulcn install webkit\",\n );\n }\n }\n\n throw new BrowserNotFoundError(`Unknown browser type: ${browserType}`);\n}\n\n/**\n * Install Playwright browsers\n */\nexport async function installBrowsers(\n browsers: BrowserType[] = [\"chromium\"],\n): Promise<void> {\n const browserArg = browsers.join(\" \");\n await execAsync(`npx playwright install ${browserArg}`);\n}\n\n/**\n * Check which browsers are available\n */\nexport async function checkBrowsers(): Promise<{\n systemChrome: boolean;\n systemEdge: boolean;\n playwrightChromium: boolean;\n playwrightFirefox: boolean;\n playwrightWebkit: boolean;\n}> {\n const results = {\n systemChrome: false,\n systemEdge: false,\n playwrightChromium: false,\n playwrightFirefox: false,\n playwrightWebkit: false,\n };\n\n // Check system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless: true,\n });\n await browser.close();\n results.systemChrome = true;\n } catch {\n // Not available\n }\n\n // Check system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless: true,\n });\n await browser.close();\n results.systemEdge = true;\n } catch {\n // Not available\n }\n\n // Check Playwright Chromium\n try {\n const browser = await chromium.launch({ headless: true });\n await browser.close();\n results.playwrightChromium = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright Firefox\n try {\n const browser = await firefox.launch({ headless: true });\n await browser.close();\n results.playwrightFirefox = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright WebKit\n try {\n const browser = await webkit.launch({ headless: true });\n await browser.close();\n results.playwrightWebkit = true;\n } catch {\n // Not installed\n }\n\n return results;\n}\n","/**\n * Browser Recorder Implementation\n *\n * Records browser interactions as replayable sessions.\n * Uses Playwright for browser automation.\n */\n\nimport type { Page } from \"playwright\";\nimport type {\n RecordingHandle,\n RecordOptions,\n Session,\n Step,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser } from \"./browser\";\nimport type { BrowserConfig, BrowserStep } from \"./index\";\n\n/**\n * Browser Recorder - captures browser interactions\n */\nexport class BrowserRecorder {\n /**\n * Start a new recording session\n */\n static async start(\n config: BrowserConfig,\n _options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const { startUrl, browser: browserType, viewport, headless } = config;\n\n if (!startUrl) {\n throw new Error(\"startUrl is required for browser recording\");\n }\n\n // Launch browser with smart fallback\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Navigate to start URL\n await page.goto(startUrl);\n\n // Track recording\n const startTime = Date.now();\n const steps: Step[] = [];\n let stepCounter = 0;\n\n const generateStepId = () => {\n stepCounter++;\n return `step_${String(stepCounter).padStart(3, \"0\")}`;\n };\n\n // Add initial navigation step\n steps.push({\n id: generateStepId(),\n type: \"browser.navigate\",\n url: startUrl,\n timestamp: 0,\n });\n\n // Attach event listeners\n BrowserRecorder.attachListeners(page, steps, startTime, generateStepId);\n\n return {\n async stop(): Promise<Session> {\n const session: Session = {\n name: `Recording ${new Date().toISOString()}`,\n driver: \"browser\",\n driverConfig: {\n browser: browserType,\n viewport,\n startUrl,\n },\n steps,\n metadata: {\n recordedAt: new Date().toISOString(),\n version: \"1\",\n },\n };\n\n await browser.close();\n return session;\n },\n\n async abort(): Promise<void> {\n await browser.close();\n },\n\n getSteps(): Step[] {\n return [...steps];\n },\n\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: Date.now() - startTime,\n } as Step);\n },\n };\n }\n\n /**\n * Attach event listeners to the page\n */\n private static attachListeners(\n page: Page,\n steps: Step[],\n startTime: number,\n generateStepId: () => string,\n ) {\n const getTimestamp = () => Date.now() - startTime;\n\n const addStep = (\n step: Omit<Step, \"id\" | \"timestamp\"> & { type: string },\n ) => {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: getTimestamp(),\n } as Step);\n };\n\n // Track navigation\n page.on(\"framenavigated\", (frame) => {\n if (frame === page.mainFrame()) {\n const url = frame.url();\n // Avoid duplicate nav steps for initial load\n const lastStep = steps[steps.length - 1];\n if (\n steps.length > 0 &&\n lastStep?.type === \"browser.navigate\" &&\n (lastStep as BrowserStep & { type: \"browser.navigate\" }).url === url\n ) {\n return;\n }\n addStep({\n type: \"browser.navigate\",\n url,\n });\n }\n });\n\n // Expose recording function to browser\n page.exposeFunction(\n \"__vulcn_record\",\n async (event: { type: string; data: Record<string, unknown> }) => {\n switch (event.type) {\n case \"click\": {\n const data = event.data as {\n selector: string;\n x: number;\n y: number;\n };\n addStep({\n type: \"browser.click\",\n selector: data.selector,\n position: { x: data.x, y: data.y },\n });\n break;\n }\n case \"input\": {\n const data = event.data as {\n selector: string;\n value: string;\n inputType: string | null;\n injectable: boolean;\n };\n addStep({\n type: \"browser.input\",\n selector: data.selector,\n value: data.value,\n injectable: data.injectable,\n });\n break;\n }\n case \"keypress\": {\n const data = event.data as { key: string; modifiers?: string[] };\n addStep({\n type: \"browser.keypress\",\n key: data.key,\n modifiers: data.modifiers,\n });\n break;\n }\n }\n },\n );\n\n // Inject recording script into every frame\n page.on(\"load\", async () => {\n await BrowserRecorder.injectRecordingScript(page);\n });\n\n // Inject into initial page\n BrowserRecorder.injectRecordingScript(page);\n }\n\n /**\n * Inject the recording script into the page\n */\n private static async injectRecordingScript(page: Page) {\n await page.evaluate(`\n (function() {\n if (window.__vulcn_injected) return;\n window.__vulcn_injected = true;\n\n var textInputTypes = ['text', 'password', 'email', 'search', 'url', 'tel', 'number'];\n\n function getSelector(el) {\n if (el.id) {\n return '#' + CSS.escape(el.id);\n }\n if (el.name) {\n var tag = el.tagName.toLowerCase();\n var nameSelector = tag + '[name=\"' + el.name + '\"]';\n if (document.querySelectorAll(nameSelector).length === 1) {\n return nameSelector;\n }\n }\n if (el.dataset && el.dataset.testid) {\n return '[data-testid=\"' + el.dataset.testid + '\"]';\n }\n if (el.tagName === 'INPUT' && el.type && el.name) {\n var inputSelector = 'input[type=\"' + el.type + '\"][name=\"' + el.name + '\"]';\n if (document.querySelectorAll(inputSelector).length === 1) {\n return inputSelector;\n }\n }\n if (el.className && typeof el.className === 'string') {\n var classes = el.className.trim().split(/\\\\s+/).filter(function(c) { return c.length > 0; });\n if (classes.length > 0) {\n var classSelector = el.tagName.toLowerCase() + '.' + classes.map(function(c) { return CSS.escape(c); }).join('.');\n if (document.querySelectorAll(classSelector).length === 1) {\n return classSelector;\n }\n }\n }\n var path = [];\n var current = el;\n while (current && current !== document.body) {\n var tag = current.tagName.toLowerCase();\n var parent = current.parentElement;\n if (parent) {\n var siblings = Array.from(parent.children).filter(function(c) { return c.tagName === current.tagName; });\n if (siblings.length > 1) {\n var index = siblings.indexOf(current) + 1;\n tag = tag + ':nth-of-type(' + index + ')';\n }\n }\n path.unshift(tag);\n current = parent;\n }\n return path.join(' > ');\n }\n\n function getInputType(el) {\n if (el.tagName === 'INPUT') return el.type || 'text';\n if (el.tagName === 'TEXTAREA') return 'textarea';\n if (el.tagName === 'SELECT') return 'select';\n return null;\n }\n\n function isTextInjectable(el) {\n var inputType = getInputType(el);\n if (!inputType) return false;\n if (inputType === 'textarea') return true;\n if (inputType === 'select') return false;\n return textInputTypes.indexOf(inputType) !== -1;\n }\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n window.__vulcn_record({\n type: 'click',\n data: {\n selector: getSelector(target),\n x: e.clientX,\n y: e.clientY\n }\n });\n }, true);\n\n document.addEventListener('change', function(e) {\n var target = e.target;\n if ('value' in target) {\n var inputType = getInputType(target);\n window.__vulcn_record({\n type: 'input',\n data: {\n selector: getSelector(target),\n value: target.value,\n inputType: inputType,\n injectable: isTextInjectable(target)\n }\n });\n }\n }, true);\n\n document.addEventListener('keydown', function(e) {\n if (e.ctrlKey || e.metaKey || e.altKey) {\n var modifiers = [];\n if (e.ctrlKey) modifiers.push('ctrl');\n if (e.metaKey) modifiers.push('meta');\n if (e.altKey) modifiers.push('alt');\n if (e.shiftKey) modifiers.push('shift');\n\n window.__vulcn_record({\n type: 'keypress',\n data: {\n key: e.key,\n modifiers: modifiers\n }\n });\n }\n }, true);\n })();\n `);\n }\n}\n","/**\n * Browser Runner Implementation\n *\n * Replays browser sessions with security payloads.\n * Uses plugin hooks for detection.\n *\n * v2: Persistent browser with in-page payload cycling.\n * - ONE browser for the entire scan (not per-session)\n * - Uses page.goBack() between payloads instead of full page.goto()\n * - Falls back to full navigation when goBack() fails\n * - 5-10x faster on SPAs, same speed on simple sites\n */\n\nimport type {\n Browser,\n BrowserContext,\n Page,\n Dialog,\n ConsoleMessage,\n} from \"playwright\";\nimport type {\n Session,\n Step,\n RunContext,\n RunResult,\n Finding,\n RuntimePayload,\n PayloadCategory,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser, type BrowserType } from \"./browser\";\nimport type { BrowserStep } from \"./index\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ninterface PayloadItem {\n payloadSet: RuntimePayload;\n value: string;\n}\n\ninterface CurrentPayloadInfo {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n}\n\n// ── Browser Runner ─────────────────────────────────────────────────────\n\n/**\n * Browser Runner - replays sessions with payloads\n *\n * Supports two modes:\n * 1. Self-managed browser: launches its own browser (backward compat)\n * 2. Shared browser: receives a browser instance via RunOptions\n *\n * In both modes, payload cycling uses goBack() for speed.\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads.\n *\n * If ctx.options.browser is provided, reuses that browser (persistent mode).\n * Otherwise, launches and closes its own browser (standalone mode).\n */\n static async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n const config = session.driverConfig;\n const browserType = (config.browser as BrowserType) ?? \"chromium\";\n const viewport = (config.viewport as { width: number; height: number }) ?? {\n width: 1280,\n height: 720,\n };\n const startUrl = config.startUrl as string;\n const headless = ctx.options.headless ?? true;\n\n const startTime = Date.now();\n const errors: string[] = [];\n let payloadsTested = 0;\n\n const payloads = ctx.payloads;\n if (payloads.length === 0) {\n return {\n findings: [],\n stepsExecuted: session.steps.length,\n payloadsTested: 0,\n duration: Date.now() - startTime,\n errors: [\n \"No payloads loaded. Add a payload plugin or configure payloads.\",\n ],\n };\n }\n\n // ── Browser lifecycle ────────────────────────────────────────────\n // Use shared browser from options if available (persistent mode),\n // otherwise launch our own (standalone / backward compat).\n const sharedBrowser = ctx.options.browser as Browser | undefined;\n const ownBrowser = sharedBrowser\n ? null\n : (await launchBrowser({ browser: browserType, headless })).browser;\n const browser = sharedBrowser ?? ownBrowser!;\n\n // Inject storageState from options if available (for auth)\n const storageState = ctx.options.storageState as string | undefined;\n const contextOptions: Record<string, unknown> = { viewport };\n if (storageState) {\n contextOptions.storageState = JSON.parse(storageState);\n }\n\n const context = await browser.newContext(contextOptions);\n const page = await context.newPage();\n\n // Signal that the page is ready — plugins (e.g. passive scanner)\n // can now attach event listeners to the real page object\n await ctx.options.onPageReady?.(page);\n\n // ── Event tracking ───────────────────────────────────────────────\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: CurrentPayloadInfo | null = null;\n\n const dialogHandler = createDialogHandler(\n page,\n eventFindings,\n () => currentPayloadInfo,\n );\n const consoleHandler = createConsoleHandler(\n eventFindings,\n () => currentPayloadInfo,\n );\n\n page.on(\"dialog\", dialogHandler);\n page.on(\"console\", consoleHandler);\n\n try {\n // Find injectable steps (browser.input with injectable=true)\n const injectableSteps = session.steps.filter(\n (\n step,\n ): step is Step & { type: \"browser.input\"; injectable?: boolean } =>\n step.type === \"browser.input\" &&\n (step as BrowserStep & { type: \"browser.input\" }).injectable !==\n false,\n );\n\n // Build interleaved payload list (round-robin across categories)\n const allPayloads = interleavePayloads(payloads);\n\n // Track confirmed vulnerability types per-step (dedup)\n const confirmedTypes = new Set<string>();\n\n // ── Per-step payload cycling ───────────────────────────────────\n for (const injectableStep of injectableSteps) {\n let isFirstPayload = true;\n // Track the URL we navigate back to for goBack fallback\n let formPageUrl: string | null = null;\n\n for (const { payloadSet, value } of allPayloads) {\n // Skip if this vuln type is already confirmed for this step\n const stepTypeKey = `${injectableStep.id}::${payloadSet.category}`;\n if (confirmedTypes.has(stepTypeKey)) {\n continue;\n }\n\n try {\n currentPayloadInfo = {\n stepId: injectableStep.id,\n payloadSet,\n payloadValue: value,\n };\n\n if (isFirstPayload) {\n // First payload: full navigation + replay all steps\n await replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n isFirstPayload = false;\n // Capture the form page URL for fallback navigation\n formPageUrl = startUrl;\n } else {\n // Subsequent payloads: try goBack() first (fast path)\n const cycled = await cyclePayload(\n page,\n session,\n injectableStep,\n value,\n formPageUrl ?? startUrl,\n );\n if (!cycled) {\n // goBack failed — fall back to full replay\n await replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n }\n }\n\n // Check for reflection\n const reflectionFinding = await checkReflection(\n page,\n injectableStep,\n payloadSet,\n value,\n );\n\n // Collect all findings from this payload\n const allFindings = [...eventFindings];\n if (reflectionFinding) {\n allFindings.push(reflectionFinding);\n }\n\n // Deduplicate: only add findings we haven't already reported\n const seenKeys = new Set<string>();\n for (const finding of allFindings) {\n const dedupKey = `${finding.type}::${finding.stepId}::${finding.title}`;\n if (!seenKeys.has(dedupKey)) {\n seenKeys.add(dedupKey);\n ctx.addFinding(finding);\n }\n }\n\n // If we got any finding, mark as confirmed and skip remaining\n if (allFindings.length > 0) {\n confirmedTypes.add(stepTypeKey);\n }\n\n // Clear event findings for next iteration\n eventFindings.length = 0;\n payloadsTested++;\n\n // Report progress\n ctx.options.onStepComplete?.(injectableStep.id, payloadsTested);\n } catch (err) {\n errors.push(`${injectableStep.id}: ${String(err)}`);\n // If an error occurred, reset so next payload does full navigation\n isFirstPayload = true;\n }\n }\n }\n } finally {\n page.off(\"dialog\", dialogHandler);\n page.off(\"console\", consoleHandler);\n currentPayloadInfo = null;\n\n // Let plugins flush pending async work before browser closes\n await ctx.options.onBeforeClose?.(page);\n\n // Close context (lightweight — doesn't close the browser)\n await context.close();\n\n // Only close browser if we launched it ourselves\n if (ownBrowser) {\n await ownBrowser.close();\n }\n }\n\n return {\n findings: ctx.findings,\n stepsExecuted: session.steps.length,\n payloadsTested,\n duration: Date.now() - startTime,\n errors,\n };\n }\n}\n\n// ── Dialog / Console Handlers ──────────────────────────────────────────\n\nfunction createDialogHandler(\n page: Page,\n eventFindings: Finding[],\n getPayloadInfo: () => CurrentPayloadInfo | null,\n) {\n return async (dialog: Dialog) => {\n const info = getPayloadInfo();\n if (info) {\n const message = dialog.message();\n const dialogType = dialog.type();\n\n // Skip beforeunload dialogs (not XSS-related)\n if (dialogType !== \"beforeunload\") {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: `XSS Confirmed - ${dialogType}() triggered`,\n description: `JavaScript ${dialogType}() dialog was triggered by payload injection. Message: \"${message}\"`,\n stepId: info.stepId,\n payload: info.payloadValue,\n url: page.url(),\n evidence: `Dialog type: ${dialogType}, Message: ${message}`,\n metadata: {\n dialogType,\n dialogMessage: message,\n detectionMethod: \"dialog\",\n },\n });\n }\n }\n // Always dismiss dialogs to prevent blocking\n try {\n await dialog.dismiss();\n } catch {\n // Dialog may have already been handled\n }\n };\n}\n\nfunction createConsoleHandler(\n eventFindings: Finding[],\n getPayloadInfo: () => CurrentPayloadInfo | null,\n) {\n return async (msg: ConsoleMessage) => {\n const info = getPayloadInfo();\n if (info && msg.type() === \"log\") {\n const text = msg.text();\n if (text.includes(\"vulcn\") || text.includes(info.payloadValue)) {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: \"XSS Confirmed - Console Output\",\n description: `JavaScript console.log was triggered by payload injection`,\n stepId: info.stepId,\n payload: info.payloadValue,\n url: \"\",\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\n };\n}\n\n// ── Payload Cycling ────────────────────────────────────────────────────\n\n/**\n * Fast-path payload cycling using goBack().\n *\n * After a form submission causes navigation, goBack() returns to the form\n * page much faster than a full page.goto() — especially for SPAs where\n * the client-side router handles the back navigation without a full reload.\n *\n * Falls back to full navigation if goBack doesn't land on a page with\n * the expected form.\n *\n * Returns true if cycling succeeded, false if caller should fall back\n * to full replay.\n */\nasync function cyclePayload(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n payloadValue: string,\n formPageUrl: string,\n): Promise<boolean> {\n try {\n // Try goBack first — fast on SPAs, cheap on static sites\n await page.goBack({ waitUntil: \"domcontentloaded\", timeout: 5000 });\n\n // Verify the form element is still present\n const targetSelector = (\n targetStep as BrowserStep & { type: \"browser.input\" }\n ).selector;\n const formPresent = await page\n .waitForSelector(targetSelector, { timeout: 3000 })\n .then(() => true)\n .catch(() => false);\n\n if (!formPresent) {\n // goBack didn't land on the form page — try direct navigation\n await page.goto(formPageUrl, {\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n });\n\n // Wait for the form element after navigation\n const formPresentAfterNav = await page\n .waitForSelector(targetSelector, { timeout: 3000 })\n .then(() => true)\n .catch(() => false);\n\n if (!formPresentAfterNav) {\n // Even direct nav failed — fall back to full replay\n return false;\n }\n }\n\n // Fill the injectable step with the new payload\n await page.fill(targetSelector, payloadValue, { timeout: 5000 });\n\n // Execute remaining steps after the injectable step (click submit, etc.)\n await replayStepsAfter(page, session, targetStep);\n\n // Wait for scripts to execute\n await page.waitForTimeout(500);\n\n return true;\n } catch {\n return false;\n }\n}\n\n// ── Full Replay ────────────────────────────────────────────────────────\n\n/**\n * Replay session steps with payload injected at target step.\n *\n * IMPORTANT: We replay ALL steps, not just up to the injectable step.\n * The injection replaces the input value, but subsequent steps (like\n * clicking submit) must still execute so the payload reaches the server\n * and gets reflected back in the response.\n */\nasync function replayWithPayload(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n payloadValue: string,\n startUrl: string,\n): Promise<void> {\n // Navigate to start\n await page.goto(startUrl, { waitUntil: \"domcontentloaded\" });\n\n let injected = false;\n\n for (const step of session.steps) {\n const browserStep = step as BrowserStep;\n\n try {\n switch (browserStep.type) {\n case \"browser.navigate\":\n if (injected && browserStep.url.includes(\"sid=\")) {\n continue;\n }\n await page.goto(browserStep.url, { waitUntil: \"domcontentloaded\" });\n break;\n\n case \"browser.click\":\n if (injected) {\n await Promise.all([\n page\n .waitForNavigation({\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n })\n .catch(() => {}),\n page.click(browserStep.selector, { timeout: 5000 }),\n ]);\n } else {\n await page.click(browserStep.selector, { timeout: 5000 });\n }\n break;\n\n case \"browser.input\": {\n const value =\n step.id === targetStep.id ? payloadValue : browserStep.value;\n await page.fill(browserStep.selector, value, { timeout: 5000 });\n if (step.id === targetStep.id) {\n injected = true;\n }\n break;\n }\n\n case \"browser.keypress\": {\n const modifiers = browserStep.modifiers ?? [];\n for (const mod of modifiers) {\n await page.keyboard.down(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n await page.keyboard.press(browserStep.key);\n for (const mod of modifiers.reverse()) {\n await page.keyboard.up(mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\");\n }\n break;\n }\n\n case \"browser.scroll\":\n if (browserStep.selector) {\n await page.locator(browserStep.selector).evaluate((el, pos) => {\n el.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n } else {\n await page.evaluate((pos) => {\n window.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n }\n break;\n\n case \"browser.wait\":\n await page.waitForTimeout(browserStep.duration);\n break;\n }\n } catch {\n // Step failed, continue to next\n }\n }\n\n // Wait for any scripts to execute after all steps complete\n await page.waitForTimeout(500);\n}\n\n/**\n * Replay only the steps AFTER the injectable step.\n *\n * Used by cyclePayload() — the injectable step has already been filled,\n * so we just need to execute the remaining steps (click submit, etc.)\n */\nasync function replayStepsAfter(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n): Promise<void> {\n let pastTarget = false;\n\n for (const step of session.steps) {\n if (step.id === targetStep.id) {\n pastTarget = true;\n continue; // skip the injectable step itself (already filled)\n }\n\n if (!pastTarget) continue;\n\n const browserStep = step as BrowserStep;\n\n try {\n switch (browserStep.type) {\n case \"browser.navigate\":\n // Skip post-submission navigates\n break;\n\n case \"browser.click\":\n await Promise.all([\n page\n .waitForNavigation({\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n })\n .catch(() => {}),\n page.click(browserStep.selector, { timeout: 5000 }),\n ]);\n break;\n\n case \"browser.input\":\n await page.fill(browserStep.selector, browserStep.value, {\n timeout: 5000,\n });\n break;\n\n case \"browser.keypress\": {\n const modifiers = browserStep.modifiers ?? [];\n for (const mod of modifiers) {\n await page.keyboard.down(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n await page.keyboard.press(browserStep.key);\n for (const mod of modifiers.reverse()) {\n await page.keyboard.up(mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\");\n }\n break;\n }\n\n case \"browser.scroll\":\n break; // skip scrolls in fast path\n\n case \"browser.wait\":\n break; // skip waits in fast path\n }\n } catch {\n // Step failed, continue\n }\n }\n\n // Wait for scripts to execute\n await page.waitForTimeout(500);\n}\n\n// ── Detection Helpers ──────────────────────────────────────────────────\n\n/**\n * Check for payload reflection in page content\n */\nasync function checkReflection(\n page: Page,\n step: Step & { type: \"browser.input\" },\n payloadSet: RuntimePayload,\n payloadValue: string,\n): Promise<Finding | undefined> {\n const content = await page.content();\n\n // Check for reflection patterns\n for (const pattern of payloadSet.detectPatterns) {\n if (pattern.test(content)) {\n return {\n type: payloadSet.category,\n severity: getSeverity(payloadSet.category),\n title: `${payloadSet.category.toUpperCase()} vulnerability detected`,\n description: `Payload pattern was reflected in page content`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n evidence: content.match(pattern)?.[0]?.slice(0, 200),\n };\n }\n }\n\n // Check if payload appears verbatim\n if (content.includes(payloadValue)) {\n return {\n type: payloadSet.category,\n severity: \"medium\",\n title: `Potential ${payloadSet.category.toUpperCase()} - payload reflection`,\n description: `Payload was reflected in page without encoding`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n };\n }\n\n return undefined;\n}\n\n/**\n * Determine severity based on vulnerability category\n */\nfunction 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 default:\n return \"medium\";\n }\n}\n\n// ── Payload Interleaving ───────────────────────────────────────────────\n\n/**\n * Build flat list of payloads, interleaved by category (round-robin).\n *\n * This ensures we test at least one payload from each category quickly,\n * so the dedup early-break fires sooner on slow SPAs.\n *\n * Before: [sqli1, sqli2, ..., sqli50, xss1, xss2, ..., xss50]\n * After: [sqli1, xss1, sqli2, xss2, ..., sqli50, xss50]\n */\nfunction interleavePayloads(payloads: RuntimePayload[]): PayloadItem[] {\n const result: PayloadItem[] = [];\n const payloadsByCategory = payloads.map((ps) =>\n ps.payloads.map((value) => ({ payloadSet: ps, value })),\n );\n const maxLen = Math.max(...payloadsByCategory.map((c) => c.length));\n for (let i = 0; i < maxLen; i++) {\n for (const category of payloadsByCategory) {\n if (i < category.length) {\n result.push(category[i]);\n }\n }\n }\n return result;\n}\n","/**\n * HTTP Scanner — Tier 1 Fast Scan\n *\n * Replays captured HTTP requests via fetch() instead of Playwright.\n * Substitutes security payloads into injectable form fields and checks\n * the response body for payload reflection or error patterns.\n *\n * Speed: ~50ms per payload (vs 2-5s for browser replay)\n *\n * Catches:\n * - Reflected XSS (payload appears in response body)\n * - Error-based SQLi (SQL error patterns in response)\n * - Server-side reflection of any payload\n *\n * Misses:\n * - DOM-based XSS (requires JS execution)\n * - Client-side state bugs\n * - CSP-blocked attacks\n *\n * When reflection IS found, the finding is marked as `needsBrowserConfirmation`\n * so the caller can escalate to Tier 2 for execution-based proof.\n */\n\nimport type {\n Finding,\n RuntimePayload,\n PayloadCategory,\n CapturedRequest,\n} from \"@vulcn/engine\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface HttpScanResult {\n /** Total HTTP requests sent */\n requestsSent: number;\n /** How long the scan took (ms) */\n duration: number;\n /** Findings from reflection detection */\n findings: Finding[];\n /** Requests where reflection was found — should be escalated to Tier 2 */\n reflectedRequests: ReflectedRequest[];\n}\n\nexport interface ReflectedRequest {\n /** Original captured request */\n request: CapturedRequest;\n /** Payload that caused reflection */\n payload: string;\n /** Category of the payload */\n category: PayloadCategory;\n}\n\nexport interface HttpScanOptions {\n /** Request timeout in ms (default: 10000) */\n timeout?: number;\n /** Max concurrent requests (default: 10) */\n concurrency?: number;\n /** Cookie header to send with requests (for auth) */\n cookies?: string;\n /** Extra headers to send */\n headers?: Record<string, string>;\n /** Callback for progress reporting */\n onProgress?: (completed: number, total: number) => void;\n}\n\n// ── Scanner ────────────────────────────────────────────────────────────\n\n/**\n * Run Tier 1 HTTP-level scan on captured requests.\n *\n * For each CapturedRequest × each payload:\n * 1. Substitute the payload into the injectable field\n * 2. Send via fetch()\n * 3. Check response body for reflection patterns\n * 4. If reflected, add finding + mark for Tier 2 escalation\n */\nexport async function httpScan(\n requests: CapturedRequest[],\n payloads: RuntimePayload[],\n options: HttpScanOptions = {},\n): Promise<HttpScanResult> {\n const timeout = options.timeout ?? 10_000;\n const concurrency = options.concurrency ?? 10;\n const start = Date.now();\n\n const findings: Finding[] = [];\n const reflectedRequests: ReflectedRequest[] = [];\n let requestsSent = 0;\n\n // Build the flat list of (request, payload, value) tuples\n const tasks: Array<{\n request: CapturedRequest;\n payloadSet: RuntimePayload;\n value: string;\n }> = [];\n\n for (const request of requests) {\n if (!request.injectableField) continue;\n\n for (const payloadSet of payloads) {\n for (const value of payloadSet.payloads) {\n tasks.push({ request, payloadSet, value });\n }\n }\n }\n\n const totalTasks = tasks.length;\n if (totalTasks === 0) {\n return { requestsSent: 0, duration: 0, findings, reflectedRequests };\n }\n\n // Process in batches for concurrency control\n for (let i = 0; i < tasks.length; i += concurrency) {\n const batch = tasks.slice(i, i + concurrency);\n\n const results = await Promise.allSettled(\n batch.map(async ({ request, payloadSet, value }) => {\n try {\n const body = await sendPayload(request, value, {\n timeout,\n cookies: options.cookies,\n headers: options.headers,\n });\n requestsSent++;\n\n const finding = checkHttpReflection(body, request, payloadSet, value);\n if (finding) {\n findings.push(finding);\n reflectedRequests.push({\n request,\n payload: value,\n category: payloadSet.category,\n });\n }\n } catch {\n // Network errors, timeouts — skip silently\n requestsSent++;\n }\n }),\n );\n\n // Report progress\n const completed = Math.min(i + batch.length, totalTasks);\n options.onProgress?.(completed, totalTasks);\n\n // Check for unhandled rejections (shouldn't happen with allSettled)\n for (const result of results) {\n if (result.status === \"rejected\") {\n // Already handled in catch above\n }\n }\n }\n\n return {\n requestsSent,\n duration: Date.now() - start,\n findings,\n reflectedRequests,\n };\n}\n\n// ── HTTP Request ───────────────────────────────────────────────────────\n\n/**\n * Send a single HTTP request with a payload substituted in.\n * Returns the response body as a string.\n */\nasync function sendPayload(\n request: CapturedRequest,\n payload: string,\n options: {\n timeout: number;\n cookies?: string;\n headers?: Record<string, string>;\n },\n): Promise<string> {\n const { method, url, headers, body, contentType, injectableField } = request;\n\n // Build request headers\n const reqHeaders: Record<string, string> = {\n ...headers,\n ...(options.headers ?? {}),\n };\n\n // Add cookies if provided\n if (options.cookies) {\n reqHeaders[\"Cookie\"] = options.cookies;\n }\n\n // Remove headers that could interfere\n delete reqHeaders[\"content-length\"];\n delete reqHeaders[\"Content-Length\"];\n\n let requestUrl = url;\n let requestBody: string | undefined;\n\n if (method.toUpperCase() === \"GET\") {\n // For GET, inject into URL query parameters\n requestUrl = injectIntoUrl(url, injectableField!, payload);\n } else {\n // For POST/PUT, inject into the request body\n requestBody = injectIntoBody(body, contentType, injectableField!, payload);\n\n // Set content type if we have a body\n if (contentType) {\n reqHeaders[\"Content-Type\"] = contentType;\n } else {\n reqHeaders[\"Content-Type\"] = \"application/x-www-form-urlencoded\";\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), options.timeout);\n\n try {\n const response = await fetch(requestUrl, {\n method: method.toUpperCase(),\n headers: reqHeaders,\n body: requestBody,\n signal: controller.signal,\n redirect: \"follow\",\n });\n\n return await response.text();\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ── Injection Helpers ──────────────────────────────────────────────────\n\n/**\n * Inject payload into a URL query parameter.\n */\nfunction injectIntoUrl(url: string, field: string, payload: string): string {\n try {\n const parsed = new URL(url);\n parsed.searchParams.set(field, payload);\n return parsed.toString();\n } catch {\n // If URL parsing fails, append as query string\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}${encodeURIComponent(field)}=${encodeURIComponent(payload)}`;\n }\n}\n\n/**\n * Inject payload into a request body.\n *\n * Supports:\n * - application/x-www-form-urlencoded\n * - application/json\n * - multipart/form-data (basic — replaces field value by regex)\n */\nfunction injectIntoBody(\n body: string | undefined,\n contentType: string | undefined,\n field: string,\n payload: string,\n): string {\n if (!body) {\n // No existing body — create a simple form body\n return `${encodeURIComponent(field)}=${encodeURIComponent(payload)}`;\n }\n\n const ct = (contentType ?? \"\").toLowerCase();\n\n if (ct.includes(\"application/json\")) {\n return injectIntoJson(body, field, payload);\n }\n\n if (ct.includes(\"multipart/form-data\")) {\n return injectIntoMultipart(body, field, payload);\n }\n\n // Default: form-urlencoded\n return injectIntoFormUrlEncoded(body, field, payload);\n}\n\n/**\n * Inject into application/x-www-form-urlencoded body.\n */\nfunction injectIntoFormUrlEncoded(\n body: string,\n field: string,\n payload: string,\n): string {\n const params = new URLSearchParams(body);\n params.set(field, payload);\n return params.toString();\n}\n\n/**\n * Inject into application/json body.\n */\nfunction injectIntoJson(body: string, field: string, payload: string): string {\n try {\n const parsed = JSON.parse(body);\n if (typeof parsed === \"object\" && parsed !== null) {\n parsed[field] = payload;\n return JSON.stringify(parsed);\n }\n } catch {\n // Fall through to regex-based injection\n }\n\n // Fallback: regex replacement for the field value\n const escaped = field.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regex = new RegExp(`(\"${escaped}\"\\\\s*:\\\\s*)\"[^\"]*\"`, \"g\");\n const replaced = body.replace(regex, `$1\"${payload}\"`);\n if (replaced !== body) return replaced;\n\n // Last resort: just return the body with the field appended\n return body;\n}\n\n/**\n * Inject into multipart/form-data body.\n * Basic regex-based replacement — works for simple text fields.\n */\nfunction injectIntoMultipart(\n body: string,\n field: string,\n payload: string,\n): string {\n // Match: Content-Disposition: form-data; name=\"fieldname\"\\r\\n\\r\\nvalue\n const escaped = field.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regex = new RegExp(\n `(Content-Disposition:\\\\s*form-data;\\\\s*name=\"${escaped}\"\\\\r?\\\\n\\\\r?\\\\n)[^\\\\r\\\\n-]*`,\n \"i\",\n );\n return body.replace(regex, `$1${payload}`);\n}\n\n// ── Reflection Detection ───────────────────────────────────────────────\n\n/**\n * Check HTTP response body for payload reflection.\n *\n * This mirrors the browser runner's `checkReflection` but works on raw\n * HTTP response text. Results are marked with `detectionMethod: \"tier1-http\"`\n * in metadata to distinguish from browser-confirmed findings.\n */\nfunction checkHttpReflection(\n responseBody: string,\n request: CapturedRequest,\n payloadSet: RuntimePayload,\n payloadValue: string,\n): Finding | undefined {\n // Check detect patterns from the payload set\n for (const pattern of payloadSet.detectPatterns) {\n if (pattern.test(responseBody)) {\n return {\n type: payloadSet.category,\n severity: getSeverity(payloadSet.category),\n title: `${payloadSet.category.toUpperCase()} reflection detected (HTTP)`,\n description: `Payload pattern was reflected in HTTP response body. Needs browser confirmation for execution proof.`,\n stepId: `http-${request.sessionName}`,\n payload: payloadValue,\n url: request.url,\n evidence: responseBody.match(pattern)?.[0]?.slice(0, 200),\n metadata: {\n detectionMethod: \"tier1-http\",\n needsBrowserConfirmation: true,\n requestMethod: request.method,\n injectableField: request.injectableField,\n },\n };\n }\n }\n\n // Check if payload appears verbatim in response\n if (responseBody.includes(payloadValue)) {\n return {\n type: payloadSet.category,\n severity: \"medium\",\n title: `Potential ${payloadSet.category.toUpperCase()} — payload reflected in HTTP response`,\n description: `Payload was reflected in HTTP response without encoding. Escalate to browser for execution proof.`,\n stepId: `http-${request.sessionName}`,\n payload: payloadValue,\n url: request.url,\n metadata: {\n detectionMethod: \"tier1-http\",\n needsBrowserConfirmation: true,\n requestMethod: request.method,\n injectableField: request.injectableField,\n },\n };\n }\n\n return undefined;\n}\n\n// ── Severity ───────────────────────────────────────────────────────────\n\n/**\n * Determine severity based on vulnerability category.\n * Mirrors the browser runner's getSeverity().\n */\nfunction 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 default:\n return \"medium\";\n }\n}\n\n// ── Utility: Build CapturedRequests from Crawler Data ──────────────────\n\n/**\n * Convert discovered forms into CapturedRequest metadata.\n *\n * Called by the crawler after form discovery. Each injectable form\n * produces one CapturedRequest per injectable input field.\n */\nexport function buildCapturedRequests(\n forms: Array<{\n pageUrl: string;\n action: string;\n method: string;\n inputs: Array<{ name: string; injectable: boolean; type: string }>;\n sessionName: string;\n }>,\n): CapturedRequest[] {\n const requests: CapturedRequest[] = [];\n\n for (const form of forms) {\n const injectableInputs = form.inputs.filter((i) => i.injectable);\n if (injectableInputs.length === 0) continue;\n\n // Resolve the form action URL\n let actionUrl: string;\n try {\n actionUrl = new URL(form.action, form.pageUrl).toString();\n } catch {\n actionUrl = form.pageUrl;\n }\n\n const method = (form.method || \"GET\").toUpperCase();\n\n for (const input of injectableInputs) {\n // Build the default form body with placeholder values\n const formParams = new URLSearchParams();\n for (const inp of form.inputs) {\n formParams.set(inp.name || inp.type, inp.injectable ? \"test\" : \"\");\n }\n\n const request: CapturedRequest = {\n method,\n url: method === \"GET\" ? actionUrl : actionUrl,\n headers: {\n \"User-Agent\": \"Vulcn/1.0 (Security Scanner)\",\n Accept: \"text/html,application/xhtml+xml,*/*\",\n },\n ...(method !== \"GET\"\n ? {\n body: formParams.toString(),\n contentType: \"application/x-www-form-urlencoded\",\n }\n : {}),\n injectableField: input.name || input.type,\n sessionName: form.sessionName,\n };\n\n requests.push(request);\n }\n }\n\n return requests;\n}\n","/**\n * Browser Crawler\n *\n * Automated session generator for the browser driver.\n * Crawls a web application using Playwright to discover:\n * - Forms with input fields and submit buttons\n * - Links to follow for deeper crawling\n *\n * Outputs Session[] that are directly compatible with BrowserRunner.\n * Also generates CapturedRequest[] metadata for Tier 1 HTTP fast scanning.\n *\n * This is the \"auto-record\" mode — instead of a human clicking around,\n * the crawler automatically discovers injection points.\n */\n\nimport type { Page, Browser, BrowserContext } from \"playwright\";\nimport type {\n Session,\n Step,\n CrawlOptions,\n CapturedRequest,\n} from \"@vulcn/engine\";\nimport { launchBrowser, type BrowserType } from \"./browser\";\nimport { buildCapturedRequests } from \"./http-scanner\";\n\n// ── Internal Types ─────────────────────────────────────────────────────\n\ninterface DiscoveredInput {\n selector: string;\n type: string;\n name: string;\n injectable: boolean;\n placeholder?: string;\n}\n\ninterface DiscoveredForm {\n pageUrl: string;\n formSelector: string;\n action: string;\n method: string;\n inputs: DiscoveredInput[];\n submitSelector: string | null;\n}\n\n// Input types that accept text and can be injected with payloads\nconst INJECTABLE_INPUT_TYPES = new Set([\n \"text\",\n \"search\",\n \"url\",\n \"email\",\n \"tel\",\n \"password\",\n \"textarea\",\n \"\",\n]);\n\nconst CRAWL_DEFAULTS = {\n maxDepth: 2,\n maxPages: 20,\n pageTimeout: 10000,\n sameOrigin: true,\n};\n\n// ── Public API ─────────────────────────────────────────────────────────\n\nexport interface BrowserCrawlConfig {\n /** Starting URL */\n startUrl: string;\n /** Browser type */\n browser?: BrowserType;\n /** Run headless */\n headless?: boolean;\n /** Viewport */\n viewport?: { width: number; height: number };\n}\n\n/** Result from crawling — sessions for browser replay + requests for HTTP scanning */\nexport interface CrawlResult {\n sessions: Session[];\n capturedRequests: CapturedRequest[];\n}\n\n/**\n * Crawl a URL and generate sessions.\n *\n * This is called by the browser driver's recorder.crawl() method.\n * Returns both Session[] for Tier 2 browser replay and\n * CapturedRequest[] for Tier 1 HTTP fast scanning.\n */\nexport async function crawlAndBuildSessions(\n config: BrowserCrawlConfig,\n options: CrawlOptions = {},\n): Promise<CrawlResult> {\n const opts = { ...CRAWL_DEFAULTS, ...options } as typeof CRAWL_DEFAULTS &\n CrawlOptions;\n const startUrl = config.startUrl;\n\n let normalizedUrl: URL;\n try {\n normalizedUrl = new URL(startUrl);\n } catch {\n throw new Error(`Invalid URL: ${startUrl}`);\n }\n\n const origin = normalizedUrl.origin;\n const visited = new Set<string>();\n const allForms: DiscoveredForm[] = [];\n\n // BFS queue: [url, depth]\n const queue: [string, number][] = [[normalizedUrl.href, 0]];\n\n // Use the shared browser launcher\n const { browser } = await launchBrowser({\n browser: config.browser ?? \"chromium\",\n headless: config.headless ?? true,\n });\n\n const context: BrowserContext = await browser.newContext({\n viewport: config.viewport ?? { width: 1280, height: 720 },\n ...(options.storageState\n ? { storageState: JSON.parse(options.storageState) }\n : {}),\n });\n\n try {\n while (queue.length > 0 && visited.size < opts.maxPages) {\n const [url, depth] = queue.shift()!;\n const normalizedPageUrl = normalizeUrl(url);\n\n if (visited.has(normalizedPageUrl)) continue;\n visited.add(normalizedPageUrl);\n\n console.log(`[crawler] [depth=${depth}] Crawling: ${normalizedPageUrl}`);\n\n const page: Page = await context.newPage();\n\n try {\n await page.goto(normalizedPageUrl, {\n waitUntil: \"domcontentloaded\",\n timeout: opts.pageTimeout,\n });\n\n // Wait for JS-rendered content\n await page.waitForTimeout(1000);\n\n // Discover forms\n const forms = await discoverForms(page, normalizedPageUrl);\n allForms.push(...forms);\n\n const injectableCount = forms.reduce(\n (s, f) => s + f.inputs.filter((i) => i.injectable).length,\n 0,\n );\n console.log(\n `[crawler] Found ${forms.length} form(s), ${injectableCount} injectable input(s)`,\n );\n\n opts.onPageCrawled?.(normalizedPageUrl, forms.length);\n\n // Follow links\n if (depth < opts.maxDepth) {\n const links = await discoverLinks(page, origin, opts.sameOrigin);\n for (const link of links) {\n const normalizedLink = normalizeUrl(link);\n if (!visited.has(normalizedLink)) {\n queue.push([normalizedLink, depth + 1]);\n }\n }\n console.log(`[crawler] Found ${links.length} link(s) to follow`);\n }\n } catch (err) {\n console.warn(\n `[crawler] Failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n await page.close();\n }\n }\n } finally {\n await browser.close();\n }\n\n console.log(\n `[crawler] Complete: ${visited.size} page(s), ${allForms.length} form(s)`,\n );\n\n // Convert discovered forms to sessions\n const sessions = buildSessions(allForms);\n\n // Build HTTP request metadata for Tier 1 fast scanning\n const capturedRequests = buildCapturedRequests(\n allForms\n .filter((f) => f.inputs.some((i) => i.injectable))\n .map((form, idx) => ({\n pageUrl: form.pageUrl,\n action: form.action,\n method: form.method,\n inputs: form.inputs,\n sessionName: sessions[idx]?.name ?? `form-${idx + 1}`,\n })),\n );\n\n console.log(\n `[crawler] Generated ${sessions.length} session(s), ${capturedRequests.length} HTTP request(s) for Tier 1`,\n );\n\n return { sessions, capturedRequests };\n}\n\n// ── Form Discovery ─────────────────────────────────────────────────────\n\nasync function discoverForms(\n page: Page,\n pageUrl: string,\n): Promise<DiscoveredForm[]> {\n const forms: DiscoveredForm[] = [];\n\n // 1. Explicit <form> elements\n const explicitForms = await page.evaluate(() => {\n const results: Array<{\n formIndex: number;\n action: string;\n method: string;\n inputs: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n }>;\n submitSelector: string | null;\n }> = [];\n\n const formElements = document.querySelectorAll(\"form\");\n\n formElements.forEach((form, formIndex) => {\n const inputs: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n }> = [];\n\n const inputEls = form.querySelectorAll(\n 'input, textarea, [contenteditable=\"true\"]',\n );\n\n inputEls.forEach((input, inputIndex) => {\n const el = input as HTMLInputElement;\n const type =\n el.tagName.toLowerCase() === \"textarea\"\n ? \"textarea\"\n : el.getAttribute(\"type\") || \"text\";\n const name = el.name || el.id || `input-${inputIndex}`;\n\n let selector = \"\";\n if (el.id) {\n selector = `#${CSS.escape(el.id)}`;\n } else if (el.name) {\n selector = `form:nth-of-type(${formIndex + 1}) [name=\"${CSS.escape(el.name)}\"]`;\n } else {\n selector = `form:nth-of-type(${formIndex + 1}) ${el.tagName.toLowerCase()}:nth-of-type(${inputIndex + 1})`;\n }\n\n inputs.push({\n selector,\n type,\n name,\n placeholder: el.placeholder || \"\",\n });\n });\n\n // Find submit trigger\n let submitSelector: string | null = null;\n const submitBtn =\n form.querySelector('button[type=\"submit\"], input[type=\"submit\"]') ||\n form.querySelector(\"button:not([type])\") ||\n form.querySelector('button, input[type=\"button\"]');\n\n if (submitBtn) {\n const btn = submitBtn as HTMLElement;\n if (btn.id) {\n submitSelector = `#${CSS.escape(btn.id)}`;\n } else {\n const tag = btn.tagName.toLowerCase();\n const type = btn.getAttribute(\"type\");\n if (type) {\n submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}[type=\"${type}\"]`;\n } else {\n submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}`;\n }\n }\n }\n\n results.push({\n formIndex,\n action: form.action || \"\",\n method: (form.method || \"GET\").toUpperCase(),\n inputs,\n submitSelector,\n });\n });\n\n return results;\n });\n\n for (const form of explicitForms) {\n if (form.inputs.length === 0) continue;\n\n forms.push({\n pageUrl,\n formSelector: `form:nth-of-type(${form.formIndex + 1})`,\n action: form.action,\n method: form.method,\n inputs: form.inputs.map((input) => ({\n selector: input.selector,\n type: input.type,\n name: input.name,\n injectable: INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase()),\n placeholder: input.placeholder || undefined,\n })),\n submitSelector: form.submitSelector,\n });\n }\n\n // 2. Standalone inputs NOT inside a <form>\n const standaloneInputs = await page.evaluate(() => {\n const results: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n nearbyButtonSelector: string | null;\n }> = [];\n\n const allInputs = document.querySelectorAll(\n 'input:not(form input), textarea:not(form textarea), [contenteditable=\"true\"]:not(form [contenteditable])',\n );\n\n allInputs.forEach((input) => {\n const el = input as HTMLInputElement;\n const type =\n el.tagName.toLowerCase() === \"textarea\"\n ? \"textarea\"\n : el.getAttribute(\"type\") || \"text\";\n const name = el.name || el.id || \"\";\n\n let selector = \"\";\n if (el.id) {\n selector = `#${CSS.escape(el.id)}`;\n } else if (el.name) {\n selector = `[name=\"${CSS.escape(el.name)}\"]`;\n } else {\n selector = `${el.tagName.toLowerCase()}[type=\"${type}\"]`;\n }\n\n // Look for nearby button\n let nearbyButtonSelector: string | null = null;\n const parent = el.parentElement;\n if (parent) {\n const btn =\n parent.querySelector(\"button\") ||\n parent.querySelector('input[type=\"submit\"]') ||\n parent.querySelector('input[type=\"button\"]');\n if (btn) {\n const btnEl = btn as HTMLElement;\n if (btnEl.id) {\n nearbyButtonSelector = `#${CSS.escape(btnEl.id)}`;\n }\n }\n }\n\n results.push({\n selector,\n type,\n name,\n placeholder: el.placeholder || \"\",\n nearbyButtonSelector,\n });\n });\n\n return results;\n });\n\n for (const input of standaloneInputs) {\n if (!INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase())) continue;\n\n forms.push({\n pageUrl,\n formSelector: \"(standalone)\",\n action: pageUrl,\n method: \"GET\",\n inputs: [\n {\n selector: input.selector,\n type: input.type,\n name: input.name,\n injectable: true,\n placeholder: input.placeholder || undefined,\n },\n ],\n submitSelector: input.nearbyButtonSelector,\n });\n }\n\n return forms;\n}\n\n// ── Link Discovery ─────────────────────────────────────────────────────\n\n/** Query parameter names commonly used for external redirects */\nconst REDIRECT_PARAMS = new Set([\n \"to\",\n \"url\",\n \"redirect\",\n \"redirect_uri\",\n \"redirect_url\",\n \"return\",\n \"return_url\",\n \"returnto\",\n \"next\",\n \"goto\",\n \"dest\",\n \"destination\",\n \"continue\",\n \"target\",\n \"rurl\",\n \"out\",\n \"link\",\n \"forward\",\n]);\n\n/**\n * Check if a link is a same-origin redirect that points to an external URL.\n * Example: /redirect?to=https://github.com/...\n */\nfunction isExternalRedirectLink(link: string, origin: string): boolean {\n try {\n const parsed = new URL(link);\n // Only check links on our origin\n if (parsed.origin !== origin) return false;\n\n for (const [key, value] of parsed.searchParams) {\n if (REDIRECT_PARAMS.has(key.toLowerCase())) {\n // If the param value looks like an external URL, skip this link\n try {\n const targetUrl = new URL(value);\n if (targetUrl.origin !== origin) return true;\n } catch {\n // Not a URL — that's fine\n }\n }\n }\n return false;\n } catch {\n return false;\n }\n}\n\nasync function discoverLinks(\n page: Page,\n origin: string,\n sameOrigin: boolean,\n): Promise<string[]> {\n const links = await page.evaluate(() => {\n return Array.from(document.querySelectorAll(\"a[href]\"))\n .map((a) => (a as HTMLAnchorElement).href)\n .filter((href) => href.startsWith(\"http\"));\n });\n\n return links.filter((link) => {\n try {\n const linkOrigin = new URL(link).origin;\n // Filter out links to different origins\n if (sameOrigin && linkOrigin !== origin) return false;\n // Filter out redirect links that point to external URLs\n if (isExternalRedirectLink(link, origin)) return false;\n return true;\n } catch {\n return false;\n }\n });\n}\n\n// ── Session Builder ────────────────────────────────────────────────────\n\n/**\n * Convert discovered forms into Vulcn sessions.\n *\n * Each form with injectable inputs becomes one session:\n * navigate → fill input(s) → click submit / press Enter\n */\nfunction buildSessions(forms: DiscoveredForm[]): Session[] {\n const targetForms = forms.filter((f) => f.inputs.some((i) => i.injectable));\n\n return targetForms.map((form, idx) => buildSessionForForm(form, idx));\n}\n\nfunction buildSessionForForm(form: DiscoveredForm, index: number): Session {\n const steps: Step[] = [];\n let stepNum = 1;\n\n // Step 1: Navigate\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.navigate\",\n url: form.pageUrl,\n timestamp: Date.now(),\n } as Step);\n\n // Steps 2+: Fill each injectable input\n const injectableInputs = form.inputs.filter((i) => i.injectable);\n\n for (const input of injectableInputs) {\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.input\",\n selector: input.selector,\n value: \"test\",\n injectable: true,\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n }\n\n // Final step: Submit\n if (form.submitSelector) {\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.click\",\n selector: form.submitSelector,\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n } else {\n // No submit button — press Enter\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.keypress\",\n key: \"Enter\",\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n }\n\n const inputNames = injectableInputs.map((i) => i.name || i.type).join(\", \");\n const pagePath = (() => {\n try {\n return new URL(form.pageUrl).pathname;\n } catch {\n return form.pageUrl;\n }\n })();\n\n return {\n name: `Crawl: ${pagePath} — form ${index + 1} (${inputNames})`,\n driver: \"browser\",\n driverConfig: {\n startUrl: form.pageUrl,\n browser: \"chromium\",\n headless: true,\n viewport: { width: 1280, height: 720 },\n },\n steps,\n metadata: {\n recordedAt: new Date().toISOString(),\n version: \"0.3.0\",\n source: \"crawler\",\n formAction: form.action,\n formMethod: form.method,\n },\n };\n}\n\n// ── Utilities ──────────────────────────────────────────────────────────\n\nfunction normalizeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.hash = \"\";\n if (parsed.pathname !== \"/\" && parsed.pathname.endsWith(\"/\")) {\n parsed.pathname = parsed.pathname.slice(0, -1);\n }\n return parsed.href;\n } catch {\n return url;\n }\n}\n","/**\n * Login Form Auto-Detection & Auth Replay\n *\n * Detects login forms on a page and fills them with credentials.\n * After login, captures the browser storage state (cookies + localStorage)\n * for re-use in subsequent scans.\n *\n * Detection strategy:\n * 1. Find forms with a password input (strongest signal)\n * 2. Find username field via heuristics (name, id, autocomplete, type)\n * 3. Find submit button\n * 4. Fall back to custom selectors from credentials\n */\n\nimport type { Page, BrowserContext } from \"playwright\";\nimport type { FormCredentials } from \"@vulcn/engine\";\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface LoginForm {\n /** Username input selector */\n usernameSelector: string;\n /** Password input selector */\n passwordSelector: string;\n /** Submit button selector (may be null if not found) */\n submitSelector: string | null;\n /** Whether the form was detected automatically */\n autoDetected: boolean;\n}\n\nexport interface LoginResult {\n /** Whether login succeeded */\n success: boolean;\n /** Message for logging */\n message: string;\n /** Playwright storage state JSON (cookies + localStorage) */\n storageState?: string;\n}\n\n// ── Detection ──────────────────────────────────────────────────────────\n\n/**\n * Auto-detect a login form on the current page.\n *\n * Strategy:\n * 1. Find `<form>` elements containing an `input[type=\"password\"]`\n * 2. Within that form, find the username field using heuristics\n * 3. Find the submit button\n *\n * Falls back to page-wide search if no enclosing <form> is found.\n */\nexport async function detectLoginForm(page: Page): Promise<LoginForm | null> {\n return page.evaluate(() => {\n // Helper: find the best username input in a container\n function findUsernameInput(container: Element): HTMLInputElement | null {\n // Priority-ordered selectors for username fields\n const selectors = [\n 'input[autocomplete=\"username\"]',\n 'input[autocomplete=\"email\"]',\n 'input[type=\"email\"]',\n 'input[name*=\"user\" i]',\n 'input[name*=\"login\" i]',\n 'input[name*=\"email\" i]',\n 'input[id*=\"user\" i]',\n 'input[id*=\"login\" i]',\n 'input[id*=\"email\" i]',\n 'input[name*=\"name\" i]',\n 'input[type=\"text\"]',\n ];\n\n for (const sel of selectors) {\n const el = container.querySelector(sel) as HTMLInputElement | null;\n if (el && el.type !== \"password\" && el.type !== \"hidden\") {\n return el;\n }\n }\n\n return null;\n }\n\n // Helper: find submit button in a container\n function findSubmitButton(container: Element): HTMLElement | null {\n const selectors = [\n 'button[type=\"submit\"]',\n 'input[type=\"submit\"]',\n \"button:not([type])\",\n 'button[type=\"button\"]',\n ];\n\n for (const sel of selectors) {\n const el = container.querySelector(sel) as HTMLElement | null;\n if (el) return el;\n }\n\n return null;\n }\n\n // Helper: get a unique CSS selector for an element\n function getSelector(el: Element): string {\n if (el.id) return `#${CSS.escape(el.id)}`;\n if (el.getAttribute(\"name\"))\n return `${el.tagName.toLowerCase()}[name=\"${CSS.escape(el.getAttribute(\"name\")!)}\"]`;\n if (el.getAttribute(\"type\") && el.tagName === \"INPUT\")\n return `input[type=\"${el.getAttribute(\"type\")}\"]`;\n\n // Fall back to nth-child\n const parent = el.parentElement;\n if (!parent) return el.tagName.toLowerCase();\n const siblings = Array.from(parent.children);\n const index = siblings.indexOf(el) + 1;\n return `${parent.tagName.toLowerCase()} > ${el.tagName.toLowerCase()}:nth-child(${index})`;\n }\n\n // Strategy 1: Find <form> containing password input\n const forms = document.querySelectorAll(\"form\");\n for (const form of forms) {\n const passwordInput = form.querySelector(\n 'input[type=\"password\"]',\n ) as HTMLInputElement | null;\n if (!passwordInput) continue;\n\n const usernameInput = findUsernameInput(form);\n const submitButton = findSubmitButton(form);\n\n return {\n usernameSelector: usernameInput\n ? getSelector(usernameInput)\n : 'input[type=\"text\"]',\n passwordSelector: getSelector(passwordInput),\n submitSelector: submitButton ? getSelector(submitButton) : null,\n autoDetected: true,\n };\n }\n\n // Strategy 2: No <form> — look for password input anywhere on page\n const passwordInput = document.querySelector(\n 'input[type=\"password\"]',\n ) as HTMLInputElement | null;\n if (passwordInput) {\n const usernameInput = findUsernameInput(document.body);\n const submitButton = findSubmitButton(document.body);\n\n return {\n usernameSelector: usernameInput\n ? getSelector(usernameInput)\n : 'input[type=\"text\"]',\n passwordSelector: getSelector(passwordInput),\n submitSelector: submitButton ? getSelector(submitButton) : null,\n autoDetected: true,\n };\n }\n\n return null;\n });\n}\n\n// ── Login ──────────────────────────────────────────────────────────────\n\n/**\n * Perform login using detected form or custom selectors.\n *\n * Flow:\n * 1. Navigate to login URL (or target URL)\n * 2. Detect login form (or use custom selectors from credentials)\n * 3. Fill username + password\n * 4. Submit form\n * 5. Wait for navigation\n * 6. Check for logged-in indicator\n * 7. Capture storage state\n */\nexport async function performLogin(\n page: Page,\n context: BrowserContext,\n credentials: FormCredentials,\n options: {\n targetUrl: string;\n loggedInIndicator?: string;\n loggedOutIndicator?: string;\n },\n): Promise<LoginResult> {\n const loginUrl = credentials.loginUrl ?? options.targetUrl;\n\n // Navigate to login page\n await page.goto(loginUrl, { waitUntil: \"domcontentloaded\", timeout: 15000 });\n\n // Determine selectors — custom overrides or auto-detect\n let usernameSelector: string;\n let passwordSelector: string;\n let submitSelector: string | null;\n\n if (credentials.userSelector && credentials.passSelector) {\n // Use custom selectors\n usernameSelector = credentials.userSelector;\n passwordSelector = credentials.passSelector;\n submitSelector = null; // Will try to find automatically\n } else {\n // Auto-detect\n const form = await detectLoginForm(page);\n\n if (!form) {\n return {\n success: false,\n message: `No login form detected on ${loginUrl}. Use --user-field and --pass-field to specify selectors.`,\n };\n }\n\n usernameSelector = form.usernameSelector;\n passwordSelector = form.passwordSelector;\n submitSelector = form.submitSelector;\n }\n\n // Fill credentials\n try {\n await page.fill(usernameSelector, credentials.username, { timeout: 5000 });\n } catch {\n return {\n success: false,\n message: `Could not find username field: ${usernameSelector}`,\n };\n }\n\n try {\n await page.fill(passwordSelector, credentials.password, { timeout: 5000 });\n } catch {\n return {\n success: false,\n message: `Could not find password field: ${passwordSelector}`,\n };\n }\n\n // Submit form\n try {\n if (submitSelector) {\n await Promise.all([\n page\n .waitForNavigation({ waitUntil: \"domcontentloaded\", timeout: 10000 })\n .catch(() => {}),\n page.click(submitSelector, { timeout: 5000 }),\n ]);\n } else {\n // Try pressing Enter on the password field\n await Promise.all([\n page\n .waitForNavigation({ waitUntil: \"domcontentloaded\", timeout: 10000 })\n .catch(() => {}),\n page.press(passwordSelector, \"Enter\"),\n ]);\n }\n } catch {\n return {\n success: false,\n message: \"Failed to submit login form\",\n };\n }\n\n // Wait for page to settle\n await page.waitForTimeout(1000);\n\n // Check if login succeeded\n const bodyText = await page.textContent(\"body\").catch(() => \"\");\n\n if (\n options.loggedOutIndicator &&\n bodyText?.includes(options.loggedOutIndicator)\n ) {\n return {\n success: false,\n message: `Login failed — \"${options.loggedOutIndicator}\" still visible on page`,\n };\n }\n\n if (\n options.loggedInIndicator &&\n !bodyText?.includes(options.loggedInIndicator)\n ) {\n return {\n success: false,\n message: `Login uncertain — \"${options.loggedInIndicator}\" not found on page`,\n };\n }\n\n // Capture storage state (cookies + localStorage)\n const storageState = JSON.stringify(await context.storageState());\n\n return {\n success: true,\n message: \"Login successful\",\n storageState,\n };\n}\n\n// ── Session Expiry Detection ───────────────────────────────────────────\n\n/**\n * Check if the current session is still alive.\n *\n * Used during long-running scans to detect session expiry\n * and trigger re-authentication.\n */\nexport async function checkSessionAlive(\n page: Page,\n config: {\n loggedInIndicator?: string;\n loggedOutIndicator?: string;\n },\n): Promise<boolean> {\n try {\n const bodyText = await page.textContent(\"body\");\n if (!bodyText) return true; // Can't determine, assume alive\n\n // Check for logged-out indicator (strongest signal of expiry)\n if (\n config.loggedOutIndicator &&\n bodyText.includes(config.loggedOutIndicator)\n ) {\n return false;\n }\n\n // Check for missing logged-in indicator\n if (\n config.loggedInIndicator &&\n !bodyText.includes(config.loggedInIndicator)\n ) {\n return false;\n }\n\n return true;\n } catch {\n // Page error — assume session is dead\n return false;\n }\n}\n"],"mappings":"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","names":["getSeverity","passwordInput"]}
|