@vulcn/driver-browser 0.1.2 → 0.2.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 +73 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +73 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -435,6 +435,7 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
435
435
|
});
|
|
436
436
|
const context = await browser.newContext({ viewport });
|
|
437
437
|
const page = await context.newPage();
|
|
438
|
+
await ctx.options.onPageReady?.(page);
|
|
438
439
|
const eventFindings = [];
|
|
439
440
|
let currentPayloadInfo = null;
|
|
440
441
|
const dialogHandler = async (dialog) => {
|
|
@@ -492,13 +493,24 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
492
493
|
(step) => step.type === "browser.input" && step.injectable !== false
|
|
493
494
|
);
|
|
494
495
|
const allPayloads = [];
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
496
|
+
const payloadsByCategory = payloads.map(
|
|
497
|
+
(ps) => ps.payloads.map((value) => ({ payloadSet: ps, value }))
|
|
498
|
+
);
|
|
499
|
+
const maxLen = Math.max(...payloadsByCategory.map((c) => c.length));
|
|
500
|
+
for (let i = 0; i < maxLen; i++) {
|
|
501
|
+
for (const category of payloadsByCategory) {
|
|
502
|
+
if (i < category.length) {
|
|
503
|
+
allPayloads.push(category[i]);
|
|
504
|
+
}
|
|
498
505
|
}
|
|
499
506
|
}
|
|
507
|
+
const confirmedTypes = /* @__PURE__ */ new Set();
|
|
500
508
|
for (const injectableStep of injectableSteps) {
|
|
501
509
|
for (const { payloadSet, value } of allPayloads) {
|
|
510
|
+
const stepTypeKey = `${injectableStep.id}::${payloadSet.category}`;
|
|
511
|
+
if (confirmedTypes.has(stepTypeKey)) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
502
514
|
try {
|
|
503
515
|
currentPayloadInfo = {
|
|
504
516
|
stepId: injectableStep.id,
|
|
@@ -522,8 +534,16 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
522
534
|
if (reflectionFinding) {
|
|
523
535
|
allFindings.push(reflectionFinding);
|
|
524
536
|
}
|
|
537
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
525
538
|
for (const finding of allFindings) {
|
|
526
|
-
|
|
539
|
+
const dedupKey = `${finding.type}::${finding.stepId}::${finding.title}`;
|
|
540
|
+
if (!seenKeys.has(dedupKey)) {
|
|
541
|
+
seenKeys.add(dedupKey);
|
|
542
|
+
ctx.addFinding(finding);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (allFindings.length > 0) {
|
|
546
|
+
confirmedTypes.add(stepTypeKey);
|
|
527
547
|
}
|
|
528
548
|
eventFindings.length = 0;
|
|
529
549
|
payloadsTested++;
|
|
@@ -537,6 +557,7 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
537
557
|
page.off("dialog", dialogHandler);
|
|
538
558
|
page.off("console", consoleHandler);
|
|
539
559
|
currentPayloadInfo = null;
|
|
560
|
+
await ctx.options.onBeforeClose?.(page);
|
|
540
561
|
await browser.close();
|
|
541
562
|
}
|
|
542
563
|
return {
|
|
@@ -896,20 +917,58 @@ async function discoverForms(page, pageUrl) {
|
|
|
896
917
|
}
|
|
897
918
|
return forms;
|
|
898
919
|
}
|
|
920
|
+
var REDIRECT_PARAMS = /* @__PURE__ */ new Set([
|
|
921
|
+
"to",
|
|
922
|
+
"url",
|
|
923
|
+
"redirect",
|
|
924
|
+
"redirect_uri",
|
|
925
|
+
"redirect_url",
|
|
926
|
+
"return",
|
|
927
|
+
"return_url",
|
|
928
|
+
"returnto",
|
|
929
|
+
"next",
|
|
930
|
+
"goto",
|
|
931
|
+
"dest",
|
|
932
|
+
"destination",
|
|
933
|
+
"continue",
|
|
934
|
+
"target",
|
|
935
|
+
"rurl",
|
|
936
|
+
"out",
|
|
937
|
+
"link",
|
|
938
|
+
"forward"
|
|
939
|
+
]);
|
|
940
|
+
function isExternalRedirectLink(link, origin) {
|
|
941
|
+
try {
|
|
942
|
+
const parsed = new URL(link);
|
|
943
|
+
if (parsed.origin !== origin) return false;
|
|
944
|
+
for (const [key, value] of parsed.searchParams) {
|
|
945
|
+
if (REDIRECT_PARAMS.has(key.toLowerCase())) {
|
|
946
|
+
try {
|
|
947
|
+
const targetUrl = new URL(value);
|
|
948
|
+
if (targetUrl.origin !== origin) return true;
|
|
949
|
+
} catch {
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return false;
|
|
954
|
+
} catch {
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
899
958
|
async function discoverLinks(page, origin, sameOrigin) {
|
|
900
959
|
const links = await page.evaluate(() => {
|
|
901
960
|
return Array.from(document.querySelectorAll("a[href]")).map((a) => a.href).filter((href) => href.startsWith("http"));
|
|
902
961
|
});
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
962
|
+
return links.filter((link) => {
|
|
963
|
+
try {
|
|
964
|
+
const linkOrigin = new URL(link).origin;
|
|
965
|
+
if (sameOrigin && linkOrigin !== origin) return false;
|
|
966
|
+
if (isExternalRedirectLink(link, origin)) return false;
|
|
967
|
+
return true;
|
|
968
|
+
} catch {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
});
|
|
913
972
|
}
|
|
914
973
|
function buildSessions(forms) {
|
|
915
974
|
const targetForms = forms.filter((f) => f.inputs.some((i) => i.injectable));
|
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"],"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\";\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\nimport type { Page, Dialog, ConsoleMessage } 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/**\n * Browser Runner - replays sessions with payloads\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads\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 // Launch browser\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Event findings from dialog/console handlers\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n } | null = null;\n\n // Dialog handler (for alert-based XSS detection)\n // ANY dialog triggered during payload testing is evidence of XSS execution\n const dialogHandler = async (dialog: Dialog) => {\n if (currentPayloadInfo) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.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 // Console message handler\n const consoleHandler = async (msg: ConsoleMessage) => {\n if (currentPayloadInfo && msg.type() === \"log\") {\n const text = msg.text();\n if (\n text.includes(\"vulcn\") ||\n text.includes(currentPayloadInfo.payloadValue)\n ) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\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 flat list of all individual payloads to test\n const allPayloads: { payloadSet: RuntimePayload; value: string }[] = [];\n for (const payloadSet of payloads) {\n for (const value of payloadSet.payloads) {\n allPayloads.push({ payloadSet, value });\n }\n }\n\n // For each injectable step, test with each payload\n for (const injectableStep of injectableSteps) {\n for (const { payloadSet, value } of allPayloads) {\n try {\n currentPayloadInfo = {\n stepId: injectableStep.id,\n payloadSet,\n payloadValue: value,\n };\n\n // Replay session with payload\n await BrowserRunner.replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n\n // Check for reflection\n const reflectionFinding = await BrowserRunner.checkReflection(\n page,\n injectableStep,\n payloadSet,\n value,\n );\n\n // Collect all findings\n const allFindings = [...eventFindings];\n if (reflectionFinding) {\n allFindings.push(reflectionFinding);\n }\n\n // Add unique findings\n for (const finding of allFindings) {\n ctx.addFinding(finding);\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 }\n }\n }\n } finally {\n page.off(\"dialog\", dialogHandler);\n page.off(\"console\", consoleHandler);\n currentPayloadInfo = null;\n await browser.close();\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 * 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 */\n private static async 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 // Replay ALL steps — inject payload at the target input step,\n // but continue replaying remaining steps (clicks, navigations)\n // so forms get submitted and payloads reach the server\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 // Skip post-submission navigates that have session-specific URLs\n // (they'll happen naturally from form submission)\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 this click is after injection, wait for potential navigation\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 // Inject payload for target step\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(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\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 * Check for payload reflection in page content\n */\n private static async 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: BrowserRunner.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 */\n private static 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","/**\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 };\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 });\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\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 if (sameOrigin) {\n return links.filter((link) => {\n try {\n return new URL(link).origin === origin;\n } catch {\n return false;\n }\n });\n }\n\n return links;\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/recorder.ts","../src/runner.ts","../src/crawler.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\";\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\nimport type { Page, Dialog, ConsoleMessage } 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/**\n * Browser Runner - replays sessions with payloads\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads\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 // Launch browser\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n const context = await browser.newContext({ viewport });\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 findings from dialog/console handlers\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n } | null = null;\n\n // Dialog handler (for alert-based XSS detection)\n // ANY dialog triggered during payload testing is evidence of XSS execution\n const dialogHandler = async (dialog: Dialog) => {\n if (currentPayloadInfo) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.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 // Console message handler\n const consoleHandler = async (msg: ConsoleMessage) => {\n if (currentPayloadInfo && msg.type() === \"log\") {\n const text = msg.text();\n if (\n text.includes(\"vulcn\") ||\n text.includes(currentPayloadInfo.payloadValue)\n ) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\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 flat list of payloads, interleaved by category (round-robin).\n // This ensures we test at least one payload from each category quickly,\n // so the dedup early-break fires sooner on slow SPAs like Angular apps.\n // Before: [sqli1, sqli2, ..., sqli50, xss1, xss2, ..., xss50]\n // After: [sqli1, xss1, sqli2, xss2, ..., sqli50, xss50]\n const allPayloads: { payloadSet: RuntimePayload; value: string }[] = [];\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 allPayloads.push(category[i]);\n }\n }\n }\n\n // Track confirmed vulnerability types per-step to avoid duplicate findings.\n // Once XSS is confirmed on an input (e.g., via dialog), skip remaining XSS payloads.\n const confirmedTypes = new Set<string>();\n\n // For each injectable step, test with each payload\n for (const injectableStep of injectableSteps) {\n for (const { payloadSet, value } of allPayloads) {\n // Skip if this vulnerability 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 // Replay session with payload\n await BrowserRunner.replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n\n // Check for reflection\n const reflectionFinding = await BrowserRunner.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 (dialog, console, or reflection), mark as confirmed\n // and skip remaining payloads of this category for this input.\n // One confirmed finding is enough evidence — no need to test more payloads.\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 }\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 // (e.g., passive scanner's in-flight response header analysis)\n await ctx.options.onBeforeClose?.(page);\n\n await browser.close();\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 * 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 */\n private static async 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 // Replay ALL steps — inject payload at the target input step,\n // but continue replaying remaining steps (clicks, navigations)\n // so forms get submitted and payloads reach the server\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 // Skip post-submission navigates that have session-specific URLs\n // (they'll happen naturally from form submission)\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 this click is after injection, wait for potential navigation\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 // Inject payload for target step\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(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\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 * Check for payload reflection in page content\n */\n private static async 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: BrowserRunner.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 */\n private static 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","/**\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 };\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 });\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"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -402,6 +402,7 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
402
402
|
});
|
|
403
403
|
const context = await browser.newContext({ viewport });
|
|
404
404
|
const page = await context.newPage();
|
|
405
|
+
await ctx.options.onPageReady?.(page);
|
|
405
406
|
const eventFindings = [];
|
|
406
407
|
let currentPayloadInfo = null;
|
|
407
408
|
const dialogHandler = async (dialog) => {
|
|
@@ -459,13 +460,24 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
459
460
|
(step) => step.type === "browser.input" && step.injectable !== false
|
|
460
461
|
);
|
|
461
462
|
const allPayloads = [];
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
463
|
+
const payloadsByCategory = payloads.map(
|
|
464
|
+
(ps) => ps.payloads.map((value) => ({ payloadSet: ps, value }))
|
|
465
|
+
);
|
|
466
|
+
const maxLen = Math.max(...payloadsByCategory.map((c) => c.length));
|
|
467
|
+
for (let i = 0; i < maxLen; i++) {
|
|
468
|
+
for (const category of payloadsByCategory) {
|
|
469
|
+
if (i < category.length) {
|
|
470
|
+
allPayloads.push(category[i]);
|
|
471
|
+
}
|
|
465
472
|
}
|
|
466
473
|
}
|
|
474
|
+
const confirmedTypes = /* @__PURE__ */ new Set();
|
|
467
475
|
for (const injectableStep of injectableSteps) {
|
|
468
476
|
for (const { payloadSet, value } of allPayloads) {
|
|
477
|
+
const stepTypeKey = `${injectableStep.id}::${payloadSet.category}`;
|
|
478
|
+
if (confirmedTypes.has(stepTypeKey)) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
469
481
|
try {
|
|
470
482
|
currentPayloadInfo = {
|
|
471
483
|
stepId: injectableStep.id,
|
|
@@ -489,8 +501,16 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
489
501
|
if (reflectionFinding) {
|
|
490
502
|
allFindings.push(reflectionFinding);
|
|
491
503
|
}
|
|
504
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
492
505
|
for (const finding of allFindings) {
|
|
493
|
-
|
|
506
|
+
const dedupKey = `${finding.type}::${finding.stepId}::${finding.title}`;
|
|
507
|
+
if (!seenKeys.has(dedupKey)) {
|
|
508
|
+
seenKeys.add(dedupKey);
|
|
509
|
+
ctx.addFinding(finding);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (allFindings.length > 0) {
|
|
513
|
+
confirmedTypes.add(stepTypeKey);
|
|
494
514
|
}
|
|
495
515
|
eventFindings.length = 0;
|
|
496
516
|
payloadsTested++;
|
|
@@ -504,6 +524,7 @@ var BrowserRunner = class _BrowserRunner {
|
|
|
504
524
|
page.off("dialog", dialogHandler);
|
|
505
525
|
page.off("console", consoleHandler);
|
|
506
526
|
currentPayloadInfo = null;
|
|
527
|
+
await ctx.options.onBeforeClose?.(page);
|
|
507
528
|
await browser.close();
|
|
508
529
|
}
|
|
509
530
|
return {
|
|
@@ -863,20 +884,58 @@ async function discoverForms(page, pageUrl) {
|
|
|
863
884
|
}
|
|
864
885
|
return forms;
|
|
865
886
|
}
|
|
887
|
+
var REDIRECT_PARAMS = /* @__PURE__ */ new Set([
|
|
888
|
+
"to",
|
|
889
|
+
"url",
|
|
890
|
+
"redirect",
|
|
891
|
+
"redirect_uri",
|
|
892
|
+
"redirect_url",
|
|
893
|
+
"return",
|
|
894
|
+
"return_url",
|
|
895
|
+
"returnto",
|
|
896
|
+
"next",
|
|
897
|
+
"goto",
|
|
898
|
+
"dest",
|
|
899
|
+
"destination",
|
|
900
|
+
"continue",
|
|
901
|
+
"target",
|
|
902
|
+
"rurl",
|
|
903
|
+
"out",
|
|
904
|
+
"link",
|
|
905
|
+
"forward"
|
|
906
|
+
]);
|
|
907
|
+
function isExternalRedirectLink(link, origin) {
|
|
908
|
+
try {
|
|
909
|
+
const parsed = new URL(link);
|
|
910
|
+
if (parsed.origin !== origin) return false;
|
|
911
|
+
for (const [key, value] of parsed.searchParams) {
|
|
912
|
+
if (REDIRECT_PARAMS.has(key.toLowerCase())) {
|
|
913
|
+
try {
|
|
914
|
+
const targetUrl = new URL(value);
|
|
915
|
+
if (targetUrl.origin !== origin) return true;
|
|
916
|
+
} catch {
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return false;
|
|
921
|
+
} catch {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
866
925
|
async function discoverLinks(page, origin, sameOrigin) {
|
|
867
926
|
const links = await page.evaluate(() => {
|
|
868
927
|
return Array.from(document.querySelectorAll("a[href]")).map((a) => a.href).filter((href) => href.startsWith("http"));
|
|
869
928
|
});
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
929
|
+
return links.filter((link) => {
|
|
930
|
+
try {
|
|
931
|
+
const linkOrigin = new URL(link).origin;
|
|
932
|
+
if (sameOrigin && linkOrigin !== origin) return false;
|
|
933
|
+
if (isExternalRedirectLink(link, origin)) return false;
|
|
934
|
+
return true;
|
|
935
|
+
} catch {
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
});
|
|
880
939
|
}
|
|
881
940
|
function buildSessions(forms) {
|
|
882
941
|
const targetForms = forms.filter((f) => f.inputs.some((i) => i.injectable));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/recorder.ts","../src/runner.ts","../src/crawler.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\";\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\nimport type { Page, Dialog, ConsoleMessage } 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/**\n * Browser Runner - replays sessions with payloads\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads\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 // Launch browser\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Event findings from dialog/console handlers\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n } | null = null;\n\n // Dialog handler (for alert-based XSS detection)\n // ANY dialog triggered during payload testing is evidence of XSS execution\n const dialogHandler = async (dialog: Dialog) => {\n if (currentPayloadInfo) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.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 // Console message handler\n const consoleHandler = async (msg: ConsoleMessage) => {\n if (currentPayloadInfo && msg.type() === \"log\") {\n const text = msg.text();\n if (\n text.includes(\"vulcn\") ||\n text.includes(currentPayloadInfo.payloadValue)\n ) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\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 flat list of all individual payloads to test\n const allPayloads: { payloadSet: RuntimePayload; value: string }[] = [];\n for (const payloadSet of payloads) {\n for (const value of payloadSet.payloads) {\n allPayloads.push({ payloadSet, value });\n }\n }\n\n // For each injectable step, test with each payload\n for (const injectableStep of injectableSteps) {\n for (const { payloadSet, value } of allPayloads) {\n try {\n currentPayloadInfo = {\n stepId: injectableStep.id,\n payloadSet,\n payloadValue: value,\n };\n\n // Replay session with payload\n await BrowserRunner.replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n\n // Check for reflection\n const reflectionFinding = await BrowserRunner.checkReflection(\n page,\n injectableStep,\n payloadSet,\n value,\n );\n\n // Collect all findings\n const allFindings = [...eventFindings];\n if (reflectionFinding) {\n allFindings.push(reflectionFinding);\n }\n\n // Add unique findings\n for (const finding of allFindings) {\n ctx.addFinding(finding);\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 }\n }\n }\n } finally {\n page.off(\"dialog\", dialogHandler);\n page.off(\"console\", consoleHandler);\n currentPayloadInfo = null;\n await browser.close();\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 * 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 */\n private static async 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 // Replay ALL steps — inject payload at the target input step,\n // but continue replaying remaining steps (clicks, navigations)\n // so forms get submitted and payloads reach the server\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 // Skip post-submission navigates that have session-specific URLs\n // (they'll happen naturally from form submission)\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 this click is after injection, wait for potential navigation\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 // Inject payload for target step\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(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\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 * Check for payload reflection in page content\n */\n private static async 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: BrowserRunner.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 */\n private static 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","/**\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 };\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 });\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\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 if (sameOrigin) {\n return links.filter((link) => {\n try {\n return new URL(link).origin === origin;\n } catch {\n return false;\n }\n });\n }\n\n return links;\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/recorder.ts","../src/runner.ts","../src/crawler.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\";\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\nimport type { Page, Dialog, ConsoleMessage } 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/**\n * Browser Runner - replays sessions with payloads\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads\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 // Launch browser\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n const context = await browser.newContext({ viewport });\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 findings from dialog/console handlers\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n } | null = null;\n\n // Dialog handler (for alert-based XSS detection)\n // ANY dialog triggered during payload testing is evidence of XSS execution\n const dialogHandler = async (dialog: Dialog) => {\n if (currentPayloadInfo) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.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 // Console message handler\n const consoleHandler = async (msg: ConsoleMessage) => {\n if (currentPayloadInfo && msg.type() === \"log\") {\n const text = msg.text();\n if (\n text.includes(\"vulcn\") ||\n text.includes(currentPayloadInfo.payloadValue)\n ) {\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: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\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 flat list of payloads, interleaved by category (round-robin).\n // This ensures we test at least one payload from each category quickly,\n // so the dedup early-break fires sooner on slow SPAs like Angular apps.\n // Before: [sqli1, sqli2, ..., sqli50, xss1, xss2, ..., xss50]\n // After: [sqli1, xss1, sqli2, xss2, ..., sqli50, xss50]\n const allPayloads: { payloadSet: RuntimePayload; value: string }[] = [];\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 allPayloads.push(category[i]);\n }\n }\n }\n\n // Track confirmed vulnerability types per-step to avoid duplicate findings.\n // Once XSS is confirmed on an input (e.g., via dialog), skip remaining XSS payloads.\n const confirmedTypes = new Set<string>();\n\n // For each injectable step, test with each payload\n for (const injectableStep of injectableSteps) {\n for (const { payloadSet, value } of allPayloads) {\n // Skip if this vulnerability 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 // Replay session with payload\n await BrowserRunner.replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n\n // Check for reflection\n const reflectionFinding = await BrowserRunner.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 (dialog, console, or reflection), mark as confirmed\n // and skip remaining payloads of this category for this input.\n // One confirmed finding is enough evidence — no need to test more payloads.\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 }\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 // (e.g., passive scanner's in-flight response header analysis)\n await ctx.options.onBeforeClose?.(page);\n\n await browser.close();\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 * 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 */\n private static async 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 // Replay ALL steps — inject payload at the target input step,\n // but continue replaying remaining steps (clicks, navigations)\n // so forms get submitted and payloads reach the server\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 // Skip post-submission navigates that have session-specific URLs\n // (they'll happen naturally from form submission)\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 this click is after injection, wait for potential navigation\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 // Inject payload for target step\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(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\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 * Check for payload reflection in page content\n */\n private static async 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: BrowserRunner.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 */\n private static 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","/**\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 };\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 });\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"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulcn/driver-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Playwright-based browser driver for Vulcn — record and replay web application interactions for automated security testing and vulnerability scanning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@types/node": "^22.0.0",
|
|
62
62
|
"tsup": "^8.4.0",
|
|
63
63
|
"typescript": "^5.7.0",
|
|
64
|
-
"@vulcn/engine": "0.
|
|
64
|
+
"@vulcn/engine": "0.7.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsup",
|