vulcn 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d4fd4df: ### Breaking: Remove built-in payloads, PayloadBox is now the default
8
+
9
+ All hardcoded built-in payloads have been removed. Payloads are now fetched on demand from [PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings), the largest community-curated security payload collection.
10
+
11
+ **`@vulcn/engine`**
12
+ - Removed `"builtin"` from `PayloadSource` type — valid sources are now `"custom" | "payloadbox" | "plugin"`
13
+
14
+ **`@vulcn/plugin-payloads`**
15
+ - Removed all built-in payload sets and the `builtin`, `include`, `exclude`, `payloadbox` config options
16
+ - New config: `types` (short aliases), `limit`, `files`
17
+ - Short aliases for payload types: `xss`, `sqli`, `xxe`, `cmd`, `redirect`, `traversal`
18
+ - Removed legacy `payloadbox:` prefix — use short aliases directly
19
+
20
+ **`vulcn` (CLI)**
21
+ - Default payload changed from `xss-basic` to `xss` (PayloadBox)
22
+ - `vulcn payloads` now lists PayloadBox types with short aliases
23
+ - `vulcn run` help updated with payload type reference
24
+ - Auto-loads `@vulcn/plugin-detect-sqli` when `sqli` payloads are used
25
+
26
+ **`@vulcn/plugin-detect-sqli`**
27
+ - SQL injection detection plugin with error-based, response diffing, and timing-based strategies
28
+ - Auto-loaded by CLI when SQLi payloads are selected
29
+
30
+ ## 0.3.2
31
+
32
+ ### Patch Changes
33
+
34
+ - 51d69b7: ### Auto-Crawl: Automated Form Discovery & Session Generation
35
+
36
+ Adds a new **auto-crawl** capability to the browser driver — automatically discovers injectable forms, inputs, and submit buttons on a target URL, then generates ready-to-run `Session[]` objects. This replaces the need to manually record sessions for basic form testing.
37
+
38
+ #### `@vulcn/engine`
39
+ - **`CrawlOptions` type** — new interface for crawl configuration (`maxDepth`, `maxPages`, `pageTimeout`, `sameOrigin`, `onPageCrawled` callback)
40
+ - **`RecorderDriver.crawl()`** — optional method on the recorder interface, so only drivers that support auto-discovery need to implement it
41
+ - **`DriverManager.crawl()`** — new top-level method that dispatches to the driver's crawl implementation, with clear errors when a driver doesn't support it
42
+ - **Test coverage** — 4 new tests for the crawl flow (success, arg passthrough, missing driver, unsupported driver), coverage at 62.88%
43
+
44
+ #### `@vulcn/driver-browser`
45
+ - **`BrowserCrawler`** — new module (`crawler.ts`) that performs BFS-based crawling using Playwright:
46
+ - Discovers explicit `<form>` elements with their inputs and submit buttons
47
+ - Discovers standalone inputs not inside a `<form>` (common in SPAs)
48
+ - Identifies injectable text-like input types (text, search, url, email, tel, password, textarea)
49
+ - Finds submit triggers (submit buttons, untyped buttons, or falls back to Enter keypress)
50
+ - Follows same-origin links with configurable depth control
51
+ - Generates proper `navigate → input → submit` step sequences per form
52
+ - **`recorder.crawl()`** — wired into the browser driver's recorder interface
53
+ - **Exported** — `crawlAndBuildSessions` available for direct programmatic use
54
+
55
+ #### Architecture
56
+ - Removed standalone `@vulcn/crawler` package — crawler is now a core part of `@vulcn/driver-browser`, consistent with the driver-based architecture
57
+ - Cleaned up `pnpm-workspace.yaml` to remove the deleted crawler entry
58
+
3
59
  ## 0.3.1
4
60
 
5
61
  ### Patch Changes
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/run.ts
4
+ import { readFile } from "fs/promises";
5
+ import { DriverManager, PluginManager } from "@vulcn/engine";
6
+ import browserDriver from "@vulcn/driver-browser";
7
+ import chalk from "chalk";
8
+ import ora from "ora";
9
+ async function runCommand(sessionFile, options) {
10
+ const manager = new PluginManager();
11
+ await manager.loadConfig(options.config);
12
+ await manager.loadPlugins();
13
+ const drivers = new DriverManager();
14
+ drivers.register(browserDriver);
15
+ const loadSpinner = ora("Loading session...").start();
16
+ let sessionYaml;
17
+ try {
18
+ sessionYaml = await readFile(sessionFile, "utf-8");
19
+ } catch {
20
+ loadSpinner.fail(`Cannot read file: ${sessionFile}`);
21
+ process.exit(1);
22
+ }
23
+ let session;
24
+ try {
25
+ session = drivers.parseSession(sessionYaml, "browser");
26
+ loadSpinner.succeed(`Loaded session: ${chalk.cyan(session.name)}`);
27
+ } catch (err) {
28
+ loadSpinner.fail("Invalid session file");
29
+ console.error(chalk.red(String(err)));
30
+ process.exit(1);
31
+ }
32
+ if (options.payloadFile) {
33
+ const customSpinner = ora("Loading custom payloads...").start();
34
+ try {
35
+ const { loadFromFile } = await import("@vulcn/plugin-payloads");
36
+ const loaded = await loadFromFile(options.payloadFile);
37
+ manager.addPayloads(loaded);
38
+ customSpinner.succeed(
39
+ `Loaded ${chalk.cyan(loaded.length)} custom payload(s) from ${options.payloadFile}`
40
+ );
41
+ } catch (err) {
42
+ customSpinner.fail(`Failed to load custom payloads: ${err}`);
43
+ process.exit(1);
44
+ }
45
+ }
46
+ if (options.payload && options.payload.length > 0) {
47
+ const payloadSpinner = ora("Fetching payloads...").start();
48
+ try {
49
+ const { loadPayloadBox } = await import("@vulcn/plugin-payloads");
50
+ for (const name of options.payload) {
51
+ payloadSpinner.text = `Fetching ${name}...`;
52
+ const payload = await loadPayloadBox(name);
53
+ manager.addPayloads([payload]);
54
+ }
55
+ payloadSpinner.succeed(
56
+ `Loaded ${options.payload.length} payload type(s)`
57
+ );
58
+ } catch (err) {
59
+ payloadSpinner.fail(`Failed to load payloads: ${err}`);
60
+ process.exit(1);
61
+ }
62
+ }
63
+ if (manager.getPayloads().length === 0) {
64
+ const defaultSpinner = ora("Fetching default payloads (xss)...").start();
65
+ try {
66
+ const { loadPayloadBox } = await import("@vulcn/plugin-payloads");
67
+ const payload = await loadPayloadBox("xss");
68
+ manager.addPayloads([payload]);
69
+ defaultSpinner.succeed("Using default payloads: xss");
70
+ } catch (err) {
71
+ defaultSpinner.fail(`Failed to load default payloads: ${err}`);
72
+ process.exit(1);
73
+ }
74
+ }
75
+ if (!manager.hasPlugin("@vulcn/plugin-detect-xss")) {
76
+ const detectSpinner = ora("Loading XSS detection plugin...").start();
77
+ try {
78
+ const detectXssPlugin = await import("@vulcn/plugin-detect-xss");
79
+ manager.addPlugin(detectXssPlugin.default);
80
+ detectSpinner.succeed("Loaded XSS detection plugin");
81
+ } catch (err) {
82
+ detectSpinner.fail(`Failed to load detect-xss plugin: ${err}`);
83
+ }
84
+ }
85
+ const hasSqliPayloads = (options.payload ?? []).some((p) => {
86
+ const lower = p.toLowerCase();
87
+ return lower === "sqli" || lower === "sql" || lower === "sql-injection" || lower.includes("sql");
88
+ });
89
+ if (hasSqliPayloads && !manager.hasPlugin("@vulcn/plugin-detect-sqli")) {
90
+ const sqliSpinner = ora("Loading SQLi detection plugin...").start();
91
+ try {
92
+ const detectSqliPlugin = await import("@vulcn/plugin-detect-sqli");
93
+ manager.addPlugin(detectSqliPlugin.default);
94
+ sqliSpinner.succeed("Loaded SQLi detection plugin");
95
+ } catch (err) {
96
+ sqliSpinner.fail(`Failed to load detect-sqli plugin: ${err}`);
97
+ }
98
+ }
99
+ if (options.report) {
100
+ const reportSpinner = ora("Loading report plugin...").start();
101
+ try {
102
+ const reportPlugin = await import("@vulcn/plugin-report");
103
+ const outputDir = options.reportOutput || ".";
104
+ manager.addPlugin(reportPlugin.default, {
105
+ format: options.report,
106
+ outputDir,
107
+ filename: "vulcn-report",
108
+ open: options.report === "html" || options.report === "all"
109
+ });
110
+ reportSpinner.succeed(
111
+ `Report plugin loaded (format: ${chalk.cyan(options.report)})`
112
+ );
113
+ } catch (err) {
114
+ reportSpinner.fail(`Failed to load report plugin: ${err}`);
115
+ }
116
+ }
117
+ const payloads = manager.getPayloads();
118
+ console.log();
119
+ console.log(chalk.cyan("\u{1F50D} Running security tests"));
120
+ console.log(chalk.gray(` Session: ${session.name}`));
121
+ console.log(
122
+ chalk.gray(` Payloads: ${payloads.map((p) => p.name).join(", ")}`)
123
+ );
124
+ console.log(
125
+ chalk.gray(
126
+ ` Payload count: ${payloads.reduce((sum, p) => sum + p.payloads.length, 0)}`
127
+ )
128
+ );
129
+ console.log(chalk.gray(` Browser: ${options.browser}`));
130
+ console.log(chalk.gray(` Headless: ${options.headless}`));
131
+ console.log();
132
+ const runSpinner = ora("Executing tests...").start();
133
+ try {
134
+ const result = await drivers.execute(session, manager, {
135
+ headless: options.headless,
136
+ onFinding: (finding) => {
137
+ runSpinner.stop();
138
+ console.log(chalk.red(`\u26A0\uFE0F FINDING: ${finding.title}`));
139
+ console.log(chalk.gray(` Step: ${finding.stepId}`));
140
+ console.log(
141
+ chalk.gray(` Payload: ${finding.payload.slice(0, 50)}...`)
142
+ );
143
+ console.log(chalk.gray(` URL: ${finding.url}`));
144
+ console.log();
145
+ runSpinner.start("Continuing tests...");
146
+ }
147
+ });
148
+ runSpinner.succeed("Tests completed");
149
+ console.log();
150
+ console.log(chalk.cyan("\u{1F4CA} Results"));
151
+ console.log(chalk.gray(` Steps executed: ${result.stepsExecuted}`));
152
+ console.log(chalk.gray(` Payloads tested: ${result.payloadsTested}`));
153
+ console.log(
154
+ chalk.gray(` Duration: ${(result.duration / 1e3).toFixed(1)}s`)
155
+ );
156
+ console.log();
157
+ if (result.findings.length > 0) {
158
+ console.log(chalk.red(`\u{1F6A8} ${result.findings.length} findings detected!`));
159
+ console.log();
160
+ for (const finding of result.findings) {
161
+ const severityColor = finding.severity === "critical" || finding.severity === "high" ? chalk.red : finding.severity === "medium" ? chalk.yellow : chalk.gray;
162
+ console.log(
163
+ severityColor(`[${finding.severity.toUpperCase()}] ${finding.title}`)
164
+ );
165
+ console.log(chalk.gray(` Type: ${finding.type}`));
166
+ console.log(chalk.gray(` Step: ${finding.stepId}`));
167
+ console.log(chalk.gray(` URL: ${finding.url}`));
168
+ console.log(chalk.gray(` Payload: ${finding.payload}`));
169
+ console.log();
170
+ }
171
+ process.exit(1);
172
+ } else {
173
+ console.log(chalk.green("\u2705 No vulnerabilities detected"));
174
+ }
175
+ if (result.errors.length > 0) {
176
+ console.log();
177
+ console.log(
178
+ chalk.yellow(`\u26A0\uFE0F ${result.errors.length} errors during execution:`)
179
+ );
180
+ for (const err of result.errors.slice(0, 5)) {
181
+ console.log(chalk.gray(` - ${err}`));
182
+ }
183
+ if (result.errors.length > 5) {
184
+ console.log(chalk.gray(` ... and ${result.errors.length - 5} more`));
185
+ }
186
+ }
187
+ } catch (err) {
188
+ runSpinner.fail("Test execution failed");
189
+ console.error(chalk.red(String(err)));
190
+ process.exit(1);
191
+ }
192
+ }
193
+
194
+ export {
195
+ runCommand
196
+ };
197
+ //# sourceMappingURL=chunk-RLPNYI6Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/run.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { DriverManager, PluginManager } from \"@vulcn/engine\";\nimport browserDriver from \"@vulcn/driver-browser\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\n\ninterface RunOptions {\n payload?: string[];\n payloadFile?: string;\n browser: string;\n headless: boolean;\n config?: string;\n report?: string;\n reportOutput?: string;\n}\n\nexport async function runCommand(sessionFile: string, options: RunOptions) {\n // Create plugin manager for this run\n const manager = new PluginManager();\n\n // Load config from file if present\n await manager.loadConfig(options.config);\n\n // Load plugins from config\n await manager.loadPlugins();\n\n // Set up driver manager with browser driver\n const drivers = new DriverManager();\n drivers.register(browserDriver);\n\n // Load session\n const loadSpinner = ora(\"Loading session...\").start();\n\n let sessionYaml: string;\n try {\n sessionYaml = await readFile(sessionFile, \"utf-8\");\n } catch {\n loadSpinner.fail(`Cannot read file: ${sessionFile}`);\n process.exit(1);\n }\n\n // Parse session — supports both legacy and driver-based formats\n let session;\n try {\n session = drivers.parseSession(sessionYaml, \"browser\");\n loadSpinner.succeed(`Loaded session: ${chalk.cyan(session.name)}`);\n } catch (err) {\n loadSpinner.fail(\"Invalid session file\");\n console.error(chalk.red(String(err)));\n process.exit(1);\n }\n\n // Add payloads from custom file\n if (options.payloadFile) {\n const customSpinner = ora(\"Loading custom payloads...\").start();\n try {\n const { loadFromFile } = await import(\"@vulcn/plugin-payloads\");\n const loaded = await loadFromFile(options.payloadFile);\n manager.addPayloads(loaded);\n customSpinner.succeed(\n `Loaded ${chalk.cyan(loaded.length)} custom payload(s) from ${options.payloadFile}`,\n );\n } catch (err) {\n customSpinner.fail(`Failed to load custom payloads: ${err}`);\n process.exit(1);\n }\n }\n\n // Load payload types from --payload flag\n if (options.payload && options.payload.length > 0) {\n const payloadSpinner = ora(\"Fetching payloads...\").start();\n try {\n const { loadPayloadBox } = await import(\"@vulcn/plugin-payloads\");\n\n for (const name of options.payload) {\n payloadSpinner.text = `Fetching ${name}...`;\n const payload = await loadPayloadBox(name);\n manager.addPayloads([payload]);\n }\n payloadSpinner.succeed(\n `Loaded ${options.payload.length} payload type(s)`,\n );\n } catch (err) {\n payloadSpinner.fail(`Failed to load payloads: ${err}`);\n process.exit(1);\n }\n }\n\n // If no payloads loaded yet, default to XSS\n if (manager.getPayloads().length === 0) {\n const defaultSpinner = ora(\"Fetching default payloads (xss)...\").start();\n try {\n const { loadPayloadBox } = await import(\"@vulcn/plugin-payloads\");\n const payload = await loadPayloadBox(\"xss\");\n manager.addPayloads([payload]);\n defaultSpinner.succeed(\"Using default payloads: xss\");\n } catch (err) {\n defaultSpinner.fail(`Failed to load default payloads: ${err}`);\n process.exit(1);\n }\n }\n\n // Auto-load XSS detection plugin\n if (!manager.hasPlugin(\"@vulcn/plugin-detect-xss\")) {\n const detectSpinner = ora(\"Loading XSS detection plugin...\").start();\n try {\n const detectXssPlugin = await import(\"@vulcn/plugin-detect-xss\");\n manager.addPlugin(detectXssPlugin.default);\n detectSpinner.succeed(\"Loaded XSS detection plugin\");\n } catch (err) {\n detectSpinner.fail(`Failed to load detect-xss plugin: ${err}`);\n }\n }\n\n // Auto-load SQLi detection plugin when sqli payloads are used\n const hasSqliPayloads = (options.payload ?? []).some((p) => {\n const lower = p.toLowerCase();\n return (\n lower === \"sqli\" ||\n lower === \"sql\" ||\n lower === \"sql-injection\" ||\n lower.includes(\"sql\")\n );\n });\n if (hasSqliPayloads && !manager.hasPlugin(\"@vulcn/plugin-detect-sqli\")) {\n const sqliSpinner = ora(\"Loading SQLi detection plugin...\").start();\n try {\n const detectSqliPlugin = await import(\"@vulcn/plugin-detect-sqli\");\n manager.addPlugin(detectSqliPlugin.default);\n sqliSpinner.succeed(\"Loaded SQLi detection plugin\");\n } catch (err) {\n sqliSpinner.fail(`Failed to load detect-sqli plugin: ${err}`);\n }\n }\n\n // Load report plugin if --report is specified\n if (options.report) {\n const reportSpinner = ora(\"Loading report plugin...\").start();\n try {\n const reportPlugin = await import(\"@vulcn/plugin-report\");\n\n // Determine output path from --report-output or default\n const outputDir = options.reportOutput || \".\";\n\n manager.addPlugin(reportPlugin.default, {\n format: options.report,\n outputDir,\n filename: \"vulcn-report\",\n open: options.report === \"html\" || options.report === \"all\",\n });\n reportSpinner.succeed(\n `Report plugin loaded (format: ${chalk.cyan(options.report)})`,\n );\n } catch (err) {\n reportSpinner.fail(`Failed to load report plugin: ${err}`);\n }\n }\n\n const payloads = manager.getPayloads();\n\n console.log();\n console.log(chalk.cyan(\"🔍 Running security tests\"));\n console.log(chalk.gray(` Session: ${session.name}`));\n console.log(\n chalk.gray(` Payloads: ${payloads.map((p) => p.name).join(\", \")}`),\n );\n console.log(\n chalk.gray(\n ` Payload count: ${payloads.reduce((sum, p) => sum + p.payloads.length, 0)}`,\n ),\n );\n console.log(chalk.gray(` Browser: ${options.browser}`));\n console.log(chalk.gray(` Headless: ${options.headless}`));\n console.log();\n\n const runSpinner = ora(\"Executing tests...\").start();\n\n try {\n const result = await drivers.execute(session, manager, {\n headless: options.headless,\n onFinding: (finding) => {\n runSpinner.stop();\n console.log(chalk.red(`⚠️ FINDING: ${finding.title}`));\n console.log(chalk.gray(` Step: ${finding.stepId}`));\n console.log(\n chalk.gray(` Payload: ${finding.payload.slice(0, 50)}...`),\n );\n console.log(chalk.gray(` URL: ${finding.url}`));\n console.log();\n runSpinner.start(\"Continuing tests...\");\n },\n });\n\n runSpinner.succeed(\"Tests completed\");\n console.log();\n\n // Summary\n console.log(chalk.cyan(\"📊 Results\"));\n console.log(chalk.gray(` Steps executed: ${result.stepsExecuted}`));\n console.log(chalk.gray(` Payloads tested: ${result.payloadsTested}`));\n console.log(\n chalk.gray(` Duration: ${(result.duration / 1000).toFixed(1)}s`),\n );\n console.log();\n\n if (result.findings.length > 0) {\n console.log(chalk.red(`🚨 ${result.findings.length} findings detected!`));\n console.log();\n for (const finding of result.findings) {\n const severityColor =\n finding.severity === \"critical\" || finding.severity === \"high\"\n ? chalk.red\n : finding.severity === \"medium\"\n ? chalk.yellow\n : chalk.gray;\n\n console.log(\n severityColor(`[${finding.severity.toUpperCase()}] ${finding.title}`),\n );\n console.log(chalk.gray(` Type: ${finding.type}`));\n console.log(chalk.gray(` Step: ${finding.stepId}`));\n console.log(chalk.gray(` URL: ${finding.url}`));\n console.log(chalk.gray(` Payload: ${finding.payload}`));\n console.log();\n }\n process.exit(1); // Non-zero exit for CI/CD\n } else {\n console.log(chalk.green(\"✅ No vulnerabilities detected\"));\n }\n\n if (result.errors.length > 0) {\n console.log();\n console.log(\n chalk.yellow(`⚠️ ${result.errors.length} errors during execution:`),\n );\n for (const err of result.errors.slice(0, 5)) {\n console.log(chalk.gray(` - ${err}`));\n }\n if (result.errors.length > 5) {\n console.log(chalk.gray(` ... and ${result.errors.length - 5} more`));\n }\n }\n } catch (err) {\n runSpinner.fail(\"Test execution failed\");\n console.error(chalk.red(String(err)));\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}