pentesting 0.73.14 → 0.90.1

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.
Files changed (70) hide show
  1. package/README.md +120 -49
  2. package/bin/pentesting.mjs +32 -0
  3. package/lib/runtime.mjs +419 -0
  4. package/package.json +17 -46
  5. package/scripts/postinstall.mjs +30 -0
  6. package/scripts/preflight-local.sh +24 -0
  7. package/dist/ad/prompt.md +0 -60
  8. package/dist/agent-tool-MMDCBQ74.js +0 -989
  9. package/dist/api/prompt.md +0 -63
  10. package/dist/chunk-4KLVUP3C.js +0 -11458
  11. package/dist/chunk-AEQNELCQ.js +0 -5930
  12. package/dist/chunk-YZNPWDNS.js +0 -1166
  13. package/dist/cloud/prompt.md +0 -49
  14. package/dist/container/prompt.md +0 -58
  15. package/dist/database/prompt.md +0 -58
  16. package/dist/email/prompt.md +0 -44
  17. package/dist/file-sharing/prompt.md +0 -56
  18. package/dist/ics/prompt.md +0 -76
  19. package/dist/main.d.ts +0 -1
  20. package/dist/main.js +0 -9737
  21. package/dist/network/prompt.md +0 -49
  22. package/dist/persistence-IGAKJZJ3.js +0 -13
  23. package/dist/process-registry-DNEZX4S5.js +0 -30
  24. package/dist/prompts/base.md +0 -436
  25. package/dist/prompts/ctf-crypto.md +0 -168
  26. package/dist/prompts/ctf-forensics.md +0 -182
  27. package/dist/prompts/ctf-pwn.md +0 -137
  28. package/dist/prompts/evasion.md +0 -215
  29. package/dist/prompts/exploit.md +0 -416
  30. package/dist/prompts/infra.md +0 -114
  31. package/dist/prompts/llm/analyst-system.md +0 -76
  32. package/dist/prompts/llm/context-extractor-system.md +0 -19
  33. package/dist/prompts/llm/input-processor-system.md +0 -64
  34. package/dist/prompts/llm/memory-synth-system.md +0 -14
  35. package/dist/prompts/llm/playbook-synthesizer-system.md +0 -10
  36. package/dist/prompts/llm/reflector-system.md +0 -16
  37. package/dist/prompts/llm/report-generator-system.md +0 -21
  38. package/dist/prompts/llm/strategist-fallback.md +0 -9
  39. package/dist/prompts/llm/triage-system.md +0 -47
  40. package/dist/prompts/main-agent.md +0 -193
  41. package/dist/prompts/offensive-playbook.md +0 -250
  42. package/dist/prompts/payload-craft.md +0 -181
  43. package/dist/prompts/post.md +0 -185
  44. package/dist/prompts/recon.md +0 -296
  45. package/dist/prompts/report.md +0 -98
  46. package/dist/prompts/strategist-system.md +0 -472
  47. package/dist/prompts/strategy.md +0 -163
  48. package/dist/prompts/techniques/README.md +0 -40
  49. package/dist/prompts/techniques/ad-attack.md +0 -261
  50. package/dist/prompts/techniques/auth-access.md +0 -256
  51. package/dist/prompts/techniques/container-escape.md +0 -103
  52. package/dist/prompts/techniques/crypto.md +0 -296
  53. package/dist/prompts/techniques/enterprise-pentest.md +0 -175
  54. package/dist/prompts/techniques/file-attacks.md +0 -144
  55. package/dist/prompts/techniques/forensics.md +0 -313
  56. package/dist/prompts/techniques/injection.md +0 -217
  57. package/dist/prompts/techniques/lateral.md +0 -128
  58. package/dist/prompts/techniques/network-svc.md +0 -229
  59. package/dist/prompts/techniques/pivoting.md +0 -205
  60. package/dist/prompts/techniques/privesc.md +0 -190
  61. package/dist/prompts/techniques/pwn.md +0 -595
  62. package/dist/prompts/techniques/reversing.md +0 -183
  63. package/dist/prompts/techniques/sandbox-escape.md +0 -73
  64. package/dist/prompts/techniques/shells.md +0 -194
  65. package/dist/prompts/vuln.md +0 -190
  66. package/dist/prompts/web.md +0 -318
  67. package/dist/prompts/zero-day.md +0 -298
  68. package/dist/remote-access/prompt.md +0 -52
  69. package/dist/web/prompt.md +0 -59
  70. package/dist/wireless/prompt.md +0 -62
package/README.md CHANGED
@@ -1,79 +1,150 @@
1
- <div align="center">
1
+ # pentesting
2
2
 
3
- <a href="https://agnusdei1207.github.io/brainscience/pentesting/">
4
- <img src="https://api.iconify.design/game-icons:fizzing-flask.svg?color=%232496ED" width="80" height="80" alt="Pentesting Agent" />
5
- </a>
3
+ `pentesting` is the public npm facade for the Builder runtime.
6
4
 
7
- # pentesting
8
- > **Autonomous Offensive Security AI Agent**
5
+ - Installs the `pentesting` CLI.
6
+ - Keeps `builder` as a temporary compatibility alias.
7
+ - Downloads the matching Builder binary from `agnusdei1207/builder` releases for the current OS/CPU.
8
+ - Performs only minimal wrapping before delegating execution to the Rust Builder binary.
9
+ - Does not ship a second JavaScript or TypeScript runtime.
9
10
 
10
- [![npm](https://img.shields.io/badge/npm-pentesting-CB3837?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/pentesting)
11
- [![docker](https://img.shields.io/badge/docker-pentesting-2496ED?style=flat-square&logo=docker&logoColor=white)](https://hub.docker.com/r/agnusdei1207/pentesting)
12
- [![docs](https://img.shields.io/badge/docs-brainscience-10B981?style=flat-square&logo=readthedocs&logoColor=white)](https://agnusdei1207.github.io/brainscience/pentesting/)
11
+ ## Install
13
12
 
14
- </div>
13
+ ```bash
14
+ npm install -g pentesting
15
+ pentesting
16
+ builder --version
17
+ ```
15
18
 
16
- ---
19
+ ## Quick commands
17
20
 
18
- ## TUI Demo
21
+ ```bash
22
+ pentesting
23
+ pentesting run "Enumerate the target web application and summarize the next actions."
24
+ pentesting scan 10.10.10.10
25
+ builder --version
26
+ ```
19
27
 
20
- ![TUI Demo](https://raw.githubusercontent.com/agnusdei1207/public/main/tui-demo.webp)
28
+ Inside the interactive TUI:
21
29
 
22
- ## Screenshots
30
+ ```text
31
+ /goal harden the npm release flow and verify Docker startup
32
+ /auto
33
+ ```
23
34
 
24
- | Recon | Exploit |
25
- |-------|---------|
26
- | ![Recon](https://raw.githubusercontent.com/agnusdei1207/public/main/01-recon.png) | ![Exploit](https://raw.githubusercontent.com/agnusdei1207/public/main/02-exploit.png) |
35
+ - `/goal <objective>` sets the active goal only.
36
+ - `/goal` with no text clears the goal and disables auto mode.
37
+ - `/auto` toggles auto mode for the current goal.
38
+ - `/auto` requires an existing goal and will not infer one from the transcript.
39
+ - `/auto <text>` is rejected with usage guidance; use `/goal <objective>` first, then `/auto`.
27
40
 
28
- | Privesc | Lateral Movement |
29
- |---------|-----------------|
30
- | ![Privesc](https://raw.githubusercontent.com/agnusdei1207/public/main/03-privesc.png) | ![Lateral](https://raw.githubusercontent.com/agnusdei1207/public/main/04-lateral.png) |
41
+ ## What npm installs
31
42
 
32
- ---
43
+ `npm install -g pentesting` installs a thin wrapper package and then runs `postinstall`:
44
+
45
+ - if `BUILDER_BIN` is already set, the wrapper reuses that Builder executable
46
+ - if `BUILDER_SKIP_DOWNLOAD=true`, the wrapper skips managed download entirely
47
+ - otherwise it downloads the matching public Builder release asset into the package-local managed binary directory and executes that binary on launch
48
+
49
+ This means the outside package name is `pentesting`, but the actual runtime engine is Builder.
50
+
51
+ ## Thin-wrapper design
52
+
53
+ The npm package is intentionally narrow.
54
+
55
+ - it installs a launcher, not a second agent runtime
56
+ - it resolves or downloads the correct Builder release asset
57
+ - it forwards execution into the Rust Builder binary
58
+ - it keeps only minimal compatibility translation for legacy `interactive`, `run`, and `scan` entrypoints
33
59
 
34
- ## Purpose
60
+ If a change would add orchestration, memory, monitoring, or prompt-management logic directly into the npm layer, that change belongs upstream in Builder instead.
35
61
 
36
- Autonomous network penetration testing and CTF assistant. Supports offensive security workflows including recon, exploit, and post-exploitation.
62
+ ## Runtime strengths exposed through pentesting
37
63
 
38
- ## Quick Start
64
+ Because `pentesting` delegates to the Rust Builder runtime, it inherits the same core capabilities:
39
65
 
40
- ### 🐳 Docker (Recommended)
66
+ - goal-aware TUI sessions through `/goal`
67
+ - explicit auto-mode toggling through `/auto`
68
+ - post-turn self-review and persistent local memory files
69
+ - context compression for long-running sessions
70
+ - workflow status, budget, proof, and verification signals in the TUI
71
+ - Docker and npm entrypoints that share one runtime instead of diverging implementations
72
+
73
+ ## Docker and compose alternative
74
+
75
+ If you do not want a managed local binary, use Builder directly through Docker:
41
76
 
42
77
  ```bash
43
78
  docker run -it --rm \
44
- -e PENTEST_API_KEY="your_key" \
45
- -e PENTEST_BASE_URL="https://api.z.ai/api/anthropic" \
46
- -e PENTEST_MODEL="glm-4.7" \
47
- -v pentesting-data:/tmp/.pentesting \
48
- agnusdei1207/pentesting
79
+ -v "$(pwd):/workspace" \
80
+ -w /workspace \
81
+ agnusdei1207/builder:latest
49
82
  ```
50
83
 
51
- Docker stores workspace state under `/tmp/.pentesting` inside the container.
52
- The entrypoint resets that directory on each fresh container start to avoid mixing stale state into a new run.
53
- Mount that path if you want access to `.pentesting/turns`, `.pentesting/sessions`, `.pentesting/memory`, and workspace artifacts after an OOM or while the same container is still alive.
54
-
55
- ### 🐉 Kali Linux (Native)
84
+ For a PostgreSQL-backed compose path, use the public `compose.yaml` facade from the public repo or mirrored docs:
56
85
 
57
86
  ```bash
58
- npm install -g pentesting
59
- export PENTEST_API_KEY="your_key"
60
- pentesting
87
+ docker compose up -d postgres
88
+ PENTESTING_PROJECT_DIR=/path/to/project docker compose run pentesting
61
89
  ```
62
90
 
63
- ### Environment Variables
91
+ ## Supported runtime targets
64
92
 
65
- | Variable | Required | Description |
66
- |----------|----------|-------------|
67
- | `PENTEST_API_KEY` | | LLM API key |
68
- | `PENTEST_BASE_URL` | | API endpoint (z.ai auto-enables web search) |
69
- | `PENTEST_MODEL` | | Model (default: `glm-4.7`) |
70
- | `SEARCH_API_KEY` | | External search key (not needed for z.ai) |
71
- | `PENTEST_TOR` | | Enable Tor (`true`, Docker only) |
72
- ---
93
+ | OS | CPU | Release asset |
94
+ | --- | --- | --- |
95
+ | Linux | x64 | `builder-x86_64-unknown-linux-musl` |
96
+ | Linux | arm64 | `builder-aarch64-unknown-linux-musl` |
97
+ | macOS | x64 | `builder-x86_64-apple-darwin` |
98
+ | macOS | arm64 | `builder-aarch64-apple-darwin` |
99
+ | Windows | x64 | `builder-x86_64-pc-windows-msvc.exe` |
100
+ | Windows | arm64 | `builder-aarch64-pc-windows-msvc.exe` |
101
+ | Android | arm64 | `builder-aarch64-linux-android` |
102
+
103
+ ## Environment variables
104
+
105
+ | Variable | Description |
106
+ | --- | --- |
107
+ | `BUILDER_BIN` | Use an already-installed Builder binary instead of the managed download. |
108
+ | `BUILDER_REPO` | Override the public release repo used for binary downloads. Defaults to `agnusdei1207/builder`. |
109
+ | `BUILDER_SKIP_DOWNLOAD` | Skip the postinstall binary download. Useful in CI or when `BUILDER_BIN` will be provided later. |
110
+
111
+ ## Compatibility wrappers
112
+
113
+ - `pentesting` / `pentesting interactive` starts Builder interactive mode.
114
+ - `pentesting run "<objective>"` becomes a Builder prompt tailored for the objective.
115
+ - `pentesting scan <target>` becomes a Builder prompt tailored for recon and pentest scanning.
116
+ - `builder` remains available as a compatibility alias during the naming transition.
117
+
118
+ ## Runtime behavior notes
119
+
120
+ - `pentesting` with no extra arguments starts the Builder interactive/default flow.
121
+ - `builder` and `pentesting` share the same wrapper entrypoint in the public package.
122
+ - Docker and npm both end up delegating to the same upstream Builder runtime, so differences are usually install/configuration problems rather than different products.
123
+
124
+ ## Runtime cautions
125
+
126
+ - `BUILDER_SKIP_DOWNLOAD=true` is only safe when you intentionally plan to provide `BUILDER_BIN` later or reinstall after provisioning access to the Builder release asset.
127
+ - The npm package does not publish Docker images itself. Docker users should treat `agnusdei1207/builder:latest` as the runtime image and `pentesting` as the public command/package facade.
128
+ - Unsupported OS/CPU pairs fail during release asset resolution before download.
129
+ - If the managed binary is missing at runtime, reinstall the package or set `BUILDER_BIN` explicitly.
130
+
131
+ ## Development and release from source
132
+
133
+ If you maintain this npm facade from the private source repository, use the root `builder-private` commands rather than adding more logic inside this package:
134
+
135
+ ```bash
136
+ npm run pentesting:status
137
+ npm run pentesting:verify
138
+ npm run pentesting:pack:dry-run
139
+ npm run pentesting:release:patch:dry-run
140
+ NPM_TOKEN=... npm run pentesting:publish -- 0.90.1
141
+ ```
73
142
 
74
- ## Issue
143
+ ## Further reading
75
144
 
76
- email: agnusdei1207@gmail.com
145
+ - public Builder runtime docs and Docker guidance
146
+ - the mirrored public `compose.yaml` facade for PostgreSQL-backed sessions
147
+ - the Builder release asset matching your OS/CPU target
77
148
 
78
149
  ---
79
150
 
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+
5
+ import { resolveBuilderBinary, translateBuilderInvocation } from "../lib/runtime.mjs";
6
+
7
+ async function main() {
8
+ const invocation = translateBuilderInvocation(process.argv.slice(2));
9
+ for (const warning of invocation.warnings) {
10
+ console.warn(`[pentesting] ${warning}`);
11
+ }
12
+
13
+ const builderBinary = await resolveBuilderBinary({ downloadIfMissing: true });
14
+ const result = spawnSync(builderBinary, invocation.builderArgs, {
15
+ stdio: "inherit",
16
+ env: process.env,
17
+ });
18
+
19
+ if (result.error) {
20
+ console.error(
21
+ `pentesting could not start Builder (${result.error.message}). Set BUILDER_BIN or reinstall the pentesting package.`,
22
+ );
23
+ process.exit(127);
24
+ }
25
+
26
+ process.exit(result.status ?? 1);
27
+ }
28
+
29
+ main().catch((error) => {
30
+ console.error(`[pentesting] ${error instanceof Error ? error.message : String(error)}`);
31
+ process.exit(1);
32
+ });
@@ -0,0 +1,419 @@
1
+ import { createWriteStream, readFileSync } from "node:fs";
2
+ import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { Readable } from "node:stream";
5
+ import { pipeline } from "node:stream/promises";
6
+ import process from "node:process";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
+ const PACKAGE_JSON = JSON.parse(
11
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
12
+ );
13
+ const MANAGED_BINARY_DIR = path.join(PACKAGE_ROOT, "vendor");
14
+ const MANAGED_BINARY_MANIFEST = path.join(MANAGED_BINARY_DIR, "builder-manifest.json");
15
+ const BUILDER_COMMANDS = new Set([
16
+ "agent",
17
+ "banner",
18
+ "cmd",
19
+ "commit",
20
+ "config",
21
+ "conversation",
22
+ "data",
23
+ "doctor",
24
+ "info",
25
+ "list",
26
+ "mcp",
27
+ "provider",
28
+ "setup",
29
+ "suggest",
30
+ "update",
31
+ "vscode",
32
+ "workspace",
33
+ "zsh",
34
+ ]);
35
+
36
+ const RELEASE_TARGETS = {
37
+ "android:arm64": {
38
+ assetName: "builder-aarch64-linux-android",
39
+ binaryFileName: "builder",
40
+ label: "android-arm64",
41
+ },
42
+ "darwin:arm64": {
43
+ assetName: "builder-aarch64-apple-darwin",
44
+ binaryFileName: "builder",
45
+ label: "macos-arm64",
46
+ },
47
+ "darwin:x64": {
48
+ assetName: "builder-x86_64-apple-darwin",
49
+ binaryFileName: "builder",
50
+ label: "macos-x64",
51
+ },
52
+ "linux:arm64": {
53
+ assetName: "builder-aarch64-unknown-linux-musl",
54
+ binaryFileName: "builder",
55
+ label: "linux-arm64",
56
+ },
57
+ "linux:x64": {
58
+ assetName: "builder-x86_64-unknown-linux-musl",
59
+ binaryFileName: "builder",
60
+ label: "linux-x64",
61
+ },
62
+ "win32:arm64": {
63
+ assetName: "builder-aarch64-pc-windows-msvc.exe",
64
+ binaryFileName: "builder.exe",
65
+ label: "windows-arm64",
66
+ },
67
+ "win32:x64": {
68
+ assetName: "builder-x86_64-pc-windows-msvc.exe",
69
+ binaryFileName: "builder.exe",
70
+ label: "windows-x64",
71
+ },
72
+ };
73
+
74
+ function parseLegacyFlags(argv) {
75
+ const passthrough = [];
76
+ const warnings = [];
77
+
78
+ for (let index = 0; index < argv.length; index += 1) {
79
+ const current = argv[index];
80
+
81
+ if (current === "--dangerously-skip-permissions") {
82
+ warnings.push(
83
+ "Legacy '--dangerously-skip-permissions' is ignored. Builder owns approval handling inside its runtime.",
84
+ );
85
+ continue;
86
+ }
87
+
88
+ if (current === "--target" || current === "-t") {
89
+ const target = argv[index + 1];
90
+ if (target) {
91
+ warnings.push(
92
+ `Legacy target '${target}' is not auto-injected into Builder interactive mode. Paste it as your first prompt after startup.`,
93
+ );
94
+ index += 1;
95
+ } else {
96
+ warnings.push("Legacy '--target' was provided without a value and was ignored.");
97
+ }
98
+ continue;
99
+ }
100
+
101
+ passthrough.push(current);
102
+ }
103
+
104
+ return { passthrough, warnings };
105
+ }
106
+
107
+ function parseOptionValue(argv, index, option) {
108
+ if (index + 1 >= argv.length) {
109
+ throw new Error(`Missing value for ${option}.`);
110
+ }
111
+ return argv[index + 1];
112
+ }
113
+
114
+ function buildRunPrompt(objective, options) {
115
+ const lines = [`Objective: ${objective}`];
116
+
117
+ if (options.target) {
118
+ lines.push(`Primary target: ${options.target}`);
119
+ }
120
+
121
+ lines.push("");
122
+ lines.push("Use Builder to execute the task.");
123
+
124
+ if (options.maxSteps) {
125
+ lines.push(
126
+ `Keep the workflow within roughly ${options.maxSteps} meaningful tool steps when it does not reduce quality.`,
127
+ );
128
+ }
129
+
130
+ if (options.output) {
131
+ lines.push(`Write the final deliverable to ${options.output}.`);
132
+ }
133
+
134
+ return lines.join("\n");
135
+ }
136
+
137
+ function buildScanPrompt(target, options) {
138
+ const lines = [`Perform a reconnaissance scan against ${target}.`];
139
+ lines.push(`Focus mode: ${options.scanType}.`);
140
+
141
+ if (options.ports) {
142
+ lines.push(`Prioritize ports: ${options.ports}.`);
143
+ }
144
+
145
+ if (options.output) {
146
+ lines.push(`Write the final deliverable to ${options.output}.`);
147
+ }
148
+
149
+ lines.push("Prefer Builder-native tool selection and summarize the most actionable findings first.");
150
+ return lines.join("\n");
151
+ }
152
+
153
+ function translateRun(argv) {
154
+ const warnings = [];
155
+ const options = {};
156
+ const objectiveParts = [];
157
+
158
+ for (let index = 0; index < argv.length; index += 1) {
159
+ const current = argv[index];
160
+
161
+ if (current === "--output" || current === "-o") {
162
+ options.output = parseOptionValue(argv, index, current);
163
+ index += 1;
164
+ continue;
165
+ }
166
+
167
+ if (current === "--max-steps") {
168
+ options.maxSteps = parseOptionValue(argv, index, current);
169
+ index += 1;
170
+ continue;
171
+ }
172
+
173
+ if (current === "--target" || current === "-t") {
174
+ options.target = parseOptionValue(argv, index, current);
175
+ index += 1;
176
+ continue;
177
+ }
178
+
179
+ if (current === "--dangerously-skip-permissions") {
180
+ warnings.push(
181
+ "Legacy '--dangerously-skip-permissions' is ignored. Builder owns approval handling inside its runtime.",
182
+ );
183
+ continue;
184
+ }
185
+
186
+ objectiveParts.push(current);
187
+ }
188
+
189
+ const objective = objectiveParts.join(" ").trim();
190
+ if (!objective) {
191
+ throw new Error("pentesting run requires an objective.");
192
+ }
193
+
194
+ return {
195
+ builderArgs: ["--prompt", buildRunPrompt(objective, options)],
196
+ warnings,
197
+ };
198
+ }
199
+
200
+ function translateScan(argv) {
201
+ const warnings = [];
202
+ const options = { scanType: "service" };
203
+ let target = "";
204
+
205
+ for (let index = 0; index < argv.length; index += 1) {
206
+ const current = argv[index];
207
+
208
+ if (current === "--scan-type" || current === "-s") {
209
+ options.scanType = parseOptionValue(argv, index, current);
210
+ index += 1;
211
+ continue;
212
+ }
213
+
214
+ if (current === "--ports" || current === "-p") {
215
+ options.ports = parseOptionValue(argv, index, current);
216
+ index += 1;
217
+ continue;
218
+ }
219
+
220
+ if (current === "--output" || current === "-o") {
221
+ options.output = parseOptionValue(argv, index, current);
222
+ index += 1;
223
+ continue;
224
+ }
225
+
226
+ if (current === "--dangerously-skip-permissions") {
227
+ warnings.push(
228
+ "Legacy '--dangerously-skip-permissions' is ignored. Builder owns approval handling inside its runtime.",
229
+ );
230
+ continue;
231
+ }
232
+
233
+ if (!target) {
234
+ target = current;
235
+ continue;
236
+ }
237
+
238
+ warnings.push(`Extra scan argument '${current}' was folded into the Builder prompt.`);
239
+ target = `${target} ${current}`;
240
+ }
241
+
242
+ if (!target) {
243
+ throw new Error("pentesting scan requires a target.");
244
+ }
245
+
246
+ return {
247
+ builderArgs: ["--prompt", buildScanPrompt(target, options)],
248
+ warnings,
249
+ };
250
+ }
251
+
252
+ export function packageVersion() {
253
+ return PACKAGE_JSON.version;
254
+ }
255
+
256
+ export function releaseRepository() {
257
+ return process.env.BUILDER_REPO || "agnusdei1207/builder";
258
+ }
259
+
260
+ export function defaultReleaseTag(version = packageVersion()) {
261
+ return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(version) ? `v${version}` : "latest";
262
+ }
263
+
264
+ export function resolveReleaseAsset(platform = process.platform, arch = process.arch) {
265
+ const target = RELEASE_TARGETS[`${platform}:${arch}`];
266
+ if (!target) {
267
+ const supported = Object.keys(RELEASE_TARGETS).join(", ");
268
+ throw new Error(
269
+ `Unsupported platform '${platform}/${arch}'. Supported builder targets: ${supported}.`,
270
+ );
271
+ }
272
+ return target;
273
+ }
274
+
275
+ export function releaseAssetUrl(assetName, options = {}) {
276
+ const repo = options.repo || releaseRepository();
277
+ const releaseTag = options.releaseTag || defaultReleaseTag();
278
+ if (releaseTag === "latest") {
279
+ return `https://github.com/${repo}/releases/latest/download/${assetName}`;
280
+ }
281
+ return `https://github.com/${repo}/releases/download/${releaseTag}/${assetName}`;
282
+ }
283
+
284
+ async function pathExists(filePath) {
285
+ try {
286
+ await stat(filePath);
287
+ return true;
288
+ } catch {
289
+ return false;
290
+ }
291
+ }
292
+
293
+ async function manifestMatches(expected) {
294
+ try {
295
+ const raw = JSON.parse(await readFile(MANAGED_BINARY_MANIFEST, "utf8"));
296
+ return (
297
+ raw.assetName === expected.assetName &&
298
+ raw.releaseTag === expected.releaseTag &&
299
+ raw.repo === expected.repo
300
+ );
301
+ } catch {
302
+ return false;
303
+ }
304
+ }
305
+
306
+ export async function installManagedBuilder(options = {}) {
307
+ if (process.env.BUILDER_BIN) {
308
+ return { binaryPath: process.env.BUILDER_BIN, source: "external" };
309
+ }
310
+
311
+ const target = resolveReleaseAsset();
312
+ const repo = options.repo || releaseRepository();
313
+ const releaseTag = options.releaseTag || defaultReleaseTag();
314
+ const binaryPath = path.join(MANAGED_BINARY_DIR, target.binaryFileName);
315
+ const manifest = { assetName: target.assetName, releaseTag, repo };
316
+
317
+ if (!options.force && (await pathExists(binaryPath)) && (await manifestMatches(manifest))) {
318
+ return { binaryPath, source: "cached", ...manifest };
319
+ }
320
+
321
+ if (process.env.BUILDER_SKIP_DOWNLOAD === "true") {
322
+ return { binaryPath, source: "skipped", ...manifest };
323
+ }
324
+
325
+ await mkdir(MANAGED_BINARY_DIR, { recursive: true });
326
+ const downloadUrl = releaseAssetUrl(target.assetName, { repo, releaseTag });
327
+ const response = await fetch(downloadUrl, {
328
+ headers: {
329
+ "user-agent": `pentesting-npm/${packageVersion()}`,
330
+ },
331
+ });
332
+
333
+ if (!response.ok || !response.body) {
334
+ throw new Error(
335
+ `Failed to download ${target.assetName} from ${downloadUrl} (${response.status} ${response.statusText}).`,
336
+ );
337
+ }
338
+
339
+ const tempPath = path.join(MANAGED_BINARY_DIR, `${target.binaryFileName}.download`);
340
+ await rm(tempPath, { force: true });
341
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(tempPath));
342
+ if (process.platform !== "win32") {
343
+ await chmod(tempPath, 0o755);
344
+ }
345
+ await rm(binaryPath, { force: true });
346
+ await rename(tempPath, binaryPath);
347
+ await writeFile(
348
+ MANAGED_BINARY_MANIFEST,
349
+ JSON.stringify(
350
+ {
351
+ ...manifest,
352
+ downloadedAt: new Date().toISOString(),
353
+ },
354
+ null,
355
+ 2,
356
+ ),
357
+ "utf8",
358
+ );
359
+
360
+ return { binaryPath, source: "downloaded", ...manifest };
361
+ }
362
+
363
+ export async function resolveBuilderBinary(options = {}) {
364
+ if (process.env.BUILDER_BIN) {
365
+ return process.env.BUILDER_BIN;
366
+ }
367
+
368
+ const target = resolveReleaseAsset();
369
+ const binaryPath = path.join(MANAGED_BINARY_DIR, target.binaryFileName);
370
+ if (await pathExists(binaryPath)) {
371
+ return binaryPath;
372
+ }
373
+
374
+ if (options.downloadIfMissing) {
375
+ const install = await installManagedBuilder();
376
+ if (install.source === "skipped") {
377
+ throw new Error(
378
+ "Managed Builder download was skipped and no BUILDER_BIN override was provided.",
379
+ );
380
+ }
381
+ return install.binaryPath;
382
+ }
383
+
384
+ throw new Error(
385
+ "No managed Builder binary is available. Reinstall the pentesting package or set BUILDER_BIN.",
386
+ );
387
+ }
388
+
389
+ export function translateBuilderInvocation(argv) {
390
+ if (argv.length === 0) {
391
+ return { builderArgs: [], warnings: [] };
392
+ }
393
+
394
+ const [command, ...rest] = argv;
395
+
396
+ if (command === "interactive" || command === "i") {
397
+ const parsed = parseLegacyFlags(rest);
398
+ return { builderArgs: parsed.passthrough, warnings: parsed.warnings };
399
+ }
400
+
401
+ if (command === "run" || command === "r") {
402
+ return translateRun(rest);
403
+ }
404
+
405
+ if (command === "scan") {
406
+ return translateScan(rest);
407
+ }
408
+
409
+ if (command.startsWith("-")) {
410
+ const parsed = parseLegacyFlags(argv);
411
+ return { builderArgs: parsed.passthrough, warnings: parsed.warnings };
412
+ }
413
+
414
+ if (BUILDER_COMMANDS.has(command)) {
415
+ return { builderArgs: argv, warnings: [] };
416
+ }
417
+
418
+ return { builderArgs: argv, warnings: [] };
419
+ }