@vulcn/engine 0.4.0 → 0.7.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/CHANGELOG.md +52 -0
- package/dist/index.cjs +55 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +55 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 458572e: ### @vulcn/engine
|
|
8
|
+
- **`addFinding` on PluginContext**: Plugins now have `ctx.addFinding()` to report findings through the proper callback chain. This ensures consumers are notified via `onFinding` and findings survive timeouts. Plugins should use this instead of `ctx.findings.push()`.
|
|
9
|
+
- **`onPageReady` callback**: New `RunOptions.onPageReady` callback fires after the driver creates the browser page. The engine uses this to defer `onRunStart` plugin hooks until the page is ready, so plugins receive a real page object (not `null`).
|
|
10
|
+
- **`onBeforeClose` hook**: New plugin lifecycle hook called before the browser is closed. Plugins can flush in-flight async work here (e.g., pending response handlers that need browser access).
|
|
11
|
+
- **`onBeforeClose` callback**: New `RunOptions.onBeforeClose` callback fires before browser teardown, triggering plugin `onBeforeClose` hooks.
|
|
12
|
+
|
|
13
|
+
### @vulcn/driver-browser
|
|
14
|
+
- **`onPageReady` signal**: Runner now calls `ctx.options.onPageReady(page)` after creating the browser page, enabling plugins to attach event listeners before any navigation occurs.
|
|
15
|
+
- **`onBeforeClose` signal**: Runner now calls `ctx.options.onBeforeClose(page)` before `browser.close()`, giving plugins time to drain pending async work.
|
|
16
|
+
- **Payload interleaving**: Payloads are now ordered round-robin across categories (e.g., `[sqli1, xss1, sqli2, xss2, ...]`) instead of sequentially. This ensures faster category coverage and earlier dedup early-breaks on slow SPAs.
|
|
17
|
+
- **Extended dedup early-break**: The per-step category dedup now treats any finding (dialog, console, or reflection) as confirmation, not just dialog-based detections. One confirmed finding per category per step is sufficient.
|
|
18
|
+
|
|
19
|
+
### @vulcn/plugin-passive
|
|
20
|
+
- **Uses `ctx.addFinding()`**: All findings are now reported through the proper callback chain instead of pushing to `ctx.findings` directly. This fixes passive findings being invisible to `onFinding` consumers.
|
|
21
|
+
- **Cross-session dedup**: `reportedFindings` is no longer cleared between sessions, so the same passive finding (e.g., "Missing CSP" on the same origin) is reported once per scan, not once per crawled form.
|
|
22
|
+
- **Async handler drain**: Response handlers are tracked as promises and drained in the new `onBeforeClose` hook, preventing findings from being lost when the browser closes before async `response.allHeaders()` calls complete.
|
|
23
|
+
|
|
24
|
+
## 0.5.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- 56eb043: ### @vulcn/plugin-report
|
|
29
|
+
- **SARIF v2.1.0 output** — new `generateSarif()` generator produces reports compatible with GitHub Code Scanning, Azure DevOps, and other SARIF-consuming tools
|
|
30
|
+
- Added `"sarif"` to the `format` config option (`html | json | yaml | sarif | all`)
|
|
31
|
+
- CWE mappings for all vulnerability types including passive scan categories
|
|
32
|
+
- CVSS-like security-severity scores for GitHub Security tab sorting
|
|
33
|
+
- Fingerprinting and deduplication for stable result tracking across runs
|
|
34
|
+
|
|
35
|
+
### @vulcn/plugin-passive
|
|
36
|
+
- **New plugin** — passive security scanner that analyzes HTTP responses during session replay without injecting payloads
|
|
37
|
+
- Detects missing security headers (HSTS, CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy) with value validation
|
|
38
|
+
- Detects insecure cookie configurations (missing Secure, HttpOnly, SameSite flags)
|
|
39
|
+
- Detects information disclosure (Server version, X-Powered-By, debug tokens)
|
|
40
|
+
- Detects CORS misconfigurations (wildcard origins, credentials with wildcards)
|
|
41
|
+
- Detects mixed content (HTTP resources on HTTPS pages)
|
|
42
|
+
- Automatic deduplication — each unique issue reported once per origin
|
|
43
|
+
- Configurable severity levels and per-check enable/disable toggles
|
|
44
|
+
|
|
45
|
+
### @vulcn/engine
|
|
46
|
+
- Added `"security-misconfiguration"` and `"information-disclosure"` to `PayloadCategory` type
|
|
47
|
+
- These new categories support passive scanner findings in reports and SARIF output
|
|
48
|
+
|
|
49
|
+
### vulcn (CLI)
|
|
50
|
+
- Added `--passive` flag to `vulcn run` to enable the passive security scanner
|
|
51
|
+
- Added `"sarif"` to the `--report` format option
|
|
52
|
+
- Auto-loads `@vulcn/plugin-passive` when `--passive` is specified
|
|
53
|
+
- Updated help text with passive scanning and SARIF examples
|
|
54
|
+
|
|
3
55
|
## 0.4.0
|
|
4
56
|
|
|
5
57
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -197,23 +197,17 @@ var DriverManager = class {
|
|
|
197
197
|
/**
|
|
198
198
|
* Execute a session
|
|
199
199
|
* Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.
|
|
200
|
+
* Plugin onRunStart is deferred until the driver signals the page is ready
|
|
201
|
+
* via the onPageReady callback, ensuring plugins get a real page object.
|
|
200
202
|
*/
|
|
201
203
|
async execute(session, pluginManager2, options = {}) {
|
|
202
204
|
const driver = this.getForSession(session);
|
|
203
205
|
const findings = [];
|
|
204
206
|
const logger = this.createLogger(driver.name);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
findings,
|
|
210
|
-
addFinding: (finding) => {
|
|
211
|
-
findings.push(finding);
|
|
212
|
-
pluginManager2.addFinding(finding);
|
|
213
|
-
options.onFinding?.(finding);
|
|
214
|
-
},
|
|
215
|
-
logger,
|
|
216
|
-
options
|
|
207
|
+
const addFinding = (finding) => {
|
|
208
|
+
findings.push(finding);
|
|
209
|
+
pluginManager2.addFinding(finding);
|
|
210
|
+
options.onFinding?.(finding);
|
|
217
211
|
};
|
|
218
212
|
const pluginCtx = {
|
|
219
213
|
session,
|
|
@@ -223,21 +217,57 @@ var DriverManager = class {
|
|
|
223
217
|
engine: { version: "0.3.0", pluginApiVersion: 1 },
|
|
224
218
|
payloads: pluginManager2.getPayloads(),
|
|
225
219
|
findings,
|
|
220
|
+
addFinding,
|
|
226
221
|
logger,
|
|
227
222
|
fetch: globalThis.fetch
|
|
228
223
|
};
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
224
|
+
const ctx = {
|
|
225
|
+
session,
|
|
226
|
+
pluginManager: pluginManager2,
|
|
227
|
+
payloads: pluginManager2.getPayloads(),
|
|
228
|
+
findings,
|
|
229
|
+
addFinding,
|
|
230
|
+
logger,
|
|
231
|
+
options: {
|
|
232
|
+
...options,
|
|
233
|
+
// Provide onPageReady callback — fires plugin onRunStart hooks
|
|
234
|
+
// with the real page object once the driver has created it
|
|
235
|
+
onPageReady: async (page) => {
|
|
236
|
+
pluginCtx.page = page;
|
|
237
|
+
for (const loaded of pluginManager2.getPlugins()) {
|
|
238
|
+
if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {
|
|
239
|
+
try {
|
|
240
|
+
await loaded.plugin.hooks.onRunStart({
|
|
241
|
+
...pluginCtx,
|
|
242
|
+
config: loaded.config
|
|
243
|
+
});
|
|
244
|
+
} catch (err) {
|
|
245
|
+
logger.warn(
|
|
246
|
+
`Plugin ${loaded.plugin.name} onRunStart failed: ${err}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
// Fires before browser closes — lets plugins flush pending async work
|
|
253
|
+
onBeforeClose: async (_page) => {
|
|
254
|
+
for (const loaded of pluginManager2.getPlugins()) {
|
|
255
|
+
if (loaded.enabled && loaded.plugin.hooks?.onBeforeClose) {
|
|
256
|
+
try {
|
|
257
|
+
await loaded.plugin.hooks.onBeforeClose({
|
|
258
|
+
...pluginCtx,
|
|
259
|
+
config: loaded.config
|
|
260
|
+
});
|
|
261
|
+
} catch (err) {
|
|
262
|
+
logger.warn(
|
|
263
|
+
`Plugin ${loaded.plugin.name} onBeforeClose failed: ${err}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
238
268
|
}
|
|
239
269
|
}
|
|
240
|
-
}
|
|
270
|
+
};
|
|
241
271
|
let result = await driver.runner.execute(session, ctx);
|
|
242
272
|
for (const loaded of pluginManager2.getPlugins()) {
|
|
243
273
|
if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {
|
|
@@ -528,6 +558,9 @@ var PluginManager = class {
|
|
|
528
558
|
engine: engineInfo,
|
|
529
559
|
payloads: this.sharedPayloads,
|
|
530
560
|
findings: this.sharedFindings,
|
|
561
|
+
addFinding: (finding) => {
|
|
562
|
+
this.sharedFindings.push(finding);
|
|
563
|
+
},
|
|
531
564
|
logger: this.createLogger("plugin"),
|
|
532
565
|
fetch: globalThis.fetch
|
|
533
566
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/driver-manager.ts","../src/driver-types.ts","../src/plugin-manager.ts","../src/plugin-types.ts"],"sourcesContent":["/**\n * @vulcn/engine - Core security testing engine\n *\n * v0.3.0: Driver-based architecture\n *\n * The engine now provides:\n * - Driver system for different recording targets (browser, api, cli)\n * - Plugin system for payloads and detection\n * - Generic session format\n *\n * Drivers handle:\n * - Recording interactions (RecorderDriver)\n * - Replaying with payload injection (RunnerDriver)\n *\n * Plugins handle:\n * - Payload loading (builtin, payloadbox, custom files)\n * - Vulnerability detection (reflection, execution, etc.)\n * - Reporting (JSON, SARIF, HTML)\n */\n\n// ============================================================================\n// Driver System\n// ============================================================================\n\nexport { DriverManager, driverManager } from \"./driver-manager\";\nexport { DRIVER_API_VERSION } from \"./driver-types\";\nexport type {\n VulcnDriver,\n RecorderDriver,\n RunnerDriver,\n RecordingHandle,\n RecordOptions,\n CrawlOptions,\n RunOptions,\n RunResult,\n RunContext,\n Session,\n Step,\n DriverLogger,\n LoadedDriver,\n DriverSource,\n} from \"./driver-types\";\n\n// ============================================================================\n// Plugin System\n// ============================================================================\n\nexport { PluginManager, pluginManager } from \"./plugin-manager\";\nexport { PLUGIN_API_VERSION } from \"./plugin-types\";\nexport type {\n VulcnPlugin,\n VulcnConfig,\n PluginConfig,\n PluginHooks,\n PluginContext,\n RecordContext,\n RunContext as PluginRunContext,\n DetectContext,\n LoadedPlugin as LoadedPluginInfo,\n PluginLogger,\n EngineInfo,\n PluginSource,\n} from \"./plugin-types\";\n\n// ============================================================================\n// Payload Types\n// ============================================================================\n\nexport type {\n PayloadCategory,\n PayloadSource,\n RuntimePayload,\n CustomPayload,\n CustomPayloadFile,\n} from \"./payload-types\";\n\n// ============================================================================\n// Core Types\n// ============================================================================\n\nexport type { Finding } from \"./types\";\n","/**\n * Vulcn Driver Manager\n *\n * Handles driver loading, registration, and lifecycle.\n * Drivers are loaded from npm packages or local files.\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { parse, stringify } from \"yaml\";\nimport type {\n VulcnDriver,\n LoadedDriver,\n DriverSource,\n Session,\n Step,\n RunContext,\n RunResult,\n RunOptions,\n RecordOptions,\n CrawlOptions,\n RecordingHandle,\n DriverLogger,\n DRIVER_API_VERSION,\n} from \"./driver-types\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n/**\n * Driver Manager - loads and manages recording/running drivers\n */\nexport class DriverManager {\n private drivers: Map<string, LoadedDriver> = new Map();\n private defaultDriver: string | null = null;\n\n /**\n * Register a driver\n */\n register(driver: VulcnDriver, source: DriverSource = \"builtin\"): void {\n this.validateDriver(driver);\n this.drivers.set(driver.name, { driver, source });\n\n // First registered driver becomes default\n if (this.drivers.size === 1) {\n this.defaultDriver = driver.name;\n }\n }\n\n /**\n * Load a driver from npm or local path\n */\n async load(nameOrPath: string): Promise<void> {\n let driver: VulcnDriver;\n let source: DriverSource;\n\n if (\n nameOrPath.startsWith(\"./\") ||\n nameOrPath.startsWith(\"../\") ||\n isAbsolute(nameOrPath)\n ) {\n // Local file\n const resolved = isAbsolute(nameOrPath)\n ? nameOrPath\n : resolve(process.cwd(), nameOrPath);\n const module = await import(resolved);\n driver = module.default || module;\n source = \"local\";\n } else {\n // npm package\n const module = await import(nameOrPath);\n driver = module.default || module;\n source = \"npm\";\n }\n\n this.register(driver, source);\n }\n\n /**\n * Get a loaded driver by name\n */\n get(name: string): VulcnDriver | undefined {\n return this.drivers.get(name)?.driver;\n }\n\n /**\n * Get the default driver\n */\n getDefault(): VulcnDriver | undefined {\n if (!this.defaultDriver) return undefined;\n return this.get(this.defaultDriver);\n }\n\n /**\n * Set the default driver\n */\n setDefault(name: string): void {\n if (!this.drivers.has(name)) {\n throw new Error(`Driver \"${name}\" is not registered`);\n }\n this.defaultDriver = name;\n }\n\n /**\n * Check if a driver is registered\n */\n has(name: string): boolean {\n return this.drivers.has(name);\n }\n\n /**\n * Get all registered drivers\n */\n list(): LoadedDriver[] {\n return Array.from(this.drivers.values());\n }\n\n /**\n * Get driver for a session\n */\n getForSession(session: Session): VulcnDriver {\n const driverName = session.driver;\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(\n `Driver \"${driverName}\" not found. Install @vulcn/driver-${driverName} or load it manually.`,\n );\n }\n\n return driver;\n }\n\n /**\n * Parse a YAML session string into a Session object.\n *\n * Handles both new driver-format sessions and legacy v1 sessions.\n * Legacy sessions (those with non-namespaced step types like \"click\",\n * \"input\", \"navigate\") are automatically converted to the driver format\n * (e.g., \"browser.click\", \"browser.input\", \"browser.navigate\").\n *\n * @param yaml - Raw YAML string\n * @param defaultDriver - Driver to assign for legacy sessions (default: \"browser\")\n */\n parseSession(yaml: string, defaultDriver = \"browser\"): Session {\n const data = parse(yaml) as Record<string, unknown>;\n\n // Already in driver format — has a `driver` field\n if (data.driver && typeof data.driver === \"string\") {\n return data as unknown as Session;\n }\n\n // Legacy format — convert to driver session\n const steps = (data.steps as Array<Record<string, unknown>>) ?? [];\n const convertedSteps: Step[] = steps.map((step) => {\n const type = step.type as string;\n\n // If step type is already namespaced (e.g. \"browser.click\"), keep it\n if (type.includes(\".\")) {\n return step as unknown as Step;\n }\n\n // Convert legacy type → namespaced type\n return {\n ...step,\n type: `${defaultDriver}.${type}`,\n } as unknown as Step;\n });\n\n return {\n name: (data.name as string) ?? \"Untitled Session\",\n driver: defaultDriver,\n driverConfig: {\n browser: data.browser ?? \"chromium\",\n viewport: data.viewport ?? { width: 1280, height: 720 },\n startUrl: data.startUrl as string,\n },\n steps: convertedSteps,\n metadata: {\n recordedAt: data.recordedAt as string,\n version: (data.version as string) ?? \"1\",\n },\n };\n }\n\n /**\n * Start recording with a driver\n */\n async startRecording(\n driverName: string,\n config: Record<string, unknown>,\n options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n return driver.recorder.start(config, options);\n }\n\n /**\n * Auto-crawl a URL using a driver.\n *\n * Uses the driver's optional crawl() method to automatically\n * discover forms and injection points, returning Session[] that\n * can be passed to execute().\n *\n * Not all drivers support this — only browser has crawl capability.\n * CLI and API drivers will throw.\n */\n async crawl(\n driverName: string,\n config: Record<string, unknown>,\n options: CrawlOptions = {},\n ): Promise<Session[]> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n if (!driver.recorder.crawl) {\n throw new Error(\n `Driver \"${driverName}\" does not support auto-crawl. Use manual recording instead.`,\n );\n }\n\n return driver.recorder.crawl(config, options);\n }\n\n /**\n * Execute a session\n * Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.\n */\n async execute(\n session: Session,\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<RunResult> {\n const driver = this.getForSession(session);\n const findings: Finding[] = [];\n const logger = this.createLogger(driver.name);\n\n const ctx: RunContext = {\n session,\n pluginManager,\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding: (finding) => {\n findings.push(finding);\n pluginManager.addFinding(finding);\n options.onFinding?.(finding);\n },\n logger,\n options,\n };\n\n // Build a plugin context for hooks\n const pluginCtx = {\n session,\n page: null as unknown,\n headless: !!(options as Record<string, unknown>).headless,\n config: {} as Record<string, unknown>,\n engine: { version: \"0.3.0\", pluginApiVersion: 1 },\n payloads: pluginManager.getPayloads(),\n findings,\n logger,\n fetch: globalThis.fetch,\n };\n\n // Call onRunStart hooks\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {\n try {\n await loaded.plugin.hooks.onRunStart({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(`Plugin ${loaded.plugin.name} onRunStart failed: ${err}`);\n }\n }\n }\n\n // Execute via driver runner\n let result = await driver.runner.execute(session, ctx);\n\n // Call onRunEnd hooks (e.g., report generation)\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {\n try {\n result = await loaded.plugin.hooks.onRunEnd(result, {\n ...pluginCtx,\n config: loaded.config,\n findings: result.findings,\n });\n } catch (err) {\n logger.warn(`Plugin ${loaded.plugin.name} onRunEnd failed: ${err}`);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Validate driver structure\n */\n private validateDriver(driver: unknown): asserts driver is VulcnDriver {\n if (!driver || typeof driver !== \"object\") {\n throw new Error(\"Driver must be an object\");\n }\n\n const d = driver as Record<string, unknown>;\n\n if (typeof d.name !== \"string\" || !d.name) {\n throw new Error(\"Driver must have a name\");\n }\n\n if (typeof d.version !== \"string\" || !d.version) {\n throw new Error(\"Driver must have a version\");\n }\n\n if (!Array.isArray(d.stepTypes) || d.stepTypes.length === 0) {\n throw new Error(\"Driver must define stepTypes\");\n }\n\n if (!d.recorder || typeof d.recorder !== \"object\") {\n throw new Error(\"Driver must have a recorder\");\n }\n\n if (!d.runner || typeof d.runner !== \"object\") {\n throw new Error(\"Driver must have a runner\");\n }\n }\n\n /**\n * Create a scoped logger for a driver\n */\n private createLogger(name: string): DriverLogger {\n const prefix = `[driver:${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n}\n\n/**\n * Default driver manager instance\n */\nexport const driverManager = new DriverManager();\n","/**\n * Vulcn Driver System\n *\n * Drivers handle recording and running sessions for different targets:\n * - browser: Web applications (Playwright)\n * - api: REST/HTTP APIs\n * - cli: Command-line tools\n *\n * Each driver implements RecorderDriver and RunnerDriver interfaces.\n */\n\nimport type { z } from \"zod\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { PluginManager } from \"./plugin-manager\";\n\n/**\n * Current driver API version\n */\nexport const DRIVER_API_VERSION = 1;\n\n/**\n * Generic step - drivers define their own step types\n */\nexport interface Step {\n /** Unique step ID */\n id: string;\n\n /** Step type (namespaced, e.g., \"browser.click\", \"api.request\") */\n type: string;\n\n /** Timestamp when step was recorded */\n timestamp: number;\n\n /** Step-specific data */\n [key: string]: unknown;\n}\n\n/**\n * Generic session format\n */\nexport interface Session {\n /** Session name */\n name: string;\n\n /** Driver that recorded this session */\n driver: string;\n\n /** Driver-specific configuration */\n driverConfig: Record<string, unknown>;\n\n /** Recorded steps */\n steps: Step[];\n\n /** Session metadata */\n metadata?: {\n recordedAt?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Recording context passed to drivers\n */\nexport interface RecordContext {\n /** Session being built */\n session: Partial<Session>;\n\n /** Add a step to the session */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n\n /** Logger */\n logger: DriverLogger;\n}\n\n/**\n * Running context passed to drivers\n */\nexport interface RunContext {\n /** Session being executed */\n session: Session;\n\n /** Plugin manager for calling hooks */\n pluginManager: PluginManager;\n\n /** Available payloads */\n payloads: RuntimePayload[];\n\n /** Collected findings */\n findings: Finding[];\n\n /** Add a finding */\n addFinding(finding: Finding): void;\n\n /** Logger */\n logger: DriverLogger;\n\n /** Running options */\n options: RunOptions;\n}\n\n/**\n * Options for recording\n */\nexport interface RecordOptions {\n /** Enable auto-crawl mode (driver discovers forms automatically) */\n auto?: boolean;\n\n /** Crawl options (only used when auto=true) */\n crawlOptions?: CrawlOptions;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Options for auto-crawl mode\n *\n * When a driver supports crawling, these options control how\n * the automated discovery works. Not all drivers support crawling —\n * it's optional and primarily used by the browser driver.\n */\nexport interface CrawlOptions {\n /** Maximum crawl depth (0 = only the given URL, default: 2) */\n maxDepth?: number;\n\n /** Maximum number of pages to visit (default: 20) */\n maxPages?: number;\n\n /** Timeout per page navigation in ms (default: 10000) */\n pageTimeout?: number;\n\n /** Only crawl pages under the same origin (default: true) */\n sameOrigin?: boolean;\n\n /** Callback when a page is crawled */\n onPageCrawled?: (url: string, formsFound: number) => void;\n}\n\n/**\n * Options for running\n */\nexport interface RunOptions {\n /** Run headless (for visual drivers) */\n headless?: boolean;\n\n /** Callback for findings */\n onFinding?: (finding: Finding) => void;\n\n /** Callback for step completion */\n onStepComplete?: (stepId: string, payloadCount: number) => void;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Run result\n */\nexport interface RunResult {\n /** All findings */\n findings: Finding[];\n\n /** Steps executed */\n stepsExecuted: number;\n\n /** Payloads tested */\n payloadsTested: number;\n\n /** Duration in milliseconds */\n duration: number;\n\n /** Errors encountered */\n errors: string[];\n}\n\n/**\n * Driver logger\n */\nexport interface DriverLogger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\n/**\n * Recorder Driver Interface\n *\n * Implement this to add recording support for a target type.\n */\nexport interface RecorderDriver {\n /** Start recording and return control handle */\n start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle>;\n\n /**\n * Auto-crawl a URL and generate sessions.\n *\n * Optional — only drivers that support automated discovery\n * (e.g., browser) implement this. CLI and API drivers do not.\n *\n * When options.auto=true is passed to startRecording, the engine\n * calls this instead of start().\n */\n crawl?(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]>;\n}\n\n/**\n * Handle returned by RecorderDriver.start()\n */\nexport interface RecordingHandle {\n /** Stop recording and return the session */\n stop(): Promise<Session>;\n\n /** Abort recording without saving */\n abort(): Promise<void>;\n\n /** Get current steps (during recording) */\n getSteps(): Step[];\n\n /** Manually add a step */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n}\n\n/**\n * Runner Driver Interface\n *\n * Implement this to add running/replay support for a target type.\n */\nexport interface RunnerDriver {\n /** Execute a session with payloads */\n execute(session: Session, ctx: RunContext): Promise<RunResult>;\n}\n\n/**\n * Complete driver definition\n */\nexport interface VulcnDriver {\n /** Unique driver name (e.g., \"browser\", \"api\", \"cli\") */\n name: string;\n\n /** Driver version */\n version: string;\n\n /** Driver API version */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Configuration schema (Zod) */\n configSchema?: z.ZodSchema;\n\n /** Step types this driver handles */\n stepTypes: string[];\n\n /** Recorder implementation */\n recorder: RecorderDriver;\n\n /** Runner implementation */\n runner: RunnerDriver;\n}\n\n/**\n * Driver source for loading\n */\nexport type DriverSource = \"npm\" | \"local\" | \"builtin\";\n\n/**\n * Loaded driver with metadata\n */\nexport interface LoadedDriver {\n driver: VulcnDriver;\n source: DriverSource;\n}\n","/**\n * Vulcn Plugin Manager\n * Handles plugin loading, lifecycle, and hook execution\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve, isAbsolute } from \"node:path\";\nimport YAML from \"yaml\";\nimport { z } from \"zod\";\nimport type {\n VulcnPlugin,\n VulcnConfig,\n PluginConfig,\n LoadedPlugin,\n PluginContext,\n PluginSource,\n PluginLogger,\n EngineInfo,\n PluginHooks,\n} from \"./plugin-types\";\nimport { PLUGIN_API_VERSION } from \"./plugin-types\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n// Package version (injected at build time or read from package.json)\nconst ENGINE_VERSION = \"0.2.0\";\n\n/**\n * Config file schema\n */\nconst VulcnConfigSchema = z.object({\n version: z.string().default(\"1\"),\n plugins: z\n .array(\n z.object({\n name: z.string(),\n config: z.record(z.unknown()).optional(),\n enabled: z.boolean().default(true),\n }),\n )\n .optional(),\n settings: z\n .object({\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).optional(),\n headless: z.boolean().optional(),\n timeout: z.number().optional(),\n })\n .optional(),\n});\n\n/**\n * Plugin Manager - loads, configures, and orchestrates plugins\n */\nexport class PluginManager {\n private plugins: LoadedPlugin[] = [];\n private config: VulcnConfig | null = null;\n private initialized = false;\n\n /**\n * Shared context passed to all plugins\n */\n private sharedPayloads: RuntimePayload[] = [];\n private sharedFindings: Finding[] = [];\n\n /**\n * Load configuration from vulcn.config.yml\n */\n async loadConfig(configPath?: string): Promise<VulcnConfig> {\n const paths = configPath\n ? [configPath]\n : [\n \"vulcn.config.yml\",\n \"vulcn.config.yaml\",\n \"vulcn.config.json\",\n \".vulcnrc.yml\",\n \".vulcnrc.yaml\",\n \".vulcnrc.json\",\n ];\n\n for (const path of paths) {\n const resolved = isAbsolute(path) ? path : resolve(process.cwd(), path);\n if (existsSync(resolved)) {\n const content = await readFile(resolved, \"utf-8\");\n const parsed = path.endsWith(\".json\")\n ? JSON.parse(content)\n : YAML.parse(content);\n this.config = VulcnConfigSchema.parse(parsed);\n return this.config;\n }\n }\n\n // No config file - use defaults\n this.config = { version: \"1\", plugins: [], settings: {} };\n return this.config;\n }\n\n /**\n * Load all plugins from config\n */\n async loadPlugins(): Promise<void> {\n if (!this.config) {\n await this.loadConfig();\n }\n\n const pluginConfigs = this.config?.plugins || [];\n\n for (const pluginConfig of pluginConfigs) {\n if (pluginConfig.enabled === false) continue;\n\n try {\n const loaded = await this.loadPlugin(pluginConfig);\n this.plugins.push(loaded);\n } catch (err) {\n console.error(\n `Failed to load plugin ${pluginConfig.name}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n /**\n * Load a single plugin\n */\n private async loadPlugin(config: PluginConfig): Promise<LoadedPlugin> {\n const { name, config: pluginConfig = {} } = config;\n let plugin: VulcnPlugin;\n let source: PluginSource;\n\n // Determine plugin source and load\n if (name.startsWith(\"./\") || name.startsWith(\"../\") || isAbsolute(name)) {\n // Local file plugin\n const resolved = isAbsolute(name) ? name : resolve(process.cwd(), name);\n const module = await import(resolved);\n plugin = module.default || module;\n source = \"local\";\n } else if (name.startsWith(\"@vulcn/\")) {\n // Official plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n } else {\n // Community plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n }\n\n // Validate plugin structure\n this.validatePlugin(plugin);\n\n // Validate plugin config if schema provided\n let resolvedConfig = pluginConfig;\n if (plugin.configSchema) {\n try {\n resolvedConfig = plugin.configSchema.parse(pluginConfig);\n } catch (err) {\n throw new Error(\n `Invalid config for plugin ${name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return {\n plugin,\n config: resolvedConfig,\n source,\n enabled: true,\n };\n }\n\n /**\n * Validate plugin structure\n */\n private validatePlugin(plugin: unknown): asserts plugin is VulcnPlugin {\n if (!plugin || typeof plugin !== \"object\") {\n throw new Error(\"Plugin must be an object\");\n }\n\n const p = plugin as Record<string, unknown>;\n if (typeof p.name !== \"string\" || !p.name) {\n throw new Error(\"Plugin must have a name\");\n }\n if (typeof p.version !== \"string\" || !p.version) {\n throw new Error(\"Plugin must have a version\");\n }\n\n // Check API version compatibility\n const apiVersion = (p.apiVersion as number) || 1;\n if (apiVersion > PLUGIN_API_VERSION) {\n throw new Error(\n `Plugin requires API version ${apiVersion}, but engine supports ${PLUGIN_API_VERSION}`,\n );\n }\n }\n\n /**\n * Add a plugin programmatically (for testing or dynamic loading)\n */\n addPlugin(plugin: VulcnPlugin, config: Record<string, unknown> = {}): void {\n this.validatePlugin(plugin);\n this.plugins.push({\n plugin,\n config,\n source: \"custom\",\n enabled: true,\n });\n }\n\n /**\n * Initialize all plugins (call onInit hooks)\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Load payloads from plugins that provide them\n for (const loaded of this.plugins) {\n if (loaded.plugin.payloads) {\n const payloads =\n typeof loaded.plugin.payloads === \"function\"\n ? await loaded.plugin.payloads()\n : loaded.plugin.payloads;\n this.sharedPayloads.push(...payloads);\n }\n }\n\n // Call onInit hooks\n await this.callHook(\"onInit\", (hook, ctx) => hook(ctx));\n\n this.initialized = true;\n }\n\n /**\n * Destroy all plugins (call onDestroy hooks)\n */\n async destroy(): Promise<void> {\n await this.callHook(\"onDestroy\", (hook, ctx) => hook(ctx));\n this.plugins = [];\n this.sharedPayloads = [];\n this.sharedFindings = [];\n this.initialized = false;\n }\n\n /**\n * Get all loaded payloads\n */\n getPayloads(): RuntimePayload[] {\n return this.sharedPayloads;\n }\n\n /**\n * Get all collected findings\n */\n getFindings(): Finding[] {\n return this.sharedFindings;\n }\n\n /**\n * Add a finding (used by detectors)\n */\n addFinding(finding: Finding): void {\n this.sharedFindings.push(finding);\n }\n\n /**\n * Add payloads (used by loaders)\n */\n addPayloads(payloads: RuntimePayload[]): void {\n this.sharedPayloads.push(...payloads);\n }\n\n /**\n * Clear findings (for new run)\n */\n clearFindings(): void {\n this.sharedFindings = [];\n }\n\n /**\n * Get loaded plugins\n */\n getPlugins(): LoadedPlugin[] {\n return this.plugins;\n }\n\n /**\n * Check if a plugin is loaded by name\n */\n hasPlugin(name: string): boolean {\n return this.plugins.some((p) => p.plugin.name === name);\n }\n\n /**\n * Create base context for plugins\n */\n createContext(pluginConfig: Record<string, unknown>): PluginContext {\n const engineInfo: EngineInfo = {\n version: ENGINE_VERSION,\n pluginApiVersion: PLUGIN_API_VERSION,\n };\n\n return {\n config: pluginConfig,\n engine: engineInfo,\n payloads: this.sharedPayloads,\n findings: this.sharedFindings,\n logger: this.createLogger(\"plugin\"),\n fetch: globalThis.fetch,\n };\n }\n\n /**\n * Create scoped logger for a plugin\n */\n private createLogger(name: string): PluginLogger {\n const prefix = `[${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n\n /**\n * Call a hook on all plugins sequentially\n */\n async callHook<K extends keyof PluginHooks>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<unknown>,\n ): Promise<void> {\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n await executor(hook as NonNullable<PluginHooks[K]>, ctx);\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n }\n\n /**\n * Call a hook and collect results\n */\n async callHookCollect<K extends keyof PluginHooks, R>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<R | R[] | null>,\n ): Promise<R[]> {\n const results: R[] = [];\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n const result = await executor(\n hook as NonNullable<PluginHooks[K]>,\n ctx,\n );\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n results.push(...result);\n } else {\n results.push(result);\n }\n }\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return results;\n }\n\n /**\n * Call a hook that transforms a value through the pipeline\n */\n async callHookPipe<T>(\n hookName: keyof PluginHooks,\n initial: T,\n executor: (\n hook: NonNullable<PluginHooks[typeof hookName]>,\n value: T,\n ctx: PluginContext,\n ) => Promise<T>,\n ): Promise<T> {\n let value = initial;\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n value = await executor(\n hook as NonNullable<PluginHooks[typeof hookName]>,\n value,\n ctx,\n );\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return value;\n }\n}\n\n/**\n * Default shared plugin manager instance\n */\nexport const pluginManager = new PluginManager();\n","/**\n * Vulcn Plugin System Types\n * @module @vulcn/engine/plugin\n *\n * The plugin system is driver-agnostic. Detection plugins receive\n * a generic page interface rather than Playwright types directly.\n * This allows the same plugin to work across different driver types.\n */\n\nimport type { z } from \"zod\";\nimport type { Session, Step } from \"./driver-types\";\nimport type { Finding } from \"./types\";\nimport type { RunResult } from \"./driver-types\";\nimport type { RuntimePayload, PayloadCategory } from \"./payload-types\";\n\n// Re-export for plugin authors\nexport type {\n Session,\n Step,\n Finding,\n RunResult,\n RuntimePayload,\n PayloadCategory,\n};\n\n/**\n * Plugin API version - plugins declare compatibility\n */\nexport const PLUGIN_API_VERSION = 1;\n\n/**\n * Plugin source types for identification\n */\nexport type PluginSource = \"builtin\" | \"npm\" | \"local\" | \"custom\";\n\n/**\n * Main plugin interface\n */\nexport interface VulcnPlugin {\n /** Unique plugin name (e.g., \"@vulcn/plugin-payloads\") */\n name: string;\n\n /** Plugin version (semver) */\n version: string;\n\n /** Plugin API version this plugin targets */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Lifecycle hooks */\n hooks?: PluginHooks;\n\n /**\n * Payloads provided by this plugin (Loaders)\n * Can be static array or async function for lazy loading\n */\n payloads?: RuntimePayload[] | (() => Promise<RuntimePayload[]>);\n\n /**\n * Zod schema for plugin configuration validation\n */\n configSchema?: z.ZodSchema;\n}\n\n/**\n * Plugin lifecycle hooks\n *\n * Detection hooks (onDialog, onConsoleMessage, etc.) receive\n * Playwright types from the driver. Plugins that use these\n * should declare playwright as a peer/dev dependency.\n */\nexport interface PluginHooks {\n // ─────────────────────────────────────────────────────────────────\n // Initialization\n // ─────────────────────────────────────────────────────────────────\n\n /**\n * Called when plugin is loaded, before any operation\n * Use for setup, loading payloads, etc.\n */\n onInit?: (ctx: PluginContext) => Promise<void>;\n\n /**\n * Called when plugin is unloaded/cleanup\n */\n onDestroy?: (ctx: PluginContext) => Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────\n // Recording Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when recording starts */\n onRecordStart?: (ctx: RecordContext) => Promise<void>;\n\n /** Called for each recorded step, can transform */\n onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;\n\n /** Called when recording ends, can transform session */\n onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;\n\n // ─────────────────────────────────────────────────────────────────\n // Running Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when run starts */\n onRunStart?: (ctx: RunContext) => Promise<void>;\n\n /** Called before each payload is injected, can transform payload */\n onBeforePayload?: (\n payload: string,\n step: Step,\n ctx: RunContext,\n ) => Promise<string>;\n\n /** Called after payload injection, for detection */\n onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called when run ends, can transform results */\n onRunEnd?: (result: RunResult, ctx: RunContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Browser Event Hooks (Detection)\n // These receive driver-specific types (e.g. Playwright's Dialog)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when JavaScript alert/confirm/prompt appears */\n onDialog?: (dialog: unknown, ctx: DetectContext) => Promise<Finding | null>;\n\n /** Called on console.log/warn/error */\n onConsoleMessage?: (\n msg: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on page load/navigation */\n onPageLoad?: (page: unknown, ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called on network request */\n onNetworkRequest?: (\n request: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on network response */\n onNetworkResponse?: (\n response: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n}\n\n/**\n * Logger interface for plugins\n */\nexport interface PluginLogger {\n debug: (msg: string, ...args: unknown[]) => void;\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n error: (msg: string, ...args: unknown[]) => void;\n}\n\n/**\n * Engine information exposed to plugins\n */\nexport interface EngineInfo {\n version: string;\n pluginApiVersion: number;\n}\n\n/**\n * Base context available to all plugin hooks\n */\nexport interface PluginContext {\n /** Plugin-specific config from vulcn.config.yml */\n config: Record<string, unknown>;\n\n /** Engine information */\n engine: EngineInfo;\n\n /** Shared payload registry - loaders add payloads here */\n payloads: RuntimePayload[];\n\n /** Shared findings collection - detectors add findings here */\n findings: Finding[];\n\n /** Scoped logger */\n logger: PluginLogger;\n\n /** Fetch API for network requests */\n fetch: typeof fetch;\n}\n\n/**\n * Context for recording phase hooks\n */\nexport interface RecordContext extends PluginContext {\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n}\n\n/**\n * Context for running phase hooks\n */\nexport interface RunContext extends PluginContext {\n /** Session being executed */\n session: Session;\n\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n\n /** Whether running headless */\n headless: boolean;\n}\n\n/**\n * Context for detection hooks\n */\nexport interface DetectContext extends RunContext {\n /** Current step being tested */\n step: Step;\n\n /** Current payload set being tested */\n payloadSet: RuntimePayload;\n\n /** Actual payload value injected */\n payloadValue: string;\n\n /** Step ID for reporting */\n stepId: string;\n}\n\n/**\n * Plugin configuration in vulcn.config.yml\n */\nexport interface PluginConfig {\n /** Plugin name/path */\n name: string;\n\n /** Plugin-specific configuration */\n config?: Record<string, unknown>;\n\n /** Whether plugin is enabled (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Vulcn configuration file schema\n */\nexport interface VulcnConfig {\n /** Config version */\n version: string;\n\n /** Plugins to load */\n plugins?: PluginConfig[];\n\n /** Global settings */\n settings?: {\n headless?: boolean;\n timeout?: number;\n };\n}\n\n/**\n * Loaded plugin instance with resolved config\n */\nexport interface LoadedPlugin {\n /** Plugin definition */\n plugin: VulcnPlugin;\n\n /** Resolved configuration */\n config: Record<string, unknown>;\n\n /** Source of the plugin */\n source: PluginSource;\n\n /** Whether plugin is enabled */\n enabled: boolean;\n}\n"],"mappings":"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","names":["module","pluginManager","import_node_path","import_yaml","YAML","module"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/driver-manager.ts","../src/driver-types.ts","../src/plugin-manager.ts","../src/plugin-types.ts"],"sourcesContent":["/**\n * @vulcn/engine - Core security testing engine\n *\n * v0.3.0: Driver-based architecture\n *\n * The engine now provides:\n * - Driver system for different recording targets (browser, api, cli)\n * - Plugin system for payloads and detection\n * - Generic session format\n *\n * Drivers handle:\n * - Recording interactions (RecorderDriver)\n * - Replaying with payload injection (RunnerDriver)\n *\n * Plugins handle:\n * - Payload loading (builtin, payloadbox, custom files)\n * - Vulnerability detection (reflection, execution, etc.)\n * - Reporting (JSON, SARIF, HTML)\n */\n\n// ============================================================================\n// Driver System\n// ============================================================================\n\nexport { DriverManager, driverManager } from \"./driver-manager\";\nexport { DRIVER_API_VERSION } from \"./driver-types\";\nexport type {\n VulcnDriver,\n RecorderDriver,\n RunnerDriver,\n RecordingHandle,\n RecordOptions,\n CrawlOptions,\n RunOptions,\n RunResult,\n RunContext,\n Session,\n Step,\n DriverLogger,\n LoadedDriver,\n DriverSource,\n} from \"./driver-types\";\n\n// ============================================================================\n// Plugin System\n// ============================================================================\n\nexport { PluginManager, pluginManager } from \"./plugin-manager\";\nexport { PLUGIN_API_VERSION } from \"./plugin-types\";\nexport type {\n VulcnPlugin,\n VulcnConfig,\n PluginConfig,\n PluginHooks,\n PluginContext,\n RecordContext,\n RunContext as PluginRunContext,\n DetectContext,\n LoadedPlugin as LoadedPluginInfo,\n PluginLogger,\n EngineInfo,\n PluginSource,\n} from \"./plugin-types\";\n\n// ============================================================================\n// Payload Types\n// ============================================================================\n\nexport type {\n PayloadCategory,\n PayloadSource,\n RuntimePayload,\n CustomPayload,\n CustomPayloadFile,\n} from \"./payload-types\";\n\n// ============================================================================\n// Core Types\n// ============================================================================\n\nexport type { Finding } from \"./types\";\n","/**\n * Vulcn Driver Manager\n *\n * Handles driver loading, registration, and lifecycle.\n * Drivers are loaded from npm packages or local files.\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { parse, stringify } from \"yaml\";\nimport type {\n VulcnDriver,\n LoadedDriver,\n DriverSource,\n Session,\n Step,\n RunContext,\n RunResult,\n RunOptions,\n RecordOptions,\n CrawlOptions,\n RecordingHandle,\n DriverLogger,\n DRIVER_API_VERSION,\n} from \"./driver-types\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n/**\n * Driver Manager - loads and manages recording/running drivers\n */\nexport class DriverManager {\n private drivers: Map<string, LoadedDriver> = new Map();\n private defaultDriver: string | null = null;\n\n /**\n * Register a driver\n */\n register(driver: VulcnDriver, source: DriverSource = \"builtin\"): void {\n this.validateDriver(driver);\n this.drivers.set(driver.name, { driver, source });\n\n // First registered driver becomes default\n if (this.drivers.size === 1) {\n this.defaultDriver = driver.name;\n }\n }\n\n /**\n * Load a driver from npm or local path\n */\n async load(nameOrPath: string): Promise<void> {\n let driver: VulcnDriver;\n let source: DriverSource;\n\n if (\n nameOrPath.startsWith(\"./\") ||\n nameOrPath.startsWith(\"../\") ||\n isAbsolute(nameOrPath)\n ) {\n // Local file\n const resolved = isAbsolute(nameOrPath)\n ? nameOrPath\n : resolve(process.cwd(), nameOrPath);\n const module = await import(resolved);\n driver = module.default || module;\n source = \"local\";\n } else {\n // npm package\n const module = await import(nameOrPath);\n driver = module.default || module;\n source = \"npm\";\n }\n\n this.register(driver, source);\n }\n\n /**\n * Get a loaded driver by name\n */\n get(name: string): VulcnDriver | undefined {\n return this.drivers.get(name)?.driver;\n }\n\n /**\n * Get the default driver\n */\n getDefault(): VulcnDriver | undefined {\n if (!this.defaultDriver) return undefined;\n return this.get(this.defaultDriver);\n }\n\n /**\n * Set the default driver\n */\n setDefault(name: string): void {\n if (!this.drivers.has(name)) {\n throw new Error(`Driver \"${name}\" is not registered`);\n }\n this.defaultDriver = name;\n }\n\n /**\n * Check if a driver is registered\n */\n has(name: string): boolean {\n return this.drivers.has(name);\n }\n\n /**\n * Get all registered drivers\n */\n list(): LoadedDriver[] {\n return Array.from(this.drivers.values());\n }\n\n /**\n * Get driver for a session\n */\n getForSession(session: Session): VulcnDriver {\n const driverName = session.driver;\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(\n `Driver \"${driverName}\" not found. Install @vulcn/driver-${driverName} or load it manually.`,\n );\n }\n\n return driver;\n }\n\n /**\n * Parse a YAML session string into a Session object.\n *\n * Handles both new driver-format sessions and legacy v1 sessions.\n * Legacy sessions (those with non-namespaced step types like \"click\",\n * \"input\", \"navigate\") are automatically converted to the driver format\n * (e.g., \"browser.click\", \"browser.input\", \"browser.navigate\").\n *\n * @param yaml - Raw YAML string\n * @param defaultDriver - Driver to assign for legacy sessions (default: \"browser\")\n */\n parseSession(yaml: string, defaultDriver = \"browser\"): Session {\n const data = parse(yaml) as Record<string, unknown>;\n\n // Already in driver format — has a `driver` field\n if (data.driver && typeof data.driver === \"string\") {\n return data as unknown as Session;\n }\n\n // Legacy format — convert to driver session\n const steps = (data.steps as Array<Record<string, unknown>>) ?? [];\n const convertedSteps: Step[] = steps.map((step) => {\n const type = step.type as string;\n\n // If step type is already namespaced (e.g. \"browser.click\"), keep it\n if (type.includes(\".\")) {\n return step as unknown as Step;\n }\n\n // Convert legacy type → namespaced type\n return {\n ...step,\n type: `${defaultDriver}.${type}`,\n } as unknown as Step;\n });\n\n return {\n name: (data.name as string) ?? \"Untitled Session\",\n driver: defaultDriver,\n driverConfig: {\n browser: data.browser ?? \"chromium\",\n viewport: data.viewport ?? { width: 1280, height: 720 },\n startUrl: data.startUrl as string,\n },\n steps: convertedSteps,\n metadata: {\n recordedAt: data.recordedAt as string,\n version: (data.version as string) ?? \"1\",\n },\n };\n }\n\n /**\n * Start recording with a driver\n */\n async startRecording(\n driverName: string,\n config: Record<string, unknown>,\n options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n return driver.recorder.start(config, options);\n }\n\n /**\n * Auto-crawl a URL using a driver.\n *\n * Uses the driver's optional crawl() method to automatically\n * discover forms and injection points, returning Session[] that\n * can be passed to execute().\n *\n * Not all drivers support this — only browser has crawl capability.\n * CLI and API drivers will throw.\n */\n async crawl(\n driverName: string,\n config: Record<string, unknown>,\n options: CrawlOptions = {},\n ): Promise<Session[]> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n if (!driver.recorder.crawl) {\n throw new Error(\n `Driver \"${driverName}\" does not support auto-crawl. Use manual recording instead.`,\n );\n }\n\n return driver.recorder.crawl(config, options);\n }\n\n /**\n * Execute a session\n * Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.\n * Plugin onRunStart is deferred until the driver signals the page is ready\n * via the onPageReady callback, ensuring plugins get a real page object.\n */\n async execute(\n session: Session,\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<RunResult> {\n const driver = this.getForSession(session);\n const findings: Finding[] = [];\n const logger = this.createLogger(driver.name);\n\n // Shared addFinding function — used by both internal RunContext and\n // plugin context. Ensures all findings (active + passive) flow through\n // the onFinding callback so consumers get notified consistently.\n const addFinding = (finding: Finding) => {\n findings.push(finding);\n pluginManager.addFinding(finding);\n options.onFinding?.(finding);\n };\n\n // Build a plugin context template for hooks (page is set in onPageReady)\n const pluginCtx = {\n session,\n page: null as unknown,\n headless: !!(options as Record<string, unknown>).headless,\n config: {} as Record<string, unknown>,\n engine: { version: \"0.3.0\", pluginApiVersion: 1 },\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n fetch: globalThis.fetch,\n };\n\n const ctx: RunContext = {\n session,\n pluginManager,\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n options: {\n ...options,\n // Provide onPageReady callback — fires plugin onRunStart hooks\n // with the real page object once the driver has created it\n onPageReady: async (page: unknown) => {\n pluginCtx.page = page;\n\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {\n try {\n await loaded.plugin.hooks.onRunStart({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(\n `Plugin ${loaded.plugin.name} onRunStart failed: ${err}`,\n );\n }\n }\n }\n },\n // Fires before browser closes — lets plugins flush pending async work\n onBeforeClose: async (_page: unknown) => {\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onBeforeClose) {\n try {\n await loaded.plugin.hooks.onBeforeClose({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(\n `Plugin ${loaded.plugin.name} onBeforeClose failed: ${err}`,\n );\n }\n }\n }\n },\n },\n };\n\n // Execute via driver runner\n // (runner calls ctx.options.onPageReady(page) after creating the page)\n let result = await driver.runner.execute(session, ctx);\n\n // Call onRunEnd hooks (e.g., report generation)\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {\n try {\n result = await loaded.plugin.hooks.onRunEnd(result, {\n ...pluginCtx,\n config: loaded.config,\n findings: result.findings,\n });\n } catch (err) {\n logger.warn(`Plugin ${loaded.plugin.name} onRunEnd failed: ${err}`);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Validate driver structure\n */\n private validateDriver(driver: unknown): asserts driver is VulcnDriver {\n if (!driver || typeof driver !== \"object\") {\n throw new Error(\"Driver must be an object\");\n }\n\n const d = driver as Record<string, unknown>;\n\n if (typeof d.name !== \"string\" || !d.name) {\n throw new Error(\"Driver must have a name\");\n }\n\n if (typeof d.version !== \"string\" || !d.version) {\n throw new Error(\"Driver must have a version\");\n }\n\n if (!Array.isArray(d.stepTypes) || d.stepTypes.length === 0) {\n throw new Error(\"Driver must define stepTypes\");\n }\n\n if (!d.recorder || typeof d.recorder !== \"object\") {\n throw new Error(\"Driver must have a recorder\");\n }\n\n if (!d.runner || typeof d.runner !== \"object\") {\n throw new Error(\"Driver must have a runner\");\n }\n }\n\n /**\n * Create a scoped logger for a driver\n */\n private createLogger(name: string): DriverLogger {\n const prefix = `[driver:${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n}\n\n/**\n * Default driver manager instance\n */\nexport const driverManager = new DriverManager();\n","/**\n * Vulcn Driver System\n *\n * Drivers handle recording and running sessions for different targets:\n * - browser: Web applications (Playwright)\n * - api: REST/HTTP APIs\n * - cli: Command-line tools\n *\n * Each driver implements RecorderDriver and RunnerDriver interfaces.\n */\n\nimport type { z } from \"zod\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { PluginManager } from \"./plugin-manager\";\n\n/**\n * Current driver API version\n */\nexport const DRIVER_API_VERSION = 1;\n\n/**\n * Generic step - drivers define their own step types\n */\nexport interface Step {\n /** Unique step ID */\n id: string;\n\n /** Step type (namespaced, e.g., \"browser.click\", \"api.request\") */\n type: string;\n\n /** Timestamp when step was recorded */\n timestamp: number;\n\n /** Step-specific data */\n [key: string]: unknown;\n}\n\n/**\n * Generic session format\n */\nexport interface Session {\n /** Session name */\n name: string;\n\n /** Driver that recorded this session */\n driver: string;\n\n /** Driver-specific configuration */\n driverConfig: Record<string, unknown>;\n\n /** Recorded steps */\n steps: Step[];\n\n /** Session metadata */\n metadata?: {\n recordedAt?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Recording context passed to drivers\n */\nexport interface RecordContext {\n /** Session being built */\n session: Partial<Session>;\n\n /** Add a step to the session */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n\n /** Logger */\n logger: DriverLogger;\n}\n\n/**\n * Running context passed to drivers\n */\nexport interface RunContext {\n /** Session being executed */\n session: Session;\n\n /** Plugin manager for calling hooks */\n pluginManager: PluginManager;\n\n /** Available payloads */\n payloads: RuntimePayload[];\n\n /** Collected findings */\n findings: Finding[];\n\n /** Add a finding */\n addFinding(finding: Finding): void;\n\n /** Logger */\n logger: DriverLogger;\n\n /** Running options */\n options: RunOptions;\n}\n\n/**\n * Options for recording\n */\nexport interface RecordOptions {\n /** Enable auto-crawl mode (driver discovers forms automatically) */\n auto?: boolean;\n\n /** Crawl options (only used when auto=true) */\n crawlOptions?: CrawlOptions;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Options for auto-crawl mode\n *\n * When a driver supports crawling, these options control how\n * the automated discovery works. Not all drivers support crawling —\n * it's optional and primarily used by the browser driver.\n */\nexport interface CrawlOptions {\n /** Maximum crawl depth (0 = only the given URL, default: 2) */\n maxDepth?: number;\n\n /** Maximum number of pages to visit (default: 20) */\n maxPages?: number;\n\n /** Timeout per page navigation in ms (default: 10000) */\n pageTimeout?: number;\n\n /** Only crawl pages under the same origin (default: true) */\n sameOrigin?: boolean;\n\n /** Callback when a page is crawled */\n onPageCrawled?: (url: string, formsFound: number) => void;\n}\n\n/**\n * Options for running\n */\nexport interface RunOptions {\n /** Run headless (for visual drivers) */\n headless?: boolean;\n\n /** Callback for findings */\n onFinding?: (finding: Finding) => void;\n\n /** Callback for step completion */\n onStepComplete?: (stepId: string, payloadCount: number) => void;\n\n /**\n * Called by the driver runner after the page/environment is ready.\n * The driver-manager uses this to fire plugin onRunStart hooks\n * with the real page object (instead of null).\n */\n onPageReady?: (page: unknown) => Promise<void>;\n\n /**\n * Called by the driver runner before closing the browser/environment.\n * The driver-manager uses this to fire plugin onBeforeClose hooks\n * so plugins can flush pending async work.\n */\n onBeforeClose?: (page: unknown) => Promise<void>;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Run result\n */\nexport interface RunResult {\n /** All findings */\n findings: Finding[];\n\n /** Steps executed */\n stepsExecuted: number;\n\n /** Payloads tested */\n payloadsTested: number;\n\n /** Duration in milliseconds */\n duration: number;\n\n /** Errors encountered */\n errors: string[];\n}\n\n/**\n * Driver logger\n */\nexport interface DriverLogger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\n/**\n * Recorder Driver Interface\n *\n * Implement this to add recording support for a target type.\n */\nexport interface RecorderDriver {\n /** Start recording and return control handle */\n start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle>;\n\n /**\n * Auto-crawl a URL and generate sessions.\n *\n * Optional — only drivers that support automated discovery\n * (e.g., browser) implement this. CLI and API drivers do not.\n *\n * When options.auto=true is passed to startRecording, the engine\n * calls this instead of start().\n */\n crawl?(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]>;\n}\n\n/**\n * Handle returned by RecorderDriver.start()\n */\nexport interface RecordingHandle {\n /** Stop recording and return the session */\n stop(): Promise<Session>;\n\n /** Abort recording without saving */\n abort(): Promise<void>;\n\n /** Get current steps (during recording) */\n getSteps(): Step[];\n\n /** Manually add a step */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n}\n\n/**\n * Runner Driver Interface\n *\n * Implement this to add running/replay support for a target type.\n */\nexport interface RunnerDriver {\n /** Execute a session with payloads */\n execute(session: Session, ctx: RunContext): Promise<RunResult>;\n}\n\n/**\n * Complete driver definition\n */\nexport interface VulcnDriver {\n /** Unique driver name (e.g., \"browser\", \"api\", \"cli\") */\n name: string;\n\n /** Driver version */\n version: string;\n\n /** Driver API version */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Configuration schema (Zod) */\n configSchema?: z.ZodSchema;\n\n /** Step types this driver handles */\n stepTypes: string[];\n\n /** Recorder implementation */\n recorder: RecorderDriver;\n\n /** Runner implementation */\n runner: RunnerDriver;\n}\n\n/**\n * Driver source for loading\n */\nexport type DriverSource = \"npm\" | \"local\" | \"builtin\";\n\n/**\n * Loaded driver with metadata\n */\nexport interface LoadedDriver {\n driver: VulcnDriver;\n source: DriverSource;\n}\n","/**\n * Vulcn Plugin Manager\n * Handles plugin loading, lifecycle, and hook execution\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve, isAbsolute } from \"node:path\";\nimport YAML from \"yaml\";\nimport { z } from \"zod\";\nimport type {\n VulcnPlugin,\n VulcnConfig,\n PluginConfig,\n LoadedPlugin,\n PluginContext,\n PluginSource,\n PluginLogger,\n EngineInfo,\n PluginHooks,\n} from \"./plugin-types\";\nimport { PLUGIN_API_VERSION } from \"./plugin-types\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n// Package version (injected at build time or read from package.json)\nconst ENGINE_VERSION = \"0.2.0\";\n\n/**\n * Config file schema\n */\nconst VulcnConfigSchema = z.object({\n version: z.string().default(\"1\"),\n plugins: z\n .array(\n z.object({\n name: z.string(),\n config: z.record(z.unknown()).optional(),\n enabled: z.boolean().default(true),\n }),\n )\n .optional(),\n settings: z\n .object({\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).optional(),\n headless: z.boolean().optional(),\n timeout: z.number().optional(),\n })\n .optional(),\n});\n\n/**\n * Plugin Manager - loads, configures, and orchestrates plugins\n */\nexport class PluginManager {\n private plugins: LoadedPlugin[] = [];\n private config: VulcnConfig | null = null;\n private initialized = false;\n\n /**\n * Shared context passed to all plugins\n */\n private sharedPayloads: RuntimePayload[] = [];\n private sharedFindings: Finding[] = [];\n\n /**\n * Load configuration from vulcn.config.yml\n */\n async loadConfig(configPath?: string): Promise<VulcnConfig> {\n const paths = configPath\n ? [configPath]\n : [\n \"vulcn.config.yml\",\n \"vulcn.config.yaml\",\n \"vulcn.config.json\",\n \".vulcnrc.yml\",\n \".vulcnrc.yaml\",\n \".vulcnrc.json\",\n ];\n\n for (const path of paths) {\n const resolved = isAbsolute(path) ? path : resolve(process.cwd(), path);\n if (existsSync(resolved)) {\n const content = await readFile(resolved, \"utf-8\");\n const parsed = path.endsWith(\".json\")\n ? JSON.parse(content)\n : YAML.parse(content);\n this.config = VulcnConfigSchema.parse(parsed);\n return this.config;\n }\n }\n\n // No config file - use defaults\n this.config = { version: \"1\", plugins: [], settings: {} };\n return this.config;\n }\n\n /**\n * Load all plugins from config\n */\n async loadPlugins(): Promise<void> {\n if (!this.config) {\n await this.loadConfig();\n }\n\n const pluginConfigs = this.config?.plugins || [];\n\n for (const pluginConfig of pluginConfigs) {\n if (pluginConfig.enabled === false) continue;\n\n try {\n const loaded = await this.loadPlugin(pluginConfig);\n this.plugins.push(loaded);\n } catch (err) {\n console.error(\n `Failed to load plugin ${pluginConfig.name}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n /**\n * Load a single plugin\n */\n private async loadPlugin(config: PluginConfig): Promise<LoadedPlugin> {\n const { name, config: pluginConfig = {} } = config;\n let plugin: VulcnPlugin;\n let source: PluginSource;\n\n // Determine plugin source and load\n if (name.startsWith(\"./\") || name.startsWith(\"../\") || isAbsolute(name)) {\n // Local file plugin\n const resolved = isAbsolute(name) ? name : resolve(process.cwd(), name);\n const module = await import(resolved);\n plugin = module.default || module;\n source = \"local\";\n } else if (name.startsWith(\"@vulcn/\")) {\n // Official plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n } else {\n // Community plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n }\n\n // Validate plugin structure\n this.validatePlugin(plugin);\n\n // Validate plugin config if schema provided\n let resolvedConfig = pluginConfig;\n if (plugin.configSchema) {\n try {\n resolvedConfig = plugin.configSchema.parse(pluginConfig);\n } catch (err) {\n throw new Error(\n `Invalid config for plugin ${name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return {\n plugin,\n config: resolvedConfig,\n source,\n enabled: true,\n };\n }\n\n /**\n * Validate plugin structure\n */\n private validatePlugin(plugin: unknown): asserts plugin is VulcnPlugin {\n if (!plugin || typeof plugin !== \"object\") {\n throw new Error(\"Plugin must be an object\");\n }\n\n const p = plugin as Record<string, unknown>;\n if (typeof p.name !== \"string\" || !p.name) {\n throw new Error(\"Plugin must have a name\");\n }\n if (typeof p.version !== \"string\" || !p.version) {\n throw new Error(\"Plugin must have a version\");\n }\n\n // Check API version compatibility\n const apiVersion = (p.apiVersion as number) || 1;\n if (apiVersion > PLUGIN_API_VERSION) {\n throw new Error(\n `Plugin requires API version ${apiVersion}, but engine supports ${PLUGIN_API_VERSION}`,\n );\n }\n }\n\n /**\n * Add a plugin programmatically (for testing or dynamic loading)\n */\n addPlugin(plugin: VulcnPlugin, config: Record<string, unknown> = {}): void {\n this.validatePlugin(plugin);\n this.plugins.push({\n plugin,\n config,\n source: \"custom\",\n enabled: true,\n });\n }\n\n /**\n * Initialize all plugins (call onInit hooks)\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Load payloads from plugins that provide them\n for (const loaded of this.plugins) {\n if (loaded.plugin.payloads) {\n const payloads =\n typeof loaded.plugin.payloads === \"function\"\n ? await loaded.plugin.payloads()\n : loaded.plugin.payloads;\n this.sharedPayloads.push(...payloads);\n }\n }\n\n // Call onInit hooks\n await this.callHook(\"onInit\", (hook, ctx) => hook(ctx));\n\n this.initialized = true;\n }\n\n /**\n * Destroy all plugins (call onDestroy hooks)\n */\n async destroy(): Promise<void> {\n await this.callHook(\"onDestroy\", (hook, ctx) => hook(ctx));\n this.plugins = [];\n this.sharedPayloads = [];\n this.sharedFindings = [];\n this.initialized = false;\n }\n\n /**\n * Get all loaded payloads\n */\n getPayloads(): RuntimePayload[] {\n return this.sharedPayloads;\n }\n\n /**\n * Get all collected findings\n */\n getFindings(): Finding[] {\n return this.sharedFindings;\n }\n\n /**\n * Add a finding (used by detectors)\n */\n addFinding(finding: Finding): void {\n this.sharedFindings.push(finding);\n }\n\n /**\n * Add payloads (used by loaders)\n */\n addPayloads(payloads: RuntimePayload[]): void {\n this.sharedPayloads.push(...payloads);\n }\n\n /**\n * Clear findings (for new run)\n */\n clearFindings(): void {\n this.sharedFindings = [];\n }\n\n /**\n * Get loaded plugins\n */\n getPlugins(): LoadedPlugin[] {\n return this.plugins;\n }\n\n /**\n * Check if a plugin is loaded by name\n */\n hasPlugin(name: string): boolean {\n return this.plugins.some((p) => p.plugin.name === name);\n }\n\n /**\n * Create base context for plugins\n */\n createContext(pluginConfig: Record<string, unknown>): PluginContext {\n const engineInfo: EngineInfo = {\n version: ENGINE_VERSION,\n pluginApiVersion: PLUGIN_API_VERSION,\n };\n\n return {\n config: pluginConfig,\n engine: engineInfo,\n payloads: this.sharedPayloads,\n findings: this.sharedFindings,\n addFinding: (finding: Finding) => {\n this.sharedFindings.push(finding);\n },\n logger: this.createLogger(\"plugin\"),\n fetch: globalThis.fetch,\n };\n }\n\n /**\n * Create scoped logger for a plugin\n */\n private createLogger(name: string): PluginLogger {\n const prefix = `[${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n\n /**\n * Call a hook on all plugins sequentially\n */\n async callHook<K extends keyof PluginHooks>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<unknown>,\n ): Promise<void> {\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n await executor(hook as NonNullable<PluginHooks[K]>, ctx);\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n }\n\n /**\n * Call a hook and collect results\n */\n async callHookCollect<K extends keyof PluginHooks, R>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<R | R[] | null>,\n ): Promise<R[]> {\n const results: R[] = [];\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n const result = await executor(\n hook as NonNullable<PluginHooks[K]>,\n ctx,\n );\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n results.push(...result);\n } else {\n results.push(result);\n }\n }\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return results;\n }\n\n /**\n * Call a hook that transforms a value through the pipeline\n */\n async callHookPipe<T>(\n hookName: keyof PluginHooks,\n initial: T,\n executor: (\n hook: NonNullable<PluginHooks[typeof hookName]>,\n value: T,\n ctx: PluginContext,\n ) => Promise<T>,\n ): Promise<T> {\n let value = initial;\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n value = await executor(\n hook as NonNullable<PluginHooks[typeof hookName]>,\n value,\n ctx,\n );\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return value;\n }\n}\n\n/**\n * Default shared plugin manager instance\n */\nexport const pluginManager = new PluginManager();\n","/**\n * Vulcn Plugin System Types\n * @module @vulcn/engine/plugin\n *\n * The plugin system is driver-agnostic. Detection plugins receive\n * a generic page interface rather than Playwright types directly.\n * This allows the same plugin to work across different driver types.\n */\n\nimport type { z } from \"zod\";\nimport type { Session, Step } from \"./driver-types\";\nimport type { Finding } from \"./types\";\nimport type { RunResult } from \"./driver-types\";\nimport type { RuntimePayload, PayloadCategory } from \"./payload-types\";\n\n// Re-export for plugin authors\nexport type {\n Session,\n Step,\n Finding,\n RunResult,\n RuntimePayload,\n PayloadCategory,\n};\n\n/**\n * Plugin API version - plugins declare compatibility\n */\nexport const PLUGIN_API_VERSION = 1;\n\n/**\n * Plugin source types for identification\n */\nexport type PluginSource = \"builtin\" | \"npm\" | \"local\" | \"custom\";\n\n/**\n * Main plugin interface\n */\nexport interface VulcnPlugin {\n /** Unique plugin name (e.g., \"@vulcn/plugin-payloads\") */\n name: string;\n\n /** Plugin version (semver) */\n version: string;\n\n /** Plugin API version this plugin targets */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Lifecycle hooks */\n hooks?: PluginHooks;\n\n /**\n * Payloads provided by this plugin (Loaders)\n * Can be static array or async function for lazy loading\n */\n payloads?: RuntimePayload[] | (() => Promise<RuntimePayload[]>);\n\n /**\n * Zod schema for plugin configuration validation\n */\n configSchema?: z.ZodSchema;\n}\n\n/**\n * Plugin lifecycle hooks\n *\n * Detection hooks (onDialog, onConsoleMessage, etc.) receive\n * Playwright types from the driver. Plugins that use these\n * should declare playwright as a peer/dev dependency.\n */\nexport interface PluginHooks {\n // ─────────────────────────────────────────────────────────────────\n // Initialization\n // ─────────────────────────────────────────────────────────────────\n\n /**\n * Called when plugin is loaded, before any operation\n * Use for setup, loading payloads, etc.\n */\n onInit?: (ctx: PluginContext) => Promise<void>;\n\n /**\n * Called when plugin is unloaded/cleanup\n */\n onDestroy?: (ctx: PluginContext) => Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────\n // Recording Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when recording starts */\n onRecordStart?: (ctx: RecordContext) => Promise<void>;\n\n /** Called for each recorded step, can transform */\n onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;\n\n /** Called when recording ends, can transform session */\n onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;\n\n // ─────────────────────────────────────────────────────────────────\n // Running Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when run starts */\n onRunStart?: (ctx: RunContext) => Promise<void>;\n\n /** Called before each payload is injected, can transform payload */\n onBeforePayload?: (\n payload: string,\n step: Step,\n ctx: RunContext,\n ) => Promise<string>;\n\n /** Called after payload injection, for detection */\n onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;\n\n /**\n * Called before the browser/driver is closed.\n * Plugins should await any pending async work here (e.g., flush\n * in-flight response handlers that need browser access).\n */\n onBeforeClose?: (ctx: PluginContext) => Promise<void>;\n\n /** Called when run ends, can transform results */\n onRunEnd?: (result: RunResult, ctx: RunContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Browser Event Hooks (Detection)\n // These receive driver-specific types (e.g. Playwright's Dialog)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when JavaScript alert/confirm/prompt appears */\n onDialog?: (dialog: unknown, ctx: DetectContext) => Promise<Finding | null>;\n\n /** Called on console.log/warn/error */\n onConsoleMessage?: (\n msg: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on page load/navigation */\n onPageLoad?: (page: unknown, ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called on network request */\n onNetworkRequest?: (\n request: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on network response */\n onNetworkResponse?: (\n response: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n}\n\n/**\n * Logger interface for plugins\n */\nexport interface PluginLogger {\n debug: (msg: string, ...args: unknown[]) => void;\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n error: (msg: string, ...args: unknown[]) => void;\n}\n\n/**\n * Engine information exposed to plugins\n */\nexport interface EngineInfo {\n version: string;\n pluginApiVersion: number;\n}\n\n/**\n * Base context available to all plugin hooks\n */\nexport interface PluginContext {\n /** Plugin-specific config from vulcn.config.yml */\n config: Record<string, unknown>;\n\n /** Engine information */\n engine: EngineInfo;\n\n /** Shared payload registry - loaders add payloads here */\n payloads: RuntimePayload[];\n\n /** Shared findings collection (read-only view, use addFinding to add) */\n findings: Finding[];\n\n /**\n * Add a finding through the proper callback chain.\n * Plugins should use this instead of pushing to findings[] directly,\n * so consumers (CLI, worker) get notified via onFinding callbacks.\n */\n addFinding: (finding: Finding) => void;\n\n /** Scoped logger */\n logger: PluginLogger;\n\n /** Fetch API for network requests */\n fetch: typeof fetch;\n}\n\n/**\n * Context for recording phase hooks\n */\nexport interface RecordContext extends PluginContext {\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n}\n\n/**\n * Context for running phase hooks\n */\nexport interface RunContext extends PluginContext {\n /** Session being executed */\n session: Session;\n\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n\n /** Whether running headless */\n headless: boolean;\n}\n\n/**\n * Context for detection hooks\n */\nexport interface DetectContext extends RunContext {\n /** Current step being tested */\n step: Step;\n\n /** Current payload set being tested */\n payloadSet: RuntimePayload;\n\n /** Actual payload value injected */\n payloadValue: string;\n\n /** Step ID for reporting */\n stepId: string;\n}\n\n/**\n * Plugin configuration in vulcn.config.yml\n */\nexport interface PluginConfig {\n /** Plugin name/path */\n name: string;\n\n /** Plugin-specific configuration */\n config?: Record<string, unknown>;\n\n /** Whether plugin is enabled (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Vulcn configuration file schema\n */\nexport interface VulcnConfig {\n /** Config version */\n version: string;\n\n /** Plugins to load */\n plugins?: PluginConfig[];\n\n /** Global settings */\n settings?: {\n headless?: boolean;\n timeout?: number;\n };\n}\n\n/**\n * Loaded plugin instance with resolved config\n */\nexport interface LoadedPlugin {\n /** Plugin definition */\n plugin: VulcnPlugin;\n\n /** Resolved configuration */\n config: Record<string, unknown>;\n\n /** Source of the plugin */\n source: PluginSource;\n\n /** Whether plugin is enabled */\n enabled: boolean;\n}\n"],"mappings":"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","names":["module","pluginManager","import_node_path","import_yaml","YAML","module"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
/**
|
|
8
8
|
* Valid payload categories
|
|
9
9
|
*/
|
|
10
|
-
type PayloadCategory = "xss" | "sqli" | "ssrf" | "xxe" | "command-injection" | "path-traversal" | "open-redirect" | "reflection" | "custom";
|
|
10
|
+
type PayloadCategory = "xss" | "sqli" | "ssrf" | "xxe" | "command-injection" | "path-traversal" | "open-redirect" | "reflection" | "security-misconfiguration" | "information-disclosure" | "custom";
|
|
11
11
|
/**
|
|
12
12
|
* Payload source types
|
|
13
13
|
*/
|
|
@@ -130,6 +130,12 @@ interface PluginHooks {
|
|
|
130
130
|
onBeforePayload?: (payload: string, step: Step, ctx: RunContext$1) => Promise<string>;
|
|
131
131
|
/** Called after payload injection, for detection */
|
|
132
132
|
onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;
|
|
133
|
+
/**
|
|
134
|
+
* Called before the browser/driver is closed.
|
|
135
|
+
* Plugins should await any pending async work here (e.g., flush
|
|
136
|
+
* in-flight response handlers that need browser access).
|
|
137
|
+
*/
|
|
138
|
+
onBeforeClose?: (ctx: PluginContext) => Promise<void>;
|
|
133
139
|
/** Called when run ends, can transform results */
|
|
134
140
|
onRunEnd?: (result: RunResult, ctx: RunContext$1) => Promise<RunResult>;
|
|
135
141
|
/** Called when JavaScript alert/confirm/prompt appears */
|
|
@@ -169,8 +175,14 @@ interface PluginContext {
|
|
|
169
175
|
engine: EngineInfo;
|
|
170
176
|
/** Shared payload registry - loaders add payloads here */
|
|
171
177
|
payloads: RuntimePayload[];
|
|
172
|
-
/** Shared findings collection -
|
|
178
|
+
/** Shared findings collection (read-only view, use addFinding to add) */
|
|
173
179
|
findings: Finding[];
|
|
180
|
+
/**
|
|
181
|
+
* Add a finding through the proper callback chain.
|
|
182
|
+
* Plugins should use this instead of pushing to findings[] directly,
|
|
183
|
+
* so consumers (CLI, worker) get notified via onFinding callbacks.
|
|
184
|
+
*/
|
|
185
|
+
addFinding: (finding: Finding) => void;
|
|
174
186
|
/** Scoped logger */
|
|
175
187
|
logger: PluginLogger;
|
|
176
188
|
/** Fetch API for network requests */
|
|
@@ -451,6 +463,18 @@ interface RunOptions {
|
|
|
451
463
|
onFinding?: (finding: Finding) => void;
|
|
452
464
|
/** Callback for step completion */
|
|
453
465
|
onStepComplete?: (stepId: string, payloadCount: number) => void;
|
|
466
|
+
/**
|
|
467
|
+
* Called by the driver runner after the page/environment is ready.
|
|
468
|
+
* The driver-manager uses this to fire plugin onRunStart hooks
|
|
469
|
+
* with the real page object (instead of null).
|
|
470
|
+
*/
|
|
471
|
+
onPageReady?: (page: unknown) => Promise<void>;
|
|
472
|
+
/**
|
|
473
|
+
* Called by the driver runner before closing the browser/environment.
|
|
474
|
+
* The driver-manager uses this to fire plugin onBeforeClose hooks
|
|
475
|
+
* so plugins can flush pending async work.
|
|
476
|
+
*/
|
|
477
|
+
onBeforeClose?: (page: unknown) => Promise<void>;
|
|
454
478
|
/** Driver-specific options */
|
|
455
479
|
[key: string]: unknown;
|
|
456
480
|
}
|
|
@@ -627,6 +651,8 @@ declare class DriverManager {
|
|
|
627
651
|
/**
|
|
628
652
|
* Execute a session
|
|
629
653
|
* Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.
|
|
654
|
+
* Plugin onRunStart is deferred until the driver signals the page is ready
|
|
655
|
+
* via the onPageReady callback, ensuring plugins get a real page object.
|
|
630
656
|
*/
|
|
631
657
|
execute(session: Session, pluginManager: PluginManager, options?: RunOptions): Promise<RunResult>;
|
|
632
658
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
/**
|
|
8
8
|
* Valid payload categories
|
|
9
9
|
*/
|
|
10
|
-
type PayloadCategory = "xss" | "sqli" | "ssrf" | "xxe" | "command-injection" | "path-traversal" | "open-redirect" | "reflection" | "custom";
|
|
10
|
+
type PayloadCategory = "xss" | "sqli" | "ssrf" | "xxe" | "command-injection" | "path-traversal" | "open-redirect" | "reflection" | "security-misconfiguration" | "information-disclosure" | "custom";
|
|
11
11
|
/**
|
|
12
12
|
* Payload source types
|
|
13
13
|
*/
|
|
@@ -130,6 +130,12 @@ interface PluginHooks {
|
|
|
130
130
|
onBeforePayload?: (payload: string, step: Step, ctx: RunContext$1) => Promise<string>;
|
|
131
131
|
/** Called after payload injection, for detection */
|
|
132
132
|
onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;
|
|
133
|
+
/**
|
|
134
|
+
* Called before the browser/driver is closed.
|
|
135
|
+
* Plugins should await any pending async work here (e.g., flush
|
|
136
|
+
* in-flight response handlers that need browser access).
|
|
137
|
+
*/
|
|
138
|
+
onBeforeClose?: (ctx: PluginContext) => Promise<void>;
|
|
133
139
|
/** Called when run ends, can transform results */
|
|
134
140
|
onRunEnd?: (result: RunResult, ctx: RunContext$1) => Promise<RunResult>;
|
|
135
141
|
/** Called when JavaScript alert/confirm/prompt appears */
|
|
@@ -169,8 +175,14 @@ interface PluginContext {
|
|
|
169
175
|
engine: EngineInfo;
|
|
170
176
|
/** Shared payload registry - loaders add payloads here */
|
|
171
177
|
payloads: RuntimePayload[];
|
|
172
|
-
/** Shared findings collection -
|
|
178
|
+
/** Shared findings collection (read-only view, use addFinding to add) */
|
|
173
179
|
findings: Finding[];
|
|
180
|
+
/**
|
|
181
|
+
* Add a finding through the proper callback chain.
|
|
182
|
+
* Plugins should use this instead of pushing to findings[] directly,
|
|
183
|
+
* so consumers (CLI, worker) get notified via onFinding callbacks.
|
|
184
|
+
*/
|
|
185
|
+
addFinding: (finding: Finding) => void;
|
|
174
186
|
/** Scoped logger */
|
|
175
187
|
logger: PluginLogger;
|
|
176
188
|
/** Fetch API for network requests */
|
|
@@ -451,6 +463,18 @@ interface RunOptions {
|
|
|
451
463
|
onFinding?: (finding: Finding) => void;
|
|
452
464
|
/** Callback for step completion */
|
|
453
465
|
onStepComplete?: (stepId: string, payloadCount: number) => void;
|
|
466
|
+
/**
|
|
467
|
+
* Called by the driver runner after the page/environment is ready.
|
|
468
|
+
* The driver-manager uses this to fire plugin onRunStart hooks
|
|
469
|
+
* with the real page object (instead of null).
|
|
470
|
+
*/
|
|
471
|
+
onPageReady?: (page: unknown) => Promise<void>;
|
|
472
|
+
/**
|
|
473
|
+
* Called by the driver runner before closing the browser/environment.
|
|
474
|
+
* The driver-manager uses this to fire plugin onBeforeClose hooks
|
|
475
|
+
* so plugins can flush pending async work.
|
|
476
|
+
*/
|
|
477
|
+
onBeforeClose?: (page: unknown) => Promise<void>;
|
|
454
478
|
/** Driver-specific options */
|
|
455
479
|
[key: string]: unknown;
|
|
456
480
|
}
|
|
@@ -627,6 +651,8 @@ declare class DriverManager {
|
|
|
627
651
|
/**
|
|
628
652
|
* Execute a session
|
|
629
653
|
* Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.
|
|
654
|
+
* Plugin onRunStart is deferred until the driver signals the page is ready
|
|
655
|
+
* via the onPageReady callback, ensuring plugins get a real page object.
|
|
630
656
|
*/
|
|
631
657
|
execute(session: Session, pluginManager: PluginManager, options?: RunOptions): Promise<RunResult>;
|
|
632
658
|
/**
|
package/dist/index.js
CHANGED
|
@@ -156,23 +156,17 @@ var DriverManager = class {
|
|
|
156
156
|
/**
|
|
157
157
|
* Execute a session
|
|
158
158
|
* Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.
|
|
159
|
+
* Plugin onRunStart is deferred until the driver signals the page is ready
|
|
160
|
+
* via the onPageReady callback, ensuring plugins get a real page object.
|
|
159
161
|
*/
|
|
160
162
|
async execute(session, pluginManager2, options = {}) {
|
|
161
163
|
const driver = this.getForSession(session);
|
|
162
164
|
const findings = [];
|
|
163
165
|
const logger = this.createLogger(driver.name);
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
findings,
|
|
169
|
-
addFinding: (finding) => {
|
|
170
|
-
findings.push(finding);
|
|
171
|
-
pluginManager2.addFinding(finding);
|
|
172
|
-
options.onFinding?.(finding);
|
|
173
|
-
},
|
|
174
|
-
logger,
|
|
175
|
-
options
|
|
166
|
+
const addFinding = (finding) => {
|
|
167
|
+
findings.push(finding);
|
|
168
|
+
pluginManager2.addFinding(finding);
|
|
169
|
+
options.onFinding?.(finding);
|
|
176
170
|
};
|
|
177
171
|
const pluginCtx = {
|
|
178
172
|
session,
|
|
@@ -182,21 +176,57 @@ var DriverManager = class {
|
|
|
182
176
|
engine: { version: "0.3.0", pluginApiVersion: 1 },
|
|
183
177
|
payloads: pluginManager2.getPayloads(),
|
|
184
178
|
findings,
|
|
179
|
+
addFinding,
|
|
185
180
|
logger,
|
|
186
181
|
fetch: globalThis.fetch
|
|
187
182
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
183
|
+
const ctx = {
|
|
184
|
+
session,
|
|
185
|
+
pluginManager: pluginManager2,
|
|
186
|
+
payloads: pluginManager2.getPayloads(),
|
|
187
|
+
findings,
|
|
188
|
+
addFinding,
|
|
189
|
+
logger,
|
|
190
|
+
options: {
|
|
191
|
+
...options,
|
|
192
|
+
// Provide onPageReady callback — fires plugin onRunStart hooks
|
|
193
|
+
// with the real page object once the driver has created it
|
|
194
|
+
onPageReady: async (page) => {
|
|
195
|
+
pluginCtx.page = page;
|
|
196
|
+
for (const loaded of pluginManager2.getPlugins()) {
|
|
197
|
+
if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {
|
|
198
|
+
try {
|
|
199
|
+
await loaded.plugin.hooks.onRunStart({
|
|
200
|
+
...pluginCtx,
|
|
201
|
+
config: loaded.config
|
|
202
|
+
});
|
|
203
|
+
} catch (err) {
|
|
204
|
+
logger.warn(
|
|
205
|
+
`Plugin ${loaded.plugin.name} onRunStart failed: ${err}`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
// Fires before browser closes — lets plugins flush pending async work
|
|
212
|
+
onBeforeClose: async (_page) => {
|
|
213
|
+
for (const loaded of pluginManager2.getPlugins()) {
|
|
214
|
+
if (loaded.enabled && loaded.plugin.hooks?.onBeforeClose) {
|
|
215
|
+
try {
|
|
216
|
+
await loaded.plugin.hooks.onBeforeClose({
|
|
217
|
+
...pluginCtx,
|
|
218
|
+
config: loaded.config
|
|
219
|
+
});
|
|
220
|
+
} catch (err) {
|
|
221
|
+
logger.warn(
|
|
222
|
+
`Plugin ${loaded.plugin.name} onBeforeClose failed: ${err}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
197
227
|
}
|
|
198
228
|
}
|
|
199
|
-
}
|
|
229
|
+
};
|
|
200
230
|
let result = await driver.runner.execute(session, ctx);
|
|
201
231
|
for (const loaded of pluginManager2.getPlugins()) {
|
|
202
232
|
if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {
|
|
@@ -487,6 +517,9 @@ var PluginManager = class {
|
|
|
487
517
|
engine: engineInfo,
|
|
488
518
|
payloads: this.sharedPayloads,
|
|
489
519
|
findings: this.sharedFindings,
|
|
520
|
+
addFinding: (finding) => {
|
|
521
|
+
this.sharedFindings.push(finding);
|
|
522
|
+
},
|
|
490
523
|
logger: this.createLogger("plugin"),
|
|
491
524
|
fetch: globalThis.fetch
|
|
492
525
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/driver-manager.ts","../src/driver-types.ts","../src/plugin-manager.ts","../src/plugin-types.ts"],"sourcesContent":["/**\n * Vulcn Driver Manager\n *\n * Handles driver loading, registration, and lifecycle.\n * Drivers are loaded from npm packages or local files.\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { parse, stringify } from \"yaml\";\nimport type {\n VulcnDriver,\n LoadedDriver,\n DriverSource,\n Session,\n Step,\n RunContext,\n RunResult,\n RunOptions,\n RecordOptions,\n CrawlOptions,\n RecordingHandle,\n DriverLogger,\n DRIVER_API_VERSION,\n} from \"./driver-types\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n/**\n * Driver Manager - loads and manages recording/running drivers\n */\nexport class DriverManager {\n private drivers: Map<string, LoadedDriver> = new Map();\n private defaultDriver: string | null = null;\n\n /**\n * Register a driver\n */\n register(driver: VulcnDriver, source: DriverSource = \"builtin\"): void {\n this.validateDriver(driver);\n this.drivers.set(driver.name, { driver, source });\n\n // First registered driver becomes default\n if (this.drivers.size === 1) {\n this.defaultDriver = driver.name;\n }\n }\n\n /**\n * Load a driver from npm or local path\n */\n async load(nameOrPath: string): Promise<void> {\n let driver: VulcnDriver;\n let source: DriverSource;\n\n if (\n nameOrPath.startsWith(\"./\") ||\n nameOrPath.startsWith(\"../\") ||\n isAbsolute(nameOrPath)\n ) {\n // Local file\n const resolved = isAbsolute(nameOrPath)\n ? nameOrPath\n : resolve(process.cwd(), nameOrPath);\n const module = await import(resolved);\n driver = module.default || module;\n source = \"local\";\n } else {\n // npm package\n const module = await import(nameOrPath);\n driver = module.default || module;\n source = \"npm\";\n }\n\n this.register(driver, source);\n }\n\n /**\n * Get a loaded driver by name\n */\n get(name: string): VulcnDriver | undefined {\n return this.drivers.get(name)?.driver;\n }\n\n /**\n * Get the default driver\n */\n getDefault(): VulcnDriver | undefined {\n if (!this.defaultDriver) return undefined;\n return this.get(this.defaultDriver);\n }\n\n /**\n * Set the default driver\n */\n setDefault(name: string): void {\n if (!this.drivers.has(name)) {\n throw new Error(`Driver \"${name}\" is not registered`);\n }\n this.defaultDriver = name;\n }\n\n /**\n * Check if a driver is registered\n */\n has(name: string): boolean {\n return this.drivers.has(name);\n }\n\n /**\n * Get all registered drivers\n */\n list(): LoadedDriver[] {\n return Array.from(this.drivers.values());\n }\n\n /**\n * Get driver for a session\n */\n getForSession(session: Session): VulcnDriver {\n const driverName = session.driver;\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(\n `Driver \"${driverName}\" not found. Install @vulcn/driver-${driverName} or load it manually.`,\n );\n }\n\n return driver;\n }\n\n /**\n * Parse a YAML session string into a Session object.\n *\n * Handles both new driver-format sessions and legacy v1 sessions.\n * Legacy sessions (those with non-namespaced step types like \"click\",\n * \"input\", \"navigate\") are automatically converted to the driver format\n * (e.g., \"browser.click\", \"browser.input\", \"browser.navigate\").\n *\n * @param yaml - Raw YAML string\n * @param defaultDriver - Driver to assign for legacy sessions (default: \"browser\")\n */\n parseSession(yaml: string, defaultDriver = \"browser\"): Session {\n const data = parse(yaml) as Record<string, unknown>;\n\n // Already in driver format — has a `driver` field\n if (data.driver && typeof data.driver === \"string\") {\n return data as unknown as Session;\n }\n\n // Legacy format — convert to driver session\n const steps = (data.steps as Array<Record<string, unknown>>) ?? [];\n const convertedSteps: Step[] = steps.map((step) => {\n const type = step.type as string;\n\n // If step type is already namespaced (e.g. \"browser.click\"), keep it\n if (type.includes(\".\")) {\n return step as unknown as Step;\n }\n\n // Convert legacy type → namespaced type\n return {\n ...step,\n type: `${defaultDriver}.${type}`,\n } as unknown as Step;\n });\n\n return {\n name: (data.name as string) ?? \"Untitled Session\",\n driver: defaultDriver,\n driverConfig: {\n browser: data.browser ?? \"chromium\",\n viewport: data.viewport ?? { width: 1280, height: 720 },\n startUrl: data.startUrl as string,\n },\n steps: convertedSteps,\n metadata: {\n recordedAt: data.recordedAt as string,\n version: (data.version as string) ?? \"1\",\n },\n };\n }\n\n /**\n * Start recording with a driver\n */\n async startRecording(\n driverName: string,\n config: Record<string, unknown>,\n options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n return driver.recorder.start(config, options);\n }\n\n /**\n * Auto-crawl a URL using a driver.\n *\n * Uses the driver's optional crawl() method to automatically\n * discover forms and injection points, returning Session[] that\n * can be passed to execute().\n *\n * Not all drivers support this — only browser has crawl capability.\n * CLI and API drivers will throw.\n */\n async crawl(\n driverName: string,\n config: Record<string, unknown>,\n options: CrawlOptions = {},\n ): Promise<Session[]> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n if (!driver.recorder.crawl) {\n throw new Error(\n `Driver \"${driverName}\" does not support auto-crawl. Use manual recording instead.`,\n );\n }\n\n return driver.recorder.crawl(config, options);\n }\n\n /**\n * Execute a session\n * Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.\n */\n async execute(\n session: Session,\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<RunResult> {\n const driver = this.getForSession(session);\n const findings: Finding[] = [];\n const logger = this.createLogger(driver.name);\n\n const ctx: RunContext = {\n session,\n pluginManager,\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding: (finding) => {\n findings.push(finding);\n pluginManager.addFinding(finding);\n options.onFinding?.(finding);\n },\n logger,\n options,\n };\n\n // Build a plugin context for hooks\n const pluginCtx = {\n session,\n page: null as unknown,\n headless: !!(options as Record<string, unknown>).headless,\n config: {} as Record<string, unknown>,\n engine: { version: \"0.3.0\", pluginApiVersion: 1 },\n payloads: pluginManager.getPayloads(),\n findings,\n logger,\n fetch: globalThis.fetch,\n };\n\n // Call onRunStart hooks\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {\n try {\n await loaded.plugin.hooks.onRunStart({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(`Plugin ${loaded.plugin.name} onRunStart failed: ${err}`);\n }\n }\n }\n\n // Execute via driver runner\n let result = await driver.runner.execute(session, ctx);\n\n // Call onRunEnd hooks (e.g., report generation)\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {\n try {\n result = await loaded.plugin.hooks.onRunEnd(result, {\n ...pluginCtx,\n config: loaded.config,\n findings: result.findings,\n });\n } catch (err) {\n logger.warn(`Plugin ${loaded.plugin.name} onRunEnd failed: ${err}`);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Validate driver structure\n */\n private validateDriver(driver: unknown): asserts driver is VulcnDriver {\n if (!driver || typeof driver !== \"object\") {\n throw new Error(\"Driver must be an object\");\n }\n\n const d = driver as Record<string, unknown>;\n\n if (typeof d.name !== \"string\" || !d.name) {\n throw new Error(\"Driver must have a name\");\n }\n\n if (typeof d.version !== \"string\" || !d.version) {\n throw new Error(\"Driver must have a version\");\n }\n\n if (!Array.isArray(d.stepTypes) || d.stepTypes.length === 0) {\n throw new Error(\"Driver must define stepTypes\");\n }\n\n if (!d.recorder || typeof d.recorder !== \"object\") {\n throw new Error(\"Driver must have a recorder\");\n }\n\n if (!d.runner || typeof d.runner !== \"object\") {\n throw new Error(\"Driver must have a runner\");\n }\n }\n\n /**\n * Create a scoped logger for a driver\n */\n private createLogger(name: string): DriverLogger {\n const prefix = `[driver:${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n}\n\n/**\n * Default driver manager instance\n */\nexport const driverManager = new DriverManager();\n","/**\n * Vulcn Driver System\n *\n * Drivers handle recording and running sessions for different targets:\n * - browser: Web applications (Playwright)\n * - api: REST/HTTP APIs\n * - cli: Command-line tools\n *\n * Each driver implements RecorderDriver and RunnerDriver interfaces.\n */\n\nimport type { z } from \"zod\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { PluginManager } from \"./plugin-manager\";\n\n/**\n * Current driver API version\n */\nexport const DRIVER_API_VERSION = 1;\n\n/**\n * Generic step - drivers define their own step types\n */\nexport interface Step {\n /** Unique step ID */\n id: string;\n\n /** Step type (namespaced, e.g., \"browser.click\", \"api.request\") */\n type: string;\n\n /** Timestamp when step was recorded */\n timestamp: number;\n\n /** Step-specific data */\n [key: string]: unknown;\n}\n\n/**\n * Generic session format\n */\nexport interface Session {\n /** Session name */\n name: string;\n\n /** Driver that recorded this session */\n driver: string;\n\n /** Driver-specific configuration */\n driverConfig: Record<string, unknown>;\n\n /** Recorded steps */\n steps: Step[];\n\n /** Session metadata */\n metadata?: {\n recordedAt?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Recording context passed to drivers\n */\nexport interface RecordContext {\n /** Session being built */\n session: Partial<Session>;\n\n /** Add a step to the session */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n\n /** Logger */\n logger: DriverLogger;\n}\n\n/**\n * Running context passed to drivers\n */\nexport interface RunContext {\n /** Session being executed */\n session: Session;\n\n /** Plugin manager for calling hooks */\n pluginManager: PluginManager;\n\n /** Available payloads */\n payloads: RuntimePayload[];\n\n /** Collected findings */\n findings: Finding[];\n\n /** Add a finding */\n addFinding(finding: Finding): void;\n\n /** Logger */\n logger: DriverLogger;\n\n /** Running options */\n options: RunOptions;\n}\n\n/**\n * Options for recording\n */\nexport interface RecordOptions {\n /** Enable auto-crawl mode (driver discovers forms automatically) */\n auto?: boolean;\n\n /** Crawl options (only used when auto=true) */\n crawlOptions?: CrawlOptions;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Options for auto-crawl mode\n *\n * When a driver supports crawling, these options control how\n * the automated discovery works. Not all drivers support crawling —\n * it's optional and primarily used by the browser driver.\n */\nexport interface CrawlOptions {\n /** Maximum crawl depth (0 = only the given URL, default: 2) */\n maxDepth?: number;\n\n /** Maximum number of pages to visit (default: 20) */\n maxPages?: number;\n\n /** Timeout per page navigation in ms (default: 10000) */\n pageTimeout?: number;\n\n /** Only crawl pages under the same origin (default: true) */\n sameOrigin?: boolean;\n\n /** Callback when a page is crawled */\n onPageCrawled?: (url: string, formsFound: number) => void;\n}\n\n/**\n * Options for running\n */\nexport interface RunOptions {\n /** Run headless (for visual drivers) */\n headless?: boolean;\n\n /** Callback for findings */\n onFinding?: (finding: Finding) => void;\n\n /** Callback for step completion */\n onStepComplete?: (stepId: string, payloadCount: number) => void;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Run result\n */\nexport interface RunResult {\n /** All findings */\n findings: Finding[];\n\n /** Steps executed */\n stepsExecuted: number;\n\n /** Payloads tested */\n payloadsTested: number;\n\n /** Duration in milliseconds */\n duration: number;\n\n /** Errors encountered */\n errors: string[];\n}\n\n/**\n * Driver logger\n */\nexport interface DriverLogger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\n/**\n * Recorder Driver Interface\n *\n * Implement this to add recording support for a target type.\n */\nexport interface RecorderDriver {\n /** Start recording and return control handle */\n start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle>;\n\n /**\n * Auto-crawl a URL and generate sessions.\n *\n * Optional — only drivers that support automated discovery\n * (e.g., browser) implement this. CLI and API drivers do not.\n *\n * When options.auto=true is passed to startRecording, the engine\n * calls this instead of start().\n */\n crawl?(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]>;\n}\n\n/**\n * Handle returned by RecorderDriver.start()\n */\nexport interface RecordingHandle {\n /** Stop recording and return the session */\n stop(): Promise<Session>;\n\n /** Abort recording without saving */\n abort(): Promise<void>;\n\n /** Get current steps (during recording) */\n getSteps(): Step[];\n\n /** Manually add a step */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n}\n\n/**\n * Runner Driver Interface\n *\n * Implement this to add running/replay support for a target type.\n */\nexport interface RunnerDriver {\n /** Execute a session with payloads */\n execute(session: Session, ctx: RunContext): Promise<RunResult>;\n}\n\n/**\n * Complete driver definition\n */\nexport interface VulcnDriver {\n /** Unique driver name (e.g., \"browser\", \"api\", \"cli\") */\n name: string;\n\n /** Driver version */\n version: string;\n\n /** Driver API version */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Configuration schema (Zod) */\n configSchema?: z.ZodSchema;\n\n /** Step types this driver handles */\n stepTypes: string[];\n\n /** Recorder implementation */\n recorder: RecorderDriver;\n\n /** Runner implementation */\n runner: RunnerDriver;\n}\n\n/**\n * Driver source for loading\n */\nexport type DriverSource = \"npm\" | \"local\" | \"builtin\";\n\n/**\n * Loaded driver with metadata\n */\nexport interface LoadedDriver {\n driver: VulcnDriver;\n source: DriverSource;\n}\n","/**\n * Vulcn Plugin Manager\n * Handles plugin loading, lifecycle, and hook execution\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve, isAbsolute } from \"node:path\";\nimport YAML from \"yaml\";\nimport { z } from \"zod\";\nimport type {\n VulcnPlugin,\n VulcnConfig,\n PluginConfig,\n LoadedPlugin,\n PluginContext,\n PluginSource,\n PluginLogger,\n EngineInfo,\n PluginHooks,\n} from \"./plugin-types\";\nimport { PLUGIN_API_VERSION } from \"./plugin-types\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n// Package version (injected at build time or read from package.json)\nconst ENGINE_VERSION = \"0.2.0\";\n\n/**\n * Config file schema\n */\nconst VulcnConfigSchema = z.object({\n version: z.string().default(\"1\"),\n plugins: z\n .array(\n z.object({\n name: z.string(),\n config: z.record(z.unknown()).optional(),\n enabled: z.boolean().default(true),\n }),\n )\n .optional(),\n settings: z\n .object({\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).optional(),\n headless: z.boolean().optional(),\n timeout: z.number().optional(),\n })\n .optional(),\n});\n\n/**\n * Plugin Manager - loads, configures, and orchestrates plugins\n */\nexport class PluginManager {\n private plugins: LoadedPlugin[] = [];\n private config: VulcnConfig | null = null;\n private initialized = false;\n\n /**\n * Shared context passed to all plugins\n */\n private sharedPayloads: RuntimePayload[] = [];\n private sharedFindings: Finding[] = [];\n\n /**\n * Load configuration from vulcn.config.yml\n */\n async loadConfig(configPath?: string): Promise<VulcnConfig> {\n const paths = configPath\n ? [configPath]\n : [\n \"vulcn.config.yml\",\n \"vulcn.config.yaml\",\n \"vulcn.config.json\",\n \".vulcnrc.yml\",\n \".vulcnrc.yaml\",\n \".vulcnrc.json\",\n ];\n\n for (const path of paths) {\n const resolved = isAbsolute(path) ? path : resolve(process.cwd(), path);\n if (existsSync(resolved)) {\n const content = await readFile(resolved, \"utf-8\");\n const parsed = path.endsWith(\".json\")\n ? JSON.parse(content)\n : YAML.parse(content);\n this.config = VulcnConfigSchema.parse(parsed);\n return this.config;\n }\n }\n\n // No config file - use defaults\n this.config = { version: \"1\", plugins: [], settings: {} };\n return this.config;\n }\n\n /**\n * Load all plugins from config\n */\n async loadPlugins(): Promise<void> {\n if (!this.config) {\n await this.loadConfig();\n }\n\n const pluginConfigs = this.config?.plugins || [];\n\n for (const pluginConfig of pluginConfigs) {\n if (pluginConfig.enabled === false) continue;\n\n try {\n const loaded = await this.loadPlugin(pluginConfig);\n this.plugins.push(loaded);\n } catch (err) {\n console.error(\n `Failed to load plugin ${pluginConfig.name}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n /**\n * Load a single plugin\n */\n private async loadPlugin(config: PluginConfig): Promise<LoadedPlugin> {\n const { name, config: pluginConfig = {} } = config;\n let plugin: VulcnPlugin;\n let source: PluginSource;\n\n // Determine plugin source and load\n if (name.startsWith(\"./\") || name.startsWith(\"../\") || isAbsolute(name)) {\n // Local file plugin\n const resolved = isAbsolute(name) ? name : resolve(process.cwd(), name);\n const module = await import(resolved);\n plugin = module.default || module;\n source = \"local\";\n } else if (name.startsWith(\"@vulcn/\")) {\n // Official plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n } else {\n // Community plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n }\n\n // Validate plugin structure\n this.validatePlugin(plugin);\n\n // Validate plugin config if schema provided\n let resolvedConfig = pluginConfig;\n if (plugin.configSchema) {\n try {\n resolvedConfig = plugin.configSchema.parse(pluginConfig);\n } catch (err) {\n throw new Error(\n `Invalid config for plugin ${name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return {\n plugin,\n config: resolvedConfig,\n source,\n enabled: true,\n };\n }\n\n /**\n * Validate plugin structure\n */\n private validatePlugin(plugin: unknown): asserts plugin is VulcnPlugin {\n if (!plugin || typeof plugin !== \"object\") {\n throw new Error(\"Plugin must be an object\");\n }\n\n const p = plugin as Record<string, unknown>;\n if (typeof p.name !== \"string\" || !p.name) {\n throw new Error(\"Plugin must have a name\");\n }\n if (typeof p.version !== \"string\" || !p.version) {\n throw new Error(\"Plugin must have a version\");\n }\n\n // Check API version compatibility\n const apiVersion = (p.apiVersion as number) || 1;\n if (apiVersion > PLUGIN_API_VERSION) {\n throw new Error(\n `Plugin requires API version ${apiVersion}, but engine supports ${PLUGIN_API_VERSION}`,\n );\n }\n }\n\n /**\n * Add a plugin programmatically (for testing or dynamic loading)\n */\n addPlugin(plugin: VulcnPlugin, config: Record<string, unknown> = {}): void {\n this.validatePlugin(plugin);\n this.plugins.push({\n plugin,\n config,\n source: \"custom\",\n enabled: true,\n });\n }\n\n /**\n * Initialize all plugins (call onInit hooks)\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Load payloads from plugins that provide them\n for (const loaded of this.plugins) {\n if (loaded.plugin.payloads) {\n const payloads =\n typeof loaded.plugin.payloads === \"function\"\n ? await loaded.plugin.payloads()\n : loaded.plugin.payloads;\n this.sharedPayloads.push(...payloads);\n }\n }\n\n // Call onInit hooks\n await this.callHook(\"onInit\", (hook, ctx) => hook(ctx));\n\n this.initialized = true;\n }\n\n /**\n * Destroy all plugins (call onDestroy hooks)\n */\n async destroy(): Promise<void> {\n await this.callHook(\"onDestroy\", (hook, ctx) => hook(ctx));\n this.plugins = [];\n this.sharedPayloads = [];\n this.sharedFindings = [];\n this.initialized = false;\n }\n\n /**\n * Get all loaded payloads\n */\n getPayloads(): RuntimePayload[] {\n return this.sharedPayloads;\n }\n\n /**\n * Get all collected findings\n */\n getFindings(): Finding[] {\n return this.sharedFindings;\n }\n\n /**\n * Add a finding (used by detectors)\n */\n addFinding(finding: Finding): void {\n this.sharedFindings.push(finding);\n }\n\n /**\n * Add payloads (used by loaders)\n */\n addPayloads(payloads: RuntimePayload[]): void {\n this.sharedPayloads.push(...payloads);\n }\n\n /**\n * Clear findings (for new run)\n */\n clearFindings(): void {\n this.sharedFindings = [];\n }\n\n /**\n * Get loaded plugins\n */\n getPlugins(): LoadedPlugin[] {\n return this.plugins;\n }\n\n /**\n * Check if a plugin is loaded by name\n */\n hasPlugin(name: string): boolean {\n return this.plugins.some((p) => p.plugin.name === name);\n }\n\n /**\n * Create base context for plugins\n */\n createContext(pluginConfig: Record<string, unknown>): PluginContext {\n const engineInfo: EngineInfo = {\n version: ENGINE_VERSION,\n pluginApiVersion: PLUGIN_API_VERSION,\n };\n\n return {\n config: pluginConfig,\n engine: engineInfo,\n payloads: this.sharedPayloads,\n findings: this.sharedFindings,\n logger: this.createLogger(\"plugin\"),\n fetch: globalThis.fetch,\n };\n }\n\n /**\n * Create scoped logger for a plugin\n */\n private createLogger(name: string): PluginLogger {\n const prefix = `[${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n\n /**\n * Call a hook on all plugins sequentially\n */\n async callHook<K extends keyof PluginHooks>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<unknown>,\n ): Promise<void> {\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n await executor(hook as NonNullable<PluginHooks[K]>, ctx);\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n }\n\n /**\n * Call a hook and collect results\n */\n async callHookCollect<K extends keyof PluginHooks, R>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<R | R[] | null>,\n ): Promise<R[]> {\n const results: R[] = [];\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n const result = await executor(\n hook as NonNullable<PluginHooks[K]>,\n ctx,\n );\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n results.push(...result);\n } else {\n results.push(result);\n }\n }\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return results;\n }\n\n /**\n * Call a hook that transforms a value through the pipeline\n */\n async callHookPipe<T>(\n hookName: keyof PluginHooks,\n initial: T,\n executor: (\n hook: NonNullable<PluginHooks[typeof hookName]>,\n value: T,\n ctx: PluginContext,\n ) => Promise<T>,\n ): Promise<T> {\n let value = initial;\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n value = await executor(\n hook as NonNullable<PluginHooks[typeof hookName]>,\n value,\n ctx,\n );\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return value;\n }\n}\n\n/**\n * Default shared plugin manager instance\n */\nexport const pluginManager = new PluginManager();\n","/**\n * Vulcn Plugin System Types\n * @module @vulcn/engine/plugin\n *\n * The plugin system is driver-agnostic. Detection plugins receive\n * a generic page interface rather than Playwright types directly.\n * This allows the same plugin to work across different driver types.\n */\n\nimport type { z } from \"zod\";\nimport type { Session, Step } from \"./driver-types\";\nimport type { Finding } from \"./types\";\nimport type { RunResult } from \"./driver-types\";\nimport type { RuntimePayload, PayloadCategory } from \"./payload-types\";\n\n// Re-export for plugin authors\nexport type {\n Session,\n Step,\n Finding,\n RunResult,\n RuntimePayload,\n PayloadCategory,\n};\n\n/**\n * Plugin API version - plugins declare compatibility\n */\nexport const PLUGIN_API_VERSION = 1;\n\n/**\n * Plugin source types for identification\n */\nexport type PluginSource = \"builtin\" | \"npm\" | \"local\" | \"custom\";\n\n/**\n * Main plugin interface\n */\nexport interface VulcnPlugin {\n /** Unique plugin name (e.g., \"@vulcn/plugin-payloads\") */\n name: string;\n\n /** Plugin version (semver) */\n version: string;\n\n /** Plugin API version this plugin targets */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Lifecycle hooks */\n hooks?: PluginHooks;\n\n /**\n * Payloads provided by this plugin (Loaders)\n * Can be static array or async function for lazy loading\n */\n payloads?: RuntimePayload[] | (() => Promise<RuntimePayload[]>);\n\n /**\n * Zod schema for plugin configuration validation\n */\n configSchema?: z.ZodSchema;\n}\n\n/**\n * Plugin lifecycle hooks\n *\n * Detection hooks (onDialog, onConsoleMessage, etc.) receive\n * Playwright types from the driver. Plugins that use these\n * should declare playwright as a peer/dev dependency.\n */\nexport interface PluginHooks {\n // ─────────────────────────────────────────────────────────────────\n // Initialization\n // ─────────────────────────────────────────────────────────────────\n\n /**\n * Called when plugin is loaded, before any operation\n * Use for setup, loading payloads, etc.\n */\n onInit?: (ctx: PluginContext) => Promise<void>;\n\n /**\n * Called when plugin is unloaded/cleanup\n */\n onDestroy?: (ctx: PluginContext) => Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────\n // Recording Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when recording starts */\n onRecordStart?: (ctx: RecordContext) => Promise<void>;\n\n /** Called for each recorded step, can transform */\n onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;\n\n /** Called when recording ends, can transform session */\n onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;\n\n // ─────────────────────────────────────────────────────────────────\n // Running Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when run starts */\n onRunStart?: (ctx: RunContext) => Promise<void>;\n\n /** Called before each payload is injected, can transform payload */\n onBeforePayload?: (\n payload: string,\n step: Step,\n ctx: RunContext,\n ) => Promise<string>;\n\n /** Called after payload injection, for detection */\n onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called when run ends, can transform results */\n onRunEnd?: (result: RunResult, ctx: RunContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Browser Event Hooks (Detection)\n // These receive driver-specific types (e.g. Playwright's Dialog)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when JavaScript alert/confirm/prompt appears */\n onDialog?: (dialog: unknown, ctx: DetectContext) => Promise<Finding | null>;\n\n /** Called on console.log/warn/error */\n onConsoleMessage?: (\n msg: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on page load/navigation */\n onPageLoad?: (page: unknown, ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called on network request */\n onNetworkRequest?: (\n request: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on network response */\n onNetworkResponse?: (\n response: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n}\n\n/**\n * Logger interface for plugins\n */\nexport interface PluginLogger {\n debug: (msg: string, ...args: unknown[]) => void;\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n error: (msg: string, ...args: unknown[]) => void;\n}\n\n/**\n * Engine information exposed to plugins\n */\nexport interface EngineInfo {\n version: string;\n pluginApiVersion: number;\n}\n\n/**\n * Base context available to all plugin hooks\n */\nexport interface PluginContext {\n /** Plugin-specific config from vulcn.config.yml */\n config: Record<string, unknown>;\n\n /** Engine information */\n engine: EngineInfo;\n\n /** Shared payload registry - loaders add payloads here */\n payloads: RuntimePayload[];\n\n /** Shared findings collection - detectors add findings here */\n findings: Finding[];\n\n /** Scoped logger */\n logger: PluginLogger;\n\n /** Fetch API for network requests */\n fetch: typeof fetch;\n}\n\n/**\n * Context for recording phase hooks\n */\nexport interface RecordContext extends PluginContext {\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n}\n\n/**\n * Context for running phase hooks\n */\nexport interface RunContext extends PluginContext {\n /** Session being executed */\n session: Session;\n\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n\n /** Whether running headless */\n headless: boolean;\n}\n\n/**\n * Context for detection hooks\n */\nexport interface DetectContext extends RunContext {\n /** Current step being tested */\n step: Step;\n\n /** Current payload set being tested */\n payloadSet: RuntimePayload;\n\n /** Actual payload value injected */\n payloadValue: string;\n\n /** Step ID for reporting */\n stepId: string;\n}\n\n/**\n * Plugin configuration in vulcn.config.yml\n */\nexport interface PluginConfig {\n /** Plugin name/path */\n name: string;\n\n /** Plugin-specific configuration */\n config?: Record<string, unknown>;\n\n /** Whether plugin is enabled (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Vulcn configuration file schema\n */\nexport interface VulcnConfig {\n /** Config version */\n version: string;\n\n /** Plugins to load */\n plugins?: PluginConfig[];\n\n /** Global settings */\n settings?: {\n headless?: boolean;\n timeout?: number;\n };\n}\n\n/**\n * Loaded plugin instance with resolved config\n */\nexport interface LoadedPlugin {\n /** Plugin definition */\n plugin: VulcnPlugin;\n\n /** Resolved configuration */\n config: Record<string, unknown>;\n\n /** Source of the plugin */\n source: PluginSource;\n\n /** Whether plugin is enabled */\n enabled: boolean;\n}\n"],"mappings":"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","names":["pluginManager","resolve","isAbsolute","isAbsolute","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/driver-manager.ts","../src/driver-types.ts","../src/plugin-manager.ts","../src/plugin-types.ts"],"sourcesContent":["/**\n * Vulcn Driver Manager\n *\n * Handles driver loading, registration, and lifecycle.\n * Drivers are loaded from npm packages or local files.\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { parse, stringify } from \"yaml\";\nimport type {\n VulcnDriver,\n LoadedDriver,\n DriverSource,\n Session,\n Step,\n RunContext,\n RunResult,\n RunOptions,\n RecordOptions,\n CrawlOptions,\n RecordingHandle,\n DriverLogger,\n DRIVER_API_VERSION,\n} from \"./driver-types\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n/**\n * Driver Manager - loads and manages recording/running drivers\n */\nexport class DriverManager {\n private drivers: Map<string, LoadedDriver> = new Map();\n private defaultDriver: string | null = null;\n\n /**\n * Register a driver\n */\n register(driver: VulcnDriver, source: DriverSource = \"builtin\"): void {\n this.validateDriver(driver);\n this.drivers.set(driver.name, { driver, source });\n\n // First registered driver becomes default\n if (this.drivers.size === 1) {\n this.defaultDriver = driver.name;\n }\n }\n\n /**\n * Load a driver from npm or local path\n */\n async load(nameOrPath: string): Promise<void> {\n let driver: VulcnDriver;\n let source: DriverSource;\n\n if (\n nameOrPath.startsWith(\"./\") ||\n nameOrPath.startsWith(\"../\") ||\n isAbsolute(nameOrPath)\n ) {\n // Local file\n const resolved = isAbsolute(nameOrPath)\n ? nameOrPath\n : resolve(process.cwd(), nameOrPath);\n const module = await import(resolved);\n driver = module.default || module;\n source = \"local\";\n } else {\n // npm package\n const module = await import(nameOrPath);\n driver = module.default || module;\n source = \"npm\";\n }\n\n this.register(driver, source);\n }\n\n /**\n * Get a loaded driver by name\n */\n get(name: string): VulcnDriver | undefined {\n return this.drivers.get(name)?.driver;\n }\n\n /**\n * Get the default driver\n */\n getDefault(): VulcnDriver | undefined {\n if (!this.defaultDriver) return undefined;\n return this.get(this.defaultDriver);\n }\n\n /**\n * Set the default driver\n */\n setDefault(name: string): void {\n if (!this.drivers.has(name)) {\n throw new Error(`Driver \"${name}\" is not registered`);\n }\n this.defaultDriver = name;\n }\n\n /**\n * Check if a driver is registered\n */\n has(name: string): boolean {\n return this.drivers.has(name);\n }\n\n /**\n * Get all registered drivers\n */\n list(): LoadedDriver[] {\n return Array.from(this.drivers.values());\n }\n\n /**\n * Get driver for a session\n */\n getForSession(session: Session): VulcnDriver {\n const driverName = session.driver;\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(\n `Driver \"${driverName}\" not found. Install @vulcn/driver-${driverName} or load it manually.`,\n );\n }\n\n return driver;\n }\n\n /**\n * Parse a YAML session string into a Session object.\n *\n * Handles both new driver-format sessions and legacy v1 sessions.\n * Legacy sessions (those with non-namespaced step types like \"click\",\n * \"input\", \"navigate\") are automatically converted to the driver format\n * (e.g., \"browser.click\", \"browser.input\", \"browser.navigate\").\n *\n * @param yaml - Raw YAML string\n * @param defaultDriver - Driver to assign for legacy sessions (default: \"browser\")\n */\n parseSession(yaml: string, defaultDriver = \"browser\"): Session {\n const data = parse(yaml) as Record<string, unknown>;\n\n // Already in driver format — has a `driver` field\n if (data.driver && typeof data.driver === \"string\") {\n return data as unknown as Session;\n }\n\n // Legacy format — convert to driver session\n const steps = (data.steps as Array<Record<string, unknown>>) ?? [];\n const convertedSteps: Step[] = steps.map((step) => {\n const type = step.type as string;\n\n // If step type is already namespaced (e.g. \"browser.click\"), keep it\n if (type.includes(\".\")) {\n return step as unknown as Step;\n }\n\n // Convert legacy type → namespaced type\n return {\n ...step,\n type: `${defaultDriver}.${type}`,\n } as unknown as Step;\n });\n\n return {\n name: (data.name as string) ?? \"Untitled Session\",\n driver: defaultDriver,\n driverConfig: {\n browser: data.browser ?? \"chromium\",\n viewport: data.viewport ?? { width: 1280, height: 720 },\n startUrl: data.startUrl as string,\n },\n steps: convertedSteps,\n metadata: {\n recordedAt: data.recordedAt as string,\n version: (data.version as string) ?? \"1\",\n },\n };\n }\n\n /**\n * Start recording with a driver\n */\n async startRecording(\n driverName: string,\n config: Record<string, unknown>,\n options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n return driver.recorder.start(config, options);\n }\n\n /**\n * Auto-crawl a URL using a driver.\n *\n * Uses the driver's optional crawl() method to automatically\n * discover forms and injection points, returning Session[] that\n * can be passed to execute().\n *\n * Not all drivers support this — only browser has crawl capability.\n * CLI and API drivers will throw.\n */\n async crawl(\n driverName: string,\n config: Record<string, unknown>,\n options: CrawlOptions = {},\n ): Promise<Session[]> {\n const driver = this.get(driverName);\n\n if (!driver) {\n throw new Error(`Driver \"${driverName}\" not found`);\n }\n\n if (!driver.recorder.crawl) {\n throw new Error(\n `Driver \"${driverName}\" does not support auto-crawl. Use manual recording instead.`,\n );\n }\n\n return driver.recorder.crawl(config, options);\n }\n\n /**\n * Execute a session\n * Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.\n * Plugin onRunStart is deferred until the driver signals the page is ready\n * via the onPageReady callback, ensuring plugins get a real page object.\n */\n async execute(\n session: Session,\n pluginManager: PluginManager,\n options: RunOptions = {},\n ): Promise<RunResult> {\n const driver = this.getForSession(session);\n const findings: Finding[] = [];\n const logger = this.createLogger(driver.name);\n\n // Shared addFinding function — used by both internal RunContext and\n // plugin context. Ensures all findings (active + passive) flow through\n // the onFinding callback so consumers get notified consistently.\n const addFinding = (finding: Finding) => {\n findings.push(finding);\n pluginManager.addFinding(finding);\n options.onFinding?.(finding);\n };\n\n // Build a plugin context template for hooks (page is set in onPageReady)\n const pluginCtx = {\n session,\n page: null as unknown,\n headless: !!(options as Record<string, unknown>).headless,\n config: {} as Record<string, unknown>,\n engine: { version: \"0.3.0\", pluginApiVersion: 1 },\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n fetch: globalThis.fetch,\n };\n\n const ctx: RunContext = {\n session,\n pluginManager,\n payloads: pluginManager.getPayloads(),\n findings,\n addFinding,\n logger,\n options: {\n ...options,\n // Provide onPageReady callback — fires plugin onRunStart hooks\n // with the real page object once the driver has created it\n onPageReady: async (page: unknown) => {\n pluginCtx.page = page;\n\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunStart) {\n try {\n await loaded.plugin.hooks.onRunStart({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(\n `Plugin ${loaded.plugin.name} onRunStart failed: ${err}`,\n );\n }\n }\n }\n },\n // Fires before browser closes — lets plugins flush pending async work\n onBeforeClose: async (_page: unknown) => {\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onBeforeClose) {\n try {\n await loaded.plugin.hooks.onBeforeClose({\n ...pluginCtx,\n config: loaded.config,\n });\n } catch (err) {\n logger.warn(\n `Plugin ${loaded.plugin.name} onBeforeClose failed: ${err}`,\n );\n }\n }\n }\n },\n },\n };\n\n // Execute via driver runner\n // (runner calls ctx.options.onPageReady(page) after creating the page)\n let result = await driver.runner.execute(session, ctx);\n\n // Call onRunEnd hooks (e.g., report generation)\n for (const loaded of pluginManager.getPlugins()) {\n if (loaded.enabled && loaded.plugin.hooks?.onRunEnd) {\n try {\n result = await loaded.plugin.hooks.onRunEnd(result, {\n ...pluginCtx,\n config: loaded.config,\n findings: result.findings,\n });\n } catch (err) {\n logger.warn(`Plugin ${loaded.plugin.name} onRunEnd failed: ${err}`);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Validate driver structure\n */\n private validateDriver(driver: unknown): asserts driver is VulcnDriver {\n if (!driver || typeof driver !== \"object\") {\n throw new Error(\"Driver must be an object\");\n }\n\n const d = driver as Record<string, unknown>;\n\n if (typeof d.name !== \"string\" || !d.name) {\n throw new Error(\"Driver must have a name\");\n }\n\n if (typeof d.version !== \"string\" || !d.version) {\n throw new Error(\"Driver must have a version\");\n }\n\n if (!Array.isArray(d.stepTypes) || d.stepTypes.length === 0) {\n throw new Error(\"Driver must define stepTypes\");\n }\n\n if (!d.recorder || typeof d.recorder !== \"object\") {\n throw new Error(\"Driver must have a recorder\");\n }\n\n if (!d.runner || typeof d.runner !== \"object\") {\n throw new Error(\"Driver must have a runner\");\n }\n }\n\n /**\n * Create a scoped logger for a driver\n */\n private createLogger(name: string): DriverLogger {\n const prefix = `[driver:${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n}\n\n/**\n * Default driver manager instance\n */\nexport const driverManager = new DriverManager();\n","/**\n * Vulcn Driver System\n *\n * Drivers handle recording and running sessions for different targets:\n * - browser: Web applications (Playwright)\n * - api: REST/HTTP APIs\n * - cli: Command-line tools\n *\n * Each driver implements RecorderDriver and RunnerDriver interfaces.\n */\n\nimport type { z } from \"zod\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\nimport type { PluginManager } from \"./plugin-manager\";\n\n/**\n * Current driver API version\n */\nexport const DRIVER_API_VERSION = 1;\n\n/**\n * Generic step - drivers define their own step types\n */\nexport interface Step {\n /** Unique step ID */\n id: string;\n\n /** Step type (namespaced, e.g., \"browser.click\", \"api.request\") */\n type: string;\n\n /** Timestamp when step was recorded */\n timestamp: number;\n\n /** Step-specific data */\n [key: string]: unknown;\n}\n\n/**\n * Generic session format\n */\nexport interface Session {\n /** Session name */\n name: string;\n\n /** Driver that recorded this session */\n driver: string;\n\n /** Driver-specific configuration */\n driverConfig: Record<string, unknown>;\n\n /** Recorded steps */\n steps: Step[];\n\n /** Session metadata */\n metadata?: {\n recordedAt?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Recording context passed to drivers\n */\nexport interface RecordContext {\n /** Session being built */\n session: Partial<Session>;\n\n /** Add a step to the session */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n\n /** Logger */\n logger: DriverLogger;\n}\n\n/**\n * Running context passed to drivers\n */\nexport interface RunContext {\n /** Session being executed */\n session: Session;\n\n /** Plugin manager for calling hooks */\n pluginManager: PluginManager;\n\n /** Available payloads */\n payloads: RuntimePayload[];\n\n /** Collected findings */\n findings: Finding[];\n\n /** Add a finding */\n addFinding(finding: Finding): void;\n\n /** Logger */\n logger: DriverLogger;\n\n /** Running options */\n options: RunOptions;\n}\n\n/**\n * Options for recording\n */\nexport interface RecordOptions {\n /** Enable auto-crawl mode (driver discovers forms automatically) */\n auto?: boolean;\n\n /** Crawl options (only used when auto=true) */\n crawlOptions?: CrawlOptions;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Options for auto-crawl mode\n *\n * When a driver supports crawling, these options control how\n * the automated discovery works. Not all drivers support crawling —\n * it's optional and primarily used by the browser driver.\n */\nexport interface CrawlOptions {\n /** Maximum crawl depth (0 = only the given URL, default: 2) */\n maxDepth?: number;\n\n /** Maximum number of pages to visit (default: 20) */\n maxPages?: number;\n\n /** Timeout per page navigation in ms (default: 10000) */\n pageTimeout?: number;\n\n /** Only crawl pages under the same origin (default: true) */\n sameOrigin?: boolean;\n\n /** Callback when a page is crawled */\n onPageCrawled?: (url: string, formsFound: number) => void;\n}\n\n/**\n * Options for running\n */\nexport interface RunOptions {\n /** Run headless (for visual drivers) */\n headless?: boolean;\n\n /** Callback for findings */\n onFinding?: (finding: Finding) => void;\n\n /** Callback for step completion */\n onStepComplete?: (stepId: string, payloadCount: number) => void;\n\n /**\n * Called by the driver runner after the page/environment is ready.\n * The driver-manager uses this to fire plugin onRunStart hooks\n * with the real page object (instead of null).\n */\n onPageReady?: (page: unknown) => Promise<void>;\n\n /**\n * Called by the driver runner before closing the browser/environment.\n * The driver-manager uses this to fire plugin onBeforeClose hooks\n * so plugins can flush pending async work.\n */\n onBeforeClose?: (page: unknown) => Promise<void>;\n\n /** Driver-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Run result\n */\nexport interface RunResult {\n /** All findings */\n findings: Finding[];\n\n /** Steps executed */\n stepsExecuted: number;\n\n /** Payloads tested */\n payloadsTested: number;\n\n /** Duration in milliseconds */\n duration: number;\n\n /** Errors encountered */\n errors: string[];\n}\n\n/**\n * Driver logger\n */\nexport interface DriverLogger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\n/**\n * Recorder Driver Interface\n *\n * Implement this to add recording support for a target type.\n */\nexport interface RecorderDriver {\n /** Start recording and return control handle */\n start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle>;\n\n /**\n * Auto-crawl a URL and generate sessions.\n *\n * Optional — only drivers that support automated discovery\n * (e.g., browser) implement this. CLI and API drivers do not.\n *\n * When options.auto=true is passed to startRecording, the engine\n * calls this instead of start().\n */\n crawl?(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]>;\n}\n\n/**\n * Handle returned by RecorderDriver.start()\n */\nexport interface RecordingHandle {\n /** Stop recording and return the session */\n stop(): Promise<Session>;\n\n /** Abort recording without saving */\n abort(): Promise<void>;\n\n /** Get current steps (during recording) */\n getSteps(): Step[];\n\n /** Manually add a step */\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void;\n}\n\n/**\n * Runner Driver Interface\n *\n * Implement this to add running/replay support for a target type.\n */\nexport interface RunnerDriver {\n /** Execute a session with payloads */\n execute(session: Session, ctx: RunContext): Promise<RunResult>;\n}\n\n/**\n * Complete driver definition\n */\nexport interface VulcnDriver {\n /** Unique driver name (e.g., \"browser\", \"api\", \"cli\") */\n name: string;\n\n /** Driver version */\n version: string;\n\n /** Driver API version */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Configuration schema (Zod) */\n configSchema?: z.ZodSchema;\n\n /** Step types this driver handles */\n stepTypes: string[];\n\n /** Recorder implementation */\n recorder: RecorderDriver;\n\n /** Runner implementation */\n runner: RunnerDriver;\n}\n\n/**\n * Driver source for loading\n */\nexport type DriverSource = \"npm\" | \"local\" | \"builtin\";\n\n/**\n * Loaded driver with metadata\n */\nexport interface LoadedDriver {\n driver: VulcnDriver;\n source: DriverSource;\n}\n","/**\n * Vulcn Plugin Manager\n * Handles plugin loading, lifecycle, and hook execution\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve, isAbsolute } from \"node:path\";\nimport YAML from \"yaml\";\nimport { z } from \"zod\";\nimport type {\n VulcnPlugin,\n VulcnConfig,\n PluginConfig,\n LoadedPlugin,\n PluginContext,\n PluginSource,\n PluginLogger,\n EngineInfo,\n PluginHooks,\n} from \"./plugin-types\";\nimport { PLUGIN_API_VERSION } from \"./plugin-types\";\nimport type { Finding } from \"./types\";\nimport type { RuntimePayload } from \"./payload-types\";\n\n// Package version (injected at build time or read from package.json)\nconst ENGINE_VERSION = \"0.2.0\";\n\n/**\n * Config file schema\n */\nconst VulcnConfigSchema = z.object({\n version: z.string().default(\"1\"),\n plugins: z\n .array(\n z.object({\n name: z.string(),\n config: z.record(z.unknown()).optional(),\n enabled: z.boolean().default(true),\n }),\n )\n .optional(),\n settings: z\n .object({\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).optional(),\n headless: z.boolean().optional(),\n timeout: z.number().optional(),\n })\n .optional(),\n});\n\n/**\n * Plugin Manager - loads, configures, and orchestrates plugins\n */\nexport class PluginManager {\n private plugins: LoadedPlugin[] = [];\n private config: VulcnConfig | null = null;\n private initialized = false;\n\n /**\n * Shared context passed to all plugins\n */\n private sharedPayloads: RuntimePayload[] = [];\n private sharedFindings: Finding[] = [];\n\n /**\n * Load configuration from vulcn.config.yml\n */\n async loadConfig(configPath?: string): Promise<VulcnConfig> {\n const paths = configPath\n ? [configPath]\n : [\n \"vulcn.config.yml\",\n \"vulcn.config.yaml\",\n \"vulcn.config.json\",\n \".vulcnrc.yml\",\n \".vulcnrc.yaml\",\n \".vulcnrc.json\",\n ];\n\n for (const path of paths) {\n const resolved = isAbsolute(path) ? path : resolve(process.cwd(), path);\n if (existsSync(resolved)) {\n const content = await readFile(resolved, \"utf-8\");\n const parsed = path.endsWith(\".json\")\n ? JSON.parse(content)\n : YAML.parse(content);\n this.config = VulcnConfigSchema.parse(parsed);\n return this.config;\n }\n }\n\n // No config file - use defaults\n this.config = { version: \"1\", plugins: [], settings: {} };\n return this.config;\n }\n\n /**\n * Load all plugins from config\n */\n async loadPlugins(): Promise<void> {\n if (!this.config) {\n await this.loadConfig();\n }\n\n const pluginConfigs = this.config?.plugins || [];\n\n for (const pluginConfig of pluginConfigs) {\n if (pluginConfig.enabled === false) continue;\n\n try {\n const loaded = await this.loadPlugin(pluginConfig);\n this.plugins.push(loaded);\n } catch (err) {\n console.error(\n `Failed to load plugin ${pluginConfig.name}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n /**\n * Load a single plugin\n */\n private async loadPlugin(config: PluginConfig): Promise<LoadedPlugin> {\n const { name, config: pluginConfig = {} } = config;\n let plugin: VulcnPlugin;\n let source: PluginSource;\n\n // Determine plugin source and load\n if (name.startsWith(\"./\") || name.startsWith(\"../\") || isAbsolute(name)) {\n // Local file plugin\n const resolved = isAbsolute(name) ? name : resolve(process.cwd(), name);\n const module = await import(resolved);\n plugin = module.default || module;\n source = \"local\";\n } else if (name.startsWith(\"@vulcn/\")) {\n // Official plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n } else {\n // Community plugin (npm package)\n const module = await import(name);\n plugin = module.default || module;\n source = \"npm\";\n }\n\n // Validate plugin structure\n this.validatePlugin(plugin);\n\n // Validate plugin config if schema provided\n let resolvedConfig = pluginConfig;\n if (plugin.configSchema) {\n try {\n resolvedConfig = plugin.configSchema.parse(pluginConfig);\n } catch (err) {\n throw new Error(\n `Invalid config for plugin ${name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return {\n plugin,\n config: resolvedConfig,\n source,\n enabled: true,\n };\n }\n\n /**\n * Validate plugin structure\n */\n private validatePlugin(plugin: unknown): asserts plugin is VulcnPlugin {\n if (!plugin || typeof plugin !== \"object\") {\n throw new Error(\"Plugin must be an object\");\n }\n\n const p = plugin as Record<string, unknown>;\n if (typeof p.name !== \"string\" || !p.name) {\n throw new Error(\"Plugin must have a name\");\n }\n if (typeof p.version !== \"string\" || !p.version) {\n throw new Error(\"Plugin must have a version\");\n }\n\n // Check API version compatibility\n const apiVersion = (p.apiVersion as number) || 1;\n if (apiVersion > PLUGIN_API_VERSION) {\n throw new Error(\n `Plugin requires API version ${apiVersion}, but engine supports ${PLUGIN_API_VERSION}`,\n );\n }\n }\n\n /**\n * Add a plugin programmatically (for testing or dynamic loading)\n */\n addPlugin(plugin: VulcnPlugin, config: Record<string, unknown> = {}): void {\n this.validatePlugin(plugin);\n this.plugins.push({\n plugin,\n config,\n source: \"custom\",\n enabled: true,\n });\n }\n\n /**\n * Initialize all plugins (call onInit hooks)\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Load payloads from plugins that provide them\n for (const loaded of this.plugins) {\n if (loaded.plugin.payloads) {\n const payloads =\n typeof loaded.plugin.payloads === \"function\"\n ? await loaded.plugin.payloads()\n : loaded.plugin.payloads;\n this.sharedPayloads.push(...payloads);\n }\n }\n\n // Call onInit hooks\n await this.callHook(\"onInit\", (hook, ctx) => hook(ctx));\n\n this.initialized = true;\n }\n\n /**\n * Destroy all plugins (call onDestroy hooks)\n */\n async destroy(): Promise<void> {\n await this.callHook(\"onDestroy\", (hook, ctx) => hook(ctx));\n this.plugins = [];\n this.sharedPayloads = [];\n this.sharedFindings = [];\n this.initialized = false;\n }\n\n /**\n * Get all loaded payloads\n */\n getPayloads(): RuntimePayload[] {\n return this.sharedPayloads;\n }\n\n /**\n * Get all collected findings\n */\n getFindings(): Finding[] {\n return this.sharedFindings;\n }\n\n /**\n * Add a finding (used by detectors)\n */\n addFinding(finding: Finding): void {\n this.sharedFindings.push(finding);\n }\n\n /**\n * Add payloads (used by loaders)\n */\n addPayloads(payloads: RuntimePayload[]): void {\n this.sharedPayloads.push(...payloads);\n }\n\n /**\n * Clear findings (for new run)\n */\n clearFindings(): void {\n this.sharedFindings = [];\n }\n\n /**\n * Get loaded plugins\n */\n getPlugins(): LoadedPlugin[] {\n return this.plugins;\n }\n\n /**\n * Check if a plugin is loaded by name\n */\n hasPlugin(name: string): boolean {\n return this.plugins.some((p) => p.plugin.name === name);\n }\n\n /**\n * Create base context for plugins\n */\n createContext(pluginConfig: Record<string, unknown>): PluginContext {\n const engineInfo: EngineInfo = {\n version: ENGINE_VERSION,\n pluginApiVersion: PLUGIN_API_VERSION,\n };\n\n return {\n config: pluginConfig,\n engine: engineInfo,\n payloads: this.sharedPayloads,\n findings: this.sharedFindings,\n addFinding: (finding: Finding) => {\n this.sharedFindings.push(finding);\n },\n logger: this.createLogger(\"plugin\"),\n fetch: globalThis.fetch,\n };\n }\n\n /**\n * Create scoped logger for a plugin\n */\n private createLogger(name: string): PluginLogger {\n const prefix = `[${name}]`;\n return {\n debug: (msg, ...args) => console.debug(prefix, msg, ...args),\n info: (msg, ...args) => console.info(prefix, msg, ...args),\n warn: (msg, ...args) => console.warn(prefix, msg, ...args),\n error: (msg, ...args) => console.error(prefix, msg, ...args),\n };\n }\n\n /**\n * Call a hook on all plugins sequentially\n */\n async callHook<K extends keyof PluginHooks>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<unknown>,\n ): Promise<void> {\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n await executor(hook as NonNullable<PluginHooks[K]>, ctx);\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n }\n\n /**\n * Call a hook and collect results\n */\n async callHookCollect<K extends keyof PluginHooks, R>(\n hookName: K,\n executor: (\n hook: NonNullable<PluginHooks[K]>,\n ctx: PluginContext,\n ) => Promise<R | R[] | null>,\n ): Promise<R[]> {\n const results: R[] = [];\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n const result = await executor(\n hook as NonNullable<PluginHooks[K]>,\n ctx,\n );\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n results.push(...result);\n } else {\n results.push(result);\n }\n }\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return results;\n }\n\n /**\n * Call a hook that transforms a value through the pipeline\n */\n async callHookPipe<T>(\n hookName: keyof PluginHooks,\n initial: T,\n executor: (\n hook: NonNullable<PluginHooks[typeof hookName]>,\n value: T,\n ctx: PluginContext,\n ) => Promise<T>,\n ): Promise<T> {\n let value = initial;\n\n for (const loaded of this.plugins) {\n const hook = loaded.plugin.hooks?.[hookName];\n if (hook) {\n const ctx = this.createContext(loaded.config);\n ctx.logger = this.createLogger(loaded.plugin.name);\n try {\n value = await executor(\n hook as NonNullable<PluginHooks[typeof hookName]>,\n value,\n ctx,\n );\n } catch (err) {\n console.error(\n `Error in plugin ${loaded.plugin.name}.${hookName}:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n }\n\n return value;\n }\n}\n\n/**\n * Default shared plugin manager instance\n */\nexport const pluginManager = new PluginManager();\n","/**\n * Vulcn Plugin System Types\n * @module @vulcn/engine/plugin\n *\n * The plugin system is driver-agnostic. Detection plugins receive\n * a generic page interface rather than Playwright types directly.\n * This allows the same plugin to work across different driver types.\n */\n\nimport type { z } from \"zod\";\nimport type { Session, Step } from \"./driver-types\";\nimport type { Finding } from \"./types\";\nimport type { RunResult } from \"./driver-types\";\nimport type { RuntimePayload, PayloadCategory } from \"./payload-types\";\n\n// Re-export for plugin authors\nexport type {\n Session,\n Step,\n Finding,\n RunResult,\n RuntimePayload,\n PayloadCategory,\n};\n\n/**\n * Plugin API version - plugins declare compatibility\n */\nexport const PLUGIN_API_VERSION = 1;\n\n/**\n * Plugin source types for identification\n */\nexport type PluginSource = \"builtin\" | \"npm\" | \"local\" | \"custom\";\n\n/**\n * Main plugin interface\n */\nexport interface VulcnPlugin {\n /** Unique plugin name (e.g., \"@vulcn/plugin-payloads\") */\n name: string;\n\n /** Plugin version (semver) */\n version: string;\n\n /** Plugin API version this plugin targets */\n apiVersion?: number;\n\n /** Human-readable description */\n description?: string;\n\n /** Lifecycle hooks */\n hooks?: PluginHooks;\n\n /**\n * Payloads provided by this plugin (Loaders)\n * Can be static array or async function for lazy loading\n */\n payloads?: RuntimePayload[] | (() => Promise<RuntimePayload[]>);\n\n /**\n * Zod schema for plugin configuration validation\n */\n configSchema?: z.ZodSchema;\n}\n\n/**\n * Plugin lifecycle hooks\n *\n * Detection hooks (onDialog, onConsoleMessage, etc.) receive\n * Playwright types from the driver. Plugins that use these\n * should declare playwright as a peer/dev dependency.\n */\nexport interface PluginHooks {\n // ─────────────────────────────────────────────────────────────────\n // Initialization\n // ─────────────────────────────────────────────────────────────────\n\n /**\n * Called when plugin is loaded, before any operation\n * Use for setup, loading payloads, etc.\n */\n onInit?: (ctx: PluginContext) => Promise<void>;\n\n /**\n * Called when plugin is unloaded/cleanup\n */\n onDestroy?: (ctx: PluginContext) => Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────\n // Recording Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when recording starts */\n onRecordStart?: (ctx: RecordContext) => Promise<void>;\n\n /** Called for each recorded step, can transform */\n onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;\n\n /** Called when recording ends, can transform session */\n onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;\n\n // ─────────────────────────────────────────────────────────────────\n // Running Phase\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when run starts */\n onRunStart?: (ctx: RunContext) => Promise<void>;\n\n /** Called before each payload is injected, can transform payload */\n onBeforePayload?: (\n payload: string,\n step: Step,\n ctx: RunContext,\n ) => Promise<string>;\n\n /** Called after payload injection, for detection */\n onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;\n\n /**\n * Called before the browser/driver is closed.\n * Plugins should await any pending async work here (e.g., flush\n * in-flight response handlers that need browser access).\n */\n onBeforeClose?: (ctx: PluginContext) => Promise<void>;\n\n /** Called when run ends, can transform results */\n onRunEnd?: (result: RunResult, ctx: RunContext) => Promise<RunResult>;\n\n // ─────────────────────────────────────────────────────────────────\n // Browser Event Hooks (Detection)\n // These receive driver-specific types (e.g. Playwright's Dialog)\n // ─────────────────────────────────────────────────────────────────\n\n /** Called when JavaScript alert/confirm/prompt appears */\n onDialog?: (dialog: unknown, ctx: DetectContext) => Promise<Finding | null>;\n\n /** Called on console.log/warn/error */\n onConsoleMessage?: (\n msg: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on page load/navigation */\n onPageLoad?: (page: unknown, ctx: DetectContext) => Promise<Finding[]>;\n\n /** Called on network request */\n onNetworkRequest?: (\n request: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n\n /** Called on network response */\n onNetworkResponse?: (\n response: unknown,\n ctx: DetectContext,\n ) => Promise<Finding | null>;\n}\n\n/**\n * Logger interface for plugins\n */\nexport interface PluginLogger {\n debug: (msg: string, ...args: unknown[]) => void;\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n error: (msg: string, ...args: unknown[]) => void;\n}\n\n/**\n * Engine information exposed to plugins\n */\nexport interface EngineInfo {\n version: string;\n pluginApiVersion: number;\n}\n\n/**\n * Base context available to all plugin hooks\n */\nexport interface PluginContext {\n /** Plugin-specific config from vulcn.config.yml */\n config: Record<string, unknown>;\n\n /** Engine information */\n engine: EngineInfo;\n\n /** Shared payload registry - loaders add payloads here */\n payloads: RuntimePayload[];\n\n /** Shared findings collection (read-only view, use addFinding to add) */\n findings: Finding[];\n\n /**\n * Add a finding through the proper callback chain.\n * Plugins should use this instead of pushing to findings[] directly,\n * so consumers (CLI, worker) get notified via onFinding callbacks.\n */\n addFinding: (finding: Finding) => void;\n\n /** Scoped logger */\n logger: PluginLogger;\n\n /** Fetch API for network requests */\n fetch: typeof fetch;\n}\n\n/**\n * Context for recording phase hooks\n */\nexport interface RecordContext extends PluginContext {\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n}\n\n/**\n * Context for running phase hooks\n */\nexport interface RunContext extends PluginContext {\n /** Session being executed */\n session: Session;\n\n /** Page interface (driver-specific, e.g. Playwright Page) */\n page: unknown;\n\n /** Whether running headless */\n headless: boolean;\n}\n\n/**\n * Context for detection hooks\n */\nexport interface DetectContext extends RunContext {\n /** Current step being tested */\n step: Step;\n\n /** Current payload set being tested */\n payloadSet: RuntimePayload;\n\n /** Actual payload value injected */\n payloadValue: string;\n\n /** Step ID for reporting */\n stepId: string;\n}\n\n/**\n * Plugin configuration in vulcn.config.yml\n */\nexport interface PluginConfig {\n /** Plugin name/path */\n name: string;\n\n /** Plugin-specific configuration */\n config?: Record<string, unknown>;\n\n /** Whether plugin is enabled (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Vulcn configuration file schema\n */\nexport interface VulcnConfig {\n /** Config version */\n version: string;\n\n /** Plugins to load */\n plugins?: PluginConfig[];\n\n /** Global settings */\n settings?: {\n headless?: boolean;\n timeout?: number;\n };\n}\n\n/**\n * Loaded plugin instance with resolved config\n */\nexport interface LoadedPlugin {\n /** Plugin definition */\n plugin: VulcnPlugin;\n\n /** Resolved configuration */\n config: Record<string, unknown>;\n\n /** Source of the plugin */\n source: PluginSource;\n\n /** Whether plugin is enabled */\n enabled: boolean;\n}\n"],"mappings":"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","names":["pluginManager","resolve","isAbsolute","isAbsolute","resolve"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulcn/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Fast, modern security testing engine — record browser sessions, replay with attack payloads, and detect vulnerabilities automatically. Pluggable driver and detection system for web application penetration testing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|