pentesting 0.16.7 → 0.16.8
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/README.md +45 -0
- package/dist/main.js +329 -147
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,51 @@ export PENTEST_MODEL="glm-5"
|
|
|
30
30
|
|
|
31
31
|
pentesting
|
|
32
32
|
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Environment Variables
|
|
37
|
+
|
|
38
|
+
### Required (LLM)
|
|
39
|
+
|
|
40
|
+
| Variable | Description | Example |
|
|
41
|
+
|----------|-------------|---------|
|
|
42
|
+
| `PENTEST_API_KEY` | API key for LLM provider | `your_api_key` |
|
|
43
|
+
| `PENTEST_BASE_URL` | API endpoint URL | `https://api.z.ai/api/anthropic` |
|
|
44
|
+
| `PENTEST_MODEL` | Model name | `glm-5` |
|
|
45
|
+
|
|
46
|
+
### Optional (Web Search API)
|
|
47
|
+
|
|
48
|
+
| Variable | Description | Example |
|
|
49
|
+
|----------|-------------|---------|
|
|
50
|
+
| `SEARCH_API_KEY` | Search API key | `your_api_key` |
|
|
51
|
+
| `SEARCH_API_URL` | Search API endpoint | `https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro` |
|
|
52
|
+
| `BROWSER_HEADLESS` | Set `false` to show browser for captcha | `false` |
|
|
53
|
+
|
|
54
|
+
**Supported Search APIs:**
|
|
55
|
+
|
|
56
|
+
| Provider | SEARCH_API_URL |
|
|
57
|
+
|----------|----------------|
|
|
58
|
+
| GLM (Zhipu) | `https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro` |
|
|
59
|
+
| Brave | `https://api.search.brave.com/res/v1/web/search` |
|
|
60
|
+
| Serper | `https://google.serper.dev/search` |
|
|
61
|
+
|
|
62
|
+
### Example `.env`
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# LLM Configuration
|
|
66
|
+
PENTEST_API_KEY=your_api_key_here
|
|
67
|
+
PENTEST_BASE_URL=https://api.z.ai/api/anthropic
|
|
68
|
+
PENTEST_MODEL=glm-5
|
|
69
|
+
|
|
70
|
+
# Web Search API
|
|
71
|
+
SEARCH_API_KEY=your_api_key
|
|
72
|
+
SEARCH_API_URL=https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro
|
|
73
|
+
|
|
74
|
+
# Browser (optional - for captcha handling)
|
|
75
|
+
BROWSER_HEADLESS=false
|
|
76
|
+
```
|
|
77
|
+
|
|
33
78
|
---
|
|
34
79
|
|
|
35
80
|
## Issue Report
|
package/dist/main.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/platform/tui/main.tsx
|
|
4
10
|
import { render } from "ink";
|
|
@@ -2173,6 +2179,107 @@ All ports freed. All children killed.`
|
|
|
2173
2179
|
|
|
2174
2180
|
// src/engine/tools-mid.ts
|
|
2175
2181
|
import { execFileSync } from "child_process";
|
|
2182
|
+
|
|
2183
|
+
// src/shared/utils/config.ts
|
|
2184
|
+
import path from "path";
|
|
2185
|
+
import { fileURLToPath } from "url";
|
|
2186
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
2187
|
+
var __dirname = path.dirname(__filename);
|
|
2188
|
+
function getApiKey() {
|
|
2189
|
+
return process.env.PENTEST_API_KEY || "";
|
|
2190
|
+
}
|
|
2191
|
+
function getBaseUrl() {
|
|
2192
|
+
return process.env.PENTEST_BASE_URL || void 0;
|
|
2193
|
+
}
|
|
2194
|
+
function getModel() {
|
|
2195
|
+
return process.env.PENTEST_MODEL || "";
|
|
2196
|
+
}
|
|
2197
|
+
function getSearchApiKey() {
|
|
2198
|
+
return process.env.SEARCH_API_KEY;
|
|
2199
|
+
}
|
|
2200
|
+
function getSearchApiUrl() {
|
|
2201
|
+
return process.env.SEARCH_API_URL;
|
|
2202
|
+
}
|
|
2203
|
+
function isBrowserHeadless() {
|
|
2204
|
+
return process.env.BROWSER_HEADLESS !== "false";
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// src/shared/utils/debug-logger.ts
|
|
2208
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2209
|
+
import { join as join2 } from "path";
|
|
2210
|
+
var DebugLogger = class _DebugLogger {
|
|
2211
|
+
static instance;
|
|
2212
|
+
logPath;
|
|
2213
|
+
initialized = false;
|
|
2214
|
+
constructor(clearOnInit = false) {
|
|
2215
|
+
const debugDir = join2(process.cwd(), ".pentesting", "debug");
|
|
2216
|
+
try {
|
|
2217
|
+
if (!existsSync3(debugDir)) {
|
|
2218
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
2219
|
+
}
|
|
2220
|
+
this.logPath = join2(debugDir, "debug.log");
|
|
2221
|
+
if (clearOnInit) {
|
|
2222
|
+
this.clear();
|
|
2223
|
+
}
|
|
2224
|
+
this.initialized = true;
|
|
2225
|
+
this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
|
|
2226
|
+
this.log("general", `Log file: ${this.logPath}`);
|
|
2227
|
+
} catch (e) {
|
|
2228
|
+
console.error("[DebugLogger] Failed to initialize:", e);
|
|
2229
|
+
this.logPath = "";
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
static getInstance(clearOnInit = false) {
|
|
2233
|
+
if (!_DebugLogger.instance) {
|
|
2234
|
+
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
2235
|
+
}
|
|
2236
|
+
return _DebugLogger.instance;
|
|
2237
|
+
}
|
|
2238
|
+
log(category, message, data) {
|
|
2239
|
+
if (!this.initialized || !this.logPath) return;
|
|
2240
|
+
try {
|
|
2241
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2242
|
+
let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
|
|
2243
|
+
if (data !== void 0) {
|
|
2244
|
+
logLine += ` | ${JSON.stringify(data)}`;
|
|
2245
|
+
}
|
|
2246
|
+
logLine += "\n";
|
|
2247
|
+
appendFileSync2(this.logPath, logLine);
|
|
2248
|
+
} catch (e) {
|
|
2249
|
+
console.error("[DebugLogger] Write error:", e);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
logRaw(category, label, raw) {
|
|
2253
|
+
if (!this.initialized || !this.logPath) return;
|
|
2254
|
+
try {
|
|
2255
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2256
|
+
const logLine = `[${timestamp}] [${category.toUpperCase()}] ${label}:
|
|
2257
|
+
${raw}
|
|
2258
|
+
---
|
|
2259
|
+
`;
|
|
2260
|
+
appendFileSync2(this.logPath, logLine);
|
|
2261
|
+
} catch (e) {
|
|
2262
|
+
console.error("[DebugLogger] Write error:", e);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
clear() {
|
|
2266
|
+
if (!this.logPath) return;
|
|
2267
|
+
try {
|
|
2268
|
+
writeFileSync3(this.logPath, "");
|
|
2269
|
+
} catch (e) {
|
|
2270
|
+
console.error("[DebugLogger] Clear error:", e);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
var logger = DebugLogger.getInstance(false);
|
|
2275
|
+
function initDebugLogger() {
|
|
2276
|
+
DebugLogger.instance = new DebugLogger(true);
|
|
2277
|
+
}
|
|
2278
|
+
function debugLog(category, message, data) {
|
|
2279
|
+
logger.log(category, message, data);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/engine/tools-mid.ts
|
|
2176
2283
|
function getErrorMessage(error) {
|
|
2177
2284
|
return error instanceof Error ? error.message : String(error);
|
|
2178
2285
|
}
|
|
@@ -2276,32 +2383,39 @@ async function searchExploitDB(service, version) {
|
|
|
2276
2383
|
};
|
|
2277
2384
|
}
|
|
2278
2385
|
}
|
|
2279
|
-
async function webSearch(query,
|
|
2386
|
+
async function webSearch(query, _engine) {
|
|
2387
|
+
debugLog("search", "webSearch START", { query });
|
|
2280
2388
|
try {
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2389
|
+
const apiKey = getSearchApiKey();
|
|
2390
|
+
const apiUrl = getSearchApiUrl();
|
|
2391
|
+
debugLog("search", "Search API config", {
|
|
2392
|
+
hasApiKey: !!apiKey,
|
|
2393
|
+
apiUrl,
|
|
2394
|
+
apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + "..." : null
|
|
2395
|
+
});
|
|
2396
|
+
if (!apiKey || !apiUrl) {
|
|
2397
|
+
debugLog("search", "Search API NOT CONFIGURED");
|
|
2398
|
+
return {
|
|
2399
|
+
success: false,
|
|
2400
|
+
output: "",
|
|
2401
|
+
error: "Search API not configured. Set SEARCH_API_KEY and SEARCH_API_URL environment variables."
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
if (apiUrl.includes("bigmodel.cn") || apiUrl.includes("zhipuai")) {
|
|
2405
|
+
debugLog("search", "Using GLM search");
|
|
2406
|
+
return await searchWithGLM(query, apiKey, apiUrl);
|
|
2407
|
+
} else if (apiUrl.includes("brave.com")) {
|
|
2408
|
+
debugLog("search", "Using Brave search");
|
|
2409
|
+
return await searchWithBrave(query, apiKey, apiUrl);
|
|
2410
|
+
} else if (apiUrl.includes("serper.dev")) {
|
|
2411
|
+
debugLog("search", "Using Serper search");
|
|
2412
|
+
return await searchWithSerper(query, apiKey, apiUrl);
|
|
2296
2413
|
} else {
|
|
2297
|
-
|
|
2414
|
+
debugLog("search", "Using generic search API");
|
|
2415
|
+
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
2298
2416
|
}
|
|
2299
|
-
const formatted = results.map((r, i) => `${i + 1}. ${r}`).join("\n");
|
|
2300
|
-
return {
|
|
2301
|
-
success: true,
|
|
2302
|
-
output: formatted || `No results found for: ${query}`
|
|
2303
|
-
};
|
|
2304
2417
|
} catch (error) {
|
|
2418
|
+
debugLog("search", "webSearch ERROR", { error: getErrorMessage(error) });
|
|
2305
2419
|
return {
|
|
2306
2420
|
success: false,
|
|
2307
2421
|
output: "",
|
|
@@ -2309,11 +2423,134 @@ async function webSearch(query, engine = "duckduckgo") {
|
|
|
2309
2423
|
};
|
|
2310
2424
|
}
|
|
2311
2425
|
}
|
|
2426
|
+
async function searchWithGLM(query, apiKey, apiUrl) {
|
|
2427
|
+
debugLog("search", "GLM request START", { apiUrl, query });
|
|
2428
|
+
const requestBody = {
|
|
2429
|
+
model: "web-search-pro",
|
|
2430
|
+
messages: [{ role: "user", content: query }],
|
|
2431
|
+
stream: false
|
|
2432
|
+
};
|
|
2433
|
+
debugLog("search", "GLM request body", requestBody);
|
|
2434
|
+
const response = await fetch(apiUrl, {
|
|
2435
|
+
method: "POST",
|
|
2436
|
+
headers: {
|
|
2437
|
+
"Content-Type": "application/json",
|
|
2438
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2439
|
+
},
|
|
2440
|
+
body: JSON.stringify(requestBody)
|
|
2441
|
+
});
|
|
2442
|
+
debugLog("search", "GLM response status", { status: response.status, ok: response.ok });
|
|
2443
|
+
if (!response.ok) {
|
|
2444
|
+
const errorText = await response.text();
|
|
2445
|
+
debugLog("search", "GLM response ERROR", { status: response.status, error: errorText });
|
|
2446
|
+
throw new Error(`GLM Search API error: ${response.status} - ${errorText}`);
|
|
2447
|
+
}
|
|
2448
|
+
const data = await response.json();
|
|
2449
|
+
debugLog("search", "GLM response data", { hasChoices: !!data.choices, choicesCount: data.choices?.length });
|
|
2450
|
+
let results = "";
|
|
2451
|
+
if (data.choices?.[0]?.message?.content) {
|
|
2452
|
+
results = data.choices[0].message.content;
|
|
2453
|
+
debugLog("search", "GLM extracted from content", { contentLength: results.length });
|
|
2454
|
+
} else if (data.choices?.[0]?.message?.tool_calls) {
|
|
2455
|
+
debugLog("search", "GLM has tool_calls", { count: data.choices[0].message.tool_calls.length });
|
|
2456
|
+
for (const tc of data.choices[0].message.tool_calls) {
|
|
2457
|
+
if (tc.function?.arguments) {
|
|
2458
|
+
try {
|
|
2459
|
+
const args = JSON.parse(tc.function.arguments);
|
|
2460
|
+
results += JSON.stringify(args, null, 2) + "\n";
|
|
2461
|
+
} catch {
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
debugLog("search", "GLM search COMPLETE", { resultsLength: results.length });
|
|
2467
|
+
return {
|
|
2468
|
+
success: true,
|
|
2469
|
+
output: results || `No results found for: ${query}`
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
async function searchWithBrave(query, apiKey, apiUrl) {
|
|
2473
|
+
debugLog("search", "Brave request START", { apiUrl, query });
|
|
2474
|
+
const url = `${apiUrl}?q=${encodeURIComponent(query)}&count=10`;
|
|
2475
|
+
debugLog("search", "Brave request URL", { url });
|
|
2476
|
+
const response = await fetch(url, {
|
|
2477
|
+
headers: {
|
|
2478
|
+
"Accept": "application/json",
|
|
2479
|
+
"X-Subscription-Token": apiKey
|
|
2480
|
+
}
|
|
2481
|
+
});
|
|
2482
|
+
debugLog("search", "Brave response status", { status: response.status, ok: response.ok });
|
|
2483
|
+
if (!response.ok) {
|
|
2484
|
+
const errorText = await response.text();
|
|
2485
|
+
debugLog("search", "Brave response ERROR", { status: response.status, error: errorText });
|
|
2486
|
+
throw new Error(`Brave API error: ${response.status}`);
|
|
2487
|
+
}
|
|
2488
|
+
const data = await response.json();
|
|
2489
|
+
const results = data.web?.results || [];
|
|
2490
|
+
debugLog("search", "Brave results count", { count: results.length });
|
|
2491
|
+
if (results.length === 0) {
|
|
2492
|
+
return { success: true, output: `No results found for: ${query}` };
|
|
2493
|
+
}
|
|
2494
|
+
const formatted = results.map(
|
|
2495
|
+
(r, i) => `${i + 1}. ${r.title || "No title"}
|
|
2496
|
+
${r.url || ""}
|
|
2497
|
+
${r.description || ""}`
|
|
2498
|
+
).join("\n\n");
|
|
2499
|
+
debugLog("search", "Brave search COMPLETE", { resultsLength: formatted.length });
|
|
2500
|
+
return { success: true, output: formatted };
|
|
2501
|
+
}
|
|
2502
|
+
async function searchWithSerper(query, apiKey, apiUrl) {
|
|
2503
|
+
debugLog("search", "Serper request START", { apiUrl, query });
|
|
2504
|
+
const response = await fetch(apiUrl, {
|
|
2505
|
+
method: "POST",
|
|
2506
|
+
headers: {
|
|
2507
|
+
"Content-Type": "application/json",
|
|
2508
|
+
"X-API-KEY": apiKey
|
|
2509
|
+
},
|
|
2510
|
+
body: JSON.stringify({ q: query })
|
|
2511
|
+
});
|
|
2512
|
+
debugLog("search", "Serper response status", { status: response.status, ok: response.ok });
|
|
2513
|
+
if (!response.ok) {
|
|
2514
|
+
const errorText = await response.text();
|
|
2515
|
+
debugLog("search", "Serper response ERROR", { status: response.status, error: errorText });
|
|
2516
|
+
throw new Error(`Serper API error: ${response.status}`);
|
|
2517
|
+
}
|
|
2518
|
+
const data = await response.json();
|
|
2519
|
+
const results = data.organic || [];
|
|
2520
|
+
debugLog("search", "Serper results count", { count: results.length });
|
|
2521
|
+
if (results.length === 0) {
|
|
2522
|
+
return { success: true, output: `No results found for: ${query}` };
|
|
2523
|
+
}
|
|
2524
|
+
const formatted = results.map(
|
|
2525
|
+
(r, i) => `${i + 1}. ${r.title || "No title"}
|
|
2526
|
+
${r.link || ""}
|
|
2527
|
+
${r.snippet || ""}`
|
|
2528
|
+
).join("\n\n");
|
|
2529
|
+
debugLog("search", "Serper search COMPLETE", { resultsLength: formatted.length });
|
|
2530
|
+
return { success: true, output: formatted };
|
|
2531
|
+
}
|
|
2532
|
+
async function searchWithGenericApi(query, apiKey, apiUrl) {
|
|
2533
|
+
const response = await fetch(apiUrl, {
|
|
2534
|
+
method: "POST",
|
|
2535
|
+
headers: {
|
|
2536
|
+
"Content-Type": "application/json",
|
|
2537
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
2538
|
+
"X-API-KEY": apiKey
|
|
2539
|
+
},
|
|
2540
|
+
body: JSON.stringify({ query, q: query })
|
|
2541
|
+
});
|
|
2542
|
+
if (!response.ok) {
|
|
2543
|
+
const errorText = await response.text();
|
|
2544
|
+
throw new Error(`Search API error: ${response.status} - ${errorText}`);
|
|
2545
|
+
}
|
|
2546
|
+
const data = await response.json();
|
|
2547
|
+
return { success: true, output: JSON.stringify(data, null, 2) };
|
|
2548
|
+
}
|
|
2312
2549
|
|
|
2313
2550
|
// src/engine/tools/web-browser.ts
|
|
2314
2551
|
import { spawn as spawn3 } from "child_process";
|
|
2315
|
-
import { writeFileSync as
|
|
2316
|
-
import { join as
|
|
2552
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
2553
|
+
import { join as join3, dirname } from "path";
|
|
2317
2554
|
import { tmpdir as tmpdir2 } from "os";
|
|
2318
2555
|
|
|
2319
2556
|
// src/shared/constants/browser.ts
|
|
@@ -2336,6 +2573,14 @@ var BROWSER_PATHS = {
|
|
|
2336
2573
|
};
|
|
2337
2574
|
|
|
2338
2575
|
// src/engine/tools/web-browser.ts
|
|
2576
|
+
function getPlaywrightPath() {
|
|
2577
|
+
try {
|
|
2578
|
+
const mainPath = __require.resolve("playwright");
|
|
2579
|
+
return dirname(mainPath);
|
|
2580
|
+
} catch {
|
|
2581
|
+
return join3(process.cwd(), "node_modules", "playwright");
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2339
2584
|
var DEFAULT_OPTIONS = {
|
|
2340
2585
|
timeout: BROWSER_CONFIG.DEFAULT_TIMEOUT,
|
|
2341
2586
|
userAgent: BROWSER_CONFIG.DEFAULT_USER_AGENT,
|
|
@@ -2405,15 +2650,15 @@ async function browseUrl(url, options = {}) {
|
|
|
2405
2650
|
};
|
|
2406
2651
|
}
|
|
2407
2652
|
}
|
|
2408
|
-
const tempDir =
|
|
2409
|
-
if (!
|
|
2410
|
-
|
|
2653
|
+
const tempDir = join3(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME);
|
|
2654
|
+
if (!existsSync4(tempDir)) {
|
|
2655
|
+
mkdirSync3(tempDir, { recursive: true });
|
|
2411
2656
|
}
|
|
2412
|
-
const screenshotPath = opts.screenshot ?
|
|
2657
|
+
const screenshotPath = opts.screenshot ? join3(tempDir, `screenshot-${Date.now()}.png`) : void 0;
|
|
2413
2658
|
const script = buildBrowseScript(url, opts, screenshotPath);
|
|
2414
|
-
const scriptPath =
|
|
2659
|
+
const scriptPath = join3(tempDir, `browse-${Date.now()}.cjs`);
|
|
2415
2660
|
try {
|
|
2416
|
-
|
|
2661
|
+
writeFileSync4(scriptPath, script, "utf-8");
|
|
2417
2662
|
} catch (err) {
|
|
2418
2663
|
return {
|
|
2419
2664
|
success: false,
|
|
@@ -2422,9 +2667,10 @@ async function browseUrl(url, options = {}) {
|
|
|
2422
2667
|
};
|
|
2423
2668
|
}
|
|
2424
2669
|
return new Promise((resolve) => {
|
|
2670
|
+
const nodePath = join3(process.cwd(), "node_modules");
|
|
2425
2671
|
const child = spawn3("node", [scriptPath], {
|
|
2426
2672
|
timeout: opts.timeout + 1e4,
|
|
2427
|
-
env: { ...process.env, NODE_PATH:
|
|
2673
|
+
env: { ...process.env, NODE_PATH: nodePath }
|
|
2428
2674
|
});
|
|
2429
2675
|
let stdout = "";
|
|
2430
2676
|
let stderr = "";
|
|
@@ -2481,12 +2727,15 @@ function buildBrowseScript(url, options, screenshotPath) {
|
|
|
2481
2727
|
const safeUserAgent = safeJsString(options.userAgent || BROWSER_CONFIG.DEFAULT_USER_AGENT);
|
|
2482
2728
|
const safeScreenshotPath = screenshotPath ? safeJsString(screenshotPath) : "null";
|
|
2483
2729
|
const safeExtraHeaders = JSON.stringify(options.extraHeaders || {});
|
|
2730
|
+
const playwrightPath = getPlaywrightPath();
|
|
2731
|
+
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
2732
|
+
const headlessMode = isBrowserHeadless();
|
|
2484
2733
|
return `
|
|
2485
|
-
const { chromium } = require(
|
|
2734
|
+
const { chromium } = require(${safePlaywrightPath});
|
|
2486
2735
|
|
|
2487
2736
|
(async () => {
|
|
2488
2737
|
const browser = await chromium.launch({
|
|
2489
|
-
headless:
|
|
2738
|
+
headless: ${headlessMode},
|
|
2490
2739
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
2491
2740
|
});
|
|
2492
2741
|
|
|
@@ -2606,11 +2855,14 @@ async function fillAndSubmitForm(url, formData, options = {}) {
|
|
|
2606
2855
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
2607
2856
|
const safeUrl = safeJsString(url);
|
|
2608
2857
|
const safeFormData = JSON.stringify(formData);
|
|
2858
|
+
const playwrightPath = getPlaywrightPath();
|
|
2859
|
+
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
2860
|
+
const headlessMode = isBrowserHeadless();
|
|
2609
2861
|
const script = `
|
|
2610
|
-
const { chromium } = require(
|
|
2862
|
+
const { chromium } = require(${safePlaywrightPath});
|
|
2611
2863
|
|
|
2612
2864
|
(async () => {
|
|
2613
|
-
const browser = await chromium.launch({ headless:
|
|
2865
|
+
const browser = await chromium.launch({ headless: ${headlessMode} });
|
|
2614
2866
|
const page = await browser.newPage();
|
|
2615
2867
|
|
|
2616
2868
|
try {
|
|
@@ -2646,13 +2898,13 @@ const { chromium } = require('playwright');
|
|
|
2646
2898
|
}
|
|
2647
2899
|
})();
|
|
2648
2900
|
`;
|
|
2649
|
-
const tempDir =
|
|
2650
|
-
if (!
|
|
2651
|
-
|
|
2901
|
+
const tempDir = join3(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME);
|
|
2902
|
+
if (!existsSync4(tempDir)) {
|
|
2903
|
+
mkdirSync3(tempDir, { recursive: true });
|
|
2652
2904
|
}
|
|
2653
|
-
const scriptPath =
|
|
2905
|
+
const scriptPath = join3(tempDir, `form-${Date.now()}.cjs`);
|
|
2654
2906
|
try {
|
|
2655
|
-
|
|
2907
|
+
writeFileSync4(scriptPath, script, "utf-8");
|
|
2656
2908
|
} catch (err) {
|
|
2657
2909
|
return {
|
|
2658
2910
|
success: false,
|
|
@@ -2661,8 +2913,10 @@ const { chromium } = require('playwright');
|
|
|
2661
2913
|
};
|
|
2662
2914
|
}
|
|
2663
2915
|
return new Promise((resolve) => {
|
|
2916
|
+
const nodePath = join3(process.cwd(), "node_modules");
|
|
2664
2917
|
const child = spawn3("node", [scriptPath], {
|
|
2665
|
-
timeout: opts.timeout + 1e4
|
|
2918
|
+
timeout: opts.timeout + 1e4,
|
|
2919
|
+
env: { ...process.env, NODE_PATH: nodePath }
|
|
2666
2920
|
});
|
|
2667
2921
|
let stdout = "";
|
|
2668
2922
|
let stderr = "";
|
|
@@ -3993,81 +4247,81 @@ var ServiceParser = class {
|
|
|
3993
4247
|
};
|
|
3994
4248
|
|
|
3995
4249
|
// src/domains/registry.ts
|
|
3996
|
-
import { join as
|
|
3997
|
-
import { fileURLToPath } from "url";
|
|
3998
|
-
var
|
|
4250
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
4251
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4252
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
3999
4253
|
var DOMAINS = {
|
|
4000
4254
|
[SERVICE_CATEGORIES.NETWORK]: {
|
|
4001
4255
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
4002
4256
|
name: "Network Infrastructure",
|
|
4003
4257
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
4004
|
-
promptPath:
|
|
4258
|
+
promptPath: join4(__dirname2, "network/prompt.md")
|
|
4005
4259
|
},
|
|
4006
4260
|
[SERVICE_CATEGORIES.WEB]: {
|
|
4007
4261
|
id: SERVICE_CATEGORIES.WEB,
|
|
4008
4262
|
name: "Web Application",
|
|
4009
4263
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
4010
|
-
promptPath:
|
|
4264
|
+
promptPath: join4(__dirname2, "web/prompt.md")
|
|
4011
4265
|
},
|
|
4012
4266
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
4013
4267
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
4014
4268
|
name: "Database Security",
|
|
4015
4269
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
4016
|
-
promptPath:
|
|
4270
|
+
promptPath: join4(__dirname2, "database/prompt.md")
|
|
4017
4271
|
},
|
|
4018
4272
|
[SERVICE_CATEGORIES.AD]: {
|
|
4019
4273
|
id: SERVICE_CATEGORIES.AD,
|
|
4020
4274
|
name: "Active Directory",
|
|
4021
4275
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
4022
|
-
promptPath:
|
|
4276
|
+
promptPath: join4(__dirname2, "ad/prompt.md")
|
|
4023
4277
|
},
|
|
4024
4278
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
4025
4279
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
4026
4280
|
name: "Email Services",
|
|
4027
4281
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
4028
|
-
promptPath:
|
|
4282
|
+
promptPath: join4(__dirname2, "email/prompt.md")
|
|
4029
4283
|
},
|
|
4030
4284
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
4031
4285
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
4032
4286
|
name: "Remote Access",
|
|
4033
4287
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
4034
|
-
promptPath:
|
|
4288
|
+
promptPath: join4(__dirname2, "remote-access/prompt.md")
|
|
4035
4289
|
},
|
|
4036
4290
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
4037
4291
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
4038
4292
|
name: "File Sharing",
|
|
4039
4293
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
4040
|
-
promptPath:
|
|
4294
|
+
promptPath: join4(__dirname2, "file-sharing/prompt.md")
|
|
4041
4295
|
},
|
|
4042
4296
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
4043
4297
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
4044
4298
|
name: "Cloud Infrastructure",
|
|
4045
4299
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
4046
|
-
promptPath:
|
|
4300
|
+
promptPath: join4(__dirname2, "cloud/prompt.md")
|
|
4047
4301
|
},
|
|
4048
4302
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
4049
4303
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
4050
4304
|
name: "Container Systems",
|
|
4051
4305
|
description: "Docker and Kubernetes security testing.",
|
|
4052
|
-
promptPath:
|
|
4306
|
+
promptPath: join4(__dirname2, "container/prompt.md")
|
|
4053
4307
|
},
|
|
4054
4308
|
[SERVICE_CATEGORIES.API]: {
|
|
4055
4309
|
id: SERVICE_CATEGORIES.API,
|
|
4056
4310
|
name: "API Security",
|
|
4057
4311
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
4058
|
-
promptPath:
|
|
4312
|
+
promptPath: join4(__dirname2, "api/prompt.md")
|
|
4059
4313
|
},
|
|
4060
4314
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
4061
4315
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
4062
4316
|
name: "Wireless Networks",
|
|
4063
4317
|
description: "WiFi and Bluetooth security testing.",
|
|
4064
|
-
promptPath:
|
|
4318
|
+
promptPath: join4(__dirname2, "wireless/prompt.md")
|
|
4065
4319
|
},
|
|
4066
4320
|
[SERVICE_CATEGORIES.ICS]: {
|
|
4067
4321
|
id: SERVICE_CATEGORIES.ICS,
|
|
4068
4322
|
name: "Industrial Systems",
|
|
4069
4323
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
4070
|
-
promptPath:
|
|
4324
|
+
promptPath: join4(__dirname2, "ics/prompt.md")
|
|
4071
4325
|
}
|
|
4072
4326
|
};
|
|
4073
4327
|
|
|
@@ -4316,21 +4570,6 @@ var agentLogger = new Logger("Agent", {
|
|
|
4316
4570
|
colorOutput: true
|
|
4317
4571
|
});
|
|
4318
4572
|
|
|
4319
|
-
// src/shared/utils/config.ts
|
|
4320
|
-
import path from "path";
|
|
4321
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4322
|
-
var __filename = fileURLToPath2(import.meta.url);
|
|
4323
|
-
var __dirname2 = path.dirname(__filename);
|
|
4324
|
-
function getApiKey() {
|
|
4325
|
-
return process.env.PENTEST_API_KEY || "";
|
|
4326
|
-
}
|
|
4327
|
-
function getBaseUrl() {
|
|
4328
|
-
return process.env.PENTEST_BASE_URL || void 0;
|
|
4329
|
-
}
|
|
4330
|
-
function getModel() {
|
|
4331
|
-
return process.env.PENTEST_MODEL || "";
|
|
4332
|
-
}
|
|
4333
|
-
|
|
4334
4573
|
// src/shared/constants/llm.ts
|
|
4335
4574
|
var RETRY_CONFIG = {
|
|
4336
4575
|
baseDelayMs: 2e3,
|
|
@@ -4360,64 +4599,6 @@ var LLM_ERROR_TYPES = {
|
|
|
4360
4599
|
};
|
|
4361
4600
|
|
|
4362
4601
|
// src/engine/llm.ts
|
|
4363
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
4364
|
-
import { join as join4 } from "path";
|
|
4365
|
-
import { homedir } from "os";
|
|
4366
|
-
var LLMLogger = class _LLMLogger {
|
|
4367
|
-
static instance;
|
|
4368
|
-
logPath;
|
|
4369
|
-
initialized = false;
|
|
4370
|
-
constructor() {
|
|
4371
|
-
const debugDir = join4(homedir(), ".pentesting", "debug");
|
|
4372
|
-
try {
|
|
4373
|
-
if (!existsSync4(debugDir)) {
|
|
4374
|
-
mkdirSync3(debugDir, { recursive: true });
|
|
4375
|
-
}
|
|
4376
|
-
this.logPath = join4(debugDir, "llm-debug.log");
|
|
4377
|
-
this.initialized = true;
|
|
4378
|
-
this.log("=== LLM LOGGER INITIALIZED ===");
|
|
4379
|
-
this.log(`Log file: ${this.logPath}`);
|
|
4380
|
-
} catch (e) {
|
|
4381
|
-
console.error("[LLMLogger] Failed to initialize:", e);
|
|
4382
|
-
this.logPath = "";
|
|
4383
|
-
}
|
|
4384
|
-
}
|
|
4385
|
-
static getInstance() {
|
|
4386
|
-
if (!_LLMLogger.instance) {
|
|
4387
|
-
_LLMLogger.instance = new _LLMLogger();
|
|
4388
|
-
}
|
|
4389
|
-
return _LLMLogger.instance;
|
|
4390
|
-
}
|
|
4391
|
-
log(message, data) {
|
|
4392
|
-
if (!this.initialized || !this.logPath) return;
|
|
4393
|
-
try {
|
|
4394
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4395
|
-
let logLine = `[${timestamp}] ${message}`;
|
|
4396
|
-
if (data !== void 0) {
|
|
4397
|
-
logLine += ` | ${JSON.stringify(data)}`;
|
|
4398
|
-
}
|
|
4399
|
-
logLine += "\n";
|
|
4400
|
-
appendFileSync2(this.logPath, logLine);
|
|
4401
|
-
} catch (e) {
|
|
4402
|
-
console.error("[LLMLogger] Write error:", e);
|
|
4403
|
-
}
|
|
4404
|
-
}
|
|
4405
|
-
logRaw(label, raw) {
|
|
4406
|
-
if (!this.initialized || !this.logPath) return;
|
|
4407
|
-
try {
|
|
4408
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4409
|
-
const logLine = `[${timestamp}] ${label}:
|
|
4410
|
-
${raw}
|
|
4411
|
-
---
|
|
4412
|
-
`;
|
|
4413
|
-
appendFileSync2(this.logPath, logLine);
|
|
4414
|
-
} catch (e) {
|
|
4415
|
-
console.error("[LLMLogger] Write error:", e);
|
|
4416
|
-
}
|
|
4417
|
-
}
|
|
4418
|
-
};
|
|
4419
|
-
var logger = LLMLogger.getInstance();
|
|
4420
|
-
logger.log("=== LLM MODULE LOADED ===");
|
|
4421
4602
|
var LLMError = class extends Error {
|
|
4422
4603
|
errorInfo;
|
|
4423
4604
|
constructor(info) {
|
|
@@ -4456,7 +4637,7 @@ var LLMClient = class {
|
|
|
4456
4637
|
this.apiKey = getApiKey();
|
|
4457
4638
|
this.baseUrl = getBaseUrl() || "https://api.anthropic.com";
|
|
4458
4639
|
this.model = getModel();
|
|
4459
|
-
|
|
4640
|
+
debugLog("llm", "LLMClient constructed", { model: this.model, baseUrl: this.baseUrl, apiKeyPrefix: this.apiKey.slice(0, 8) + "..." });
|
|
4460
4641
|
}
|
|
4461
4642
|
async generateResponse(messages, tools, systemPrompt, callbacks) {
|
|
4462
4643
|
return this.executeWithRetry(() => this.executeNonStream(messages, tools, systemPrompt), callbacks);
|
|
@@ -4516,12 +4697,12 @@ var LLMClient = class {
|
|
|
4516
4697
|
messages: this.convertMessages(messages),
|
|
4517
4698
|
tools
|
|
4518
4699
|
};
|
|
4519
|
-
|
|
4700
|
+
debugLog("llm", "Non-stream request", { model: this.model, toolCount: tools?.length });
|
|
4520
4701
|
const response = await this.makeRequest(requestBody);
|
|
4521
4702
|
const data = await response.json();
|
|
4522
4703
|
const textBlock = data.content.find((b) => b.type === "text");
|
|
4523
4704
|
const toolBlocks = data.content.filter((b) => b.type === "tool_use");
|
|
4524
|
-
|
|
4705
|
+
debugLog("llm", "Non-stream response", { toolCount: toolBlocks.length, tools: toolBlocks.map((t) => ({ id: t.id, name: t.name, input: t.input })) });
|
|
4525
4706
|
return {
|
|
4526
4707
|
content: textBlock?.text || "",
|
|
4527
4708
|
toolCalls: toolBlocks.map((b) => ({ id: b.id || "", name: b.name || "", input: b.input || {} })),
|
|
@@ -4540,7 +4721,7 @@ var LLMClient = class {
|
|
|
4540
4721
|
tools,
|
|
4541
4722
|
stream: true
|
|
4542
4723
|
};
|
|
4543
|
-
|
|
4724
|
+
debugLog("llm", `[${requestId2}] Stream request START`, { model: this.model, toolCount: tools?.length, toolNames: tools?.map((t) => t.name) });
|
|
4544
4725
|
const response = await this.makeRequest(requestBody, callbacks?.abortSignal);
|
|
4545
4726
|
let fullContent = "";
|
|
4546
4727
|
let fullReasoning = "";
|
|
@@ -4569,12 +4750,12 @@ var LLMClient = class {
|
|
|
4569
4750
|
if (data.trim() === "[DONE]") continue;
|
|
4570
4751
|
try {
|
|
4571
4752
|
const event = JSON.parse(data);
|
|
4572
|
-
|
|
4753
|
+
debugLog("llm", `[${requestId2}] SSE event`, { type: event.type, index: event.index, deltaType: event.delta?.type });
|
|
4573
4754
|
if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
4574
|
-
|
|
4755
|
+
debugLog("llm", `[${requestId2}] TOOL BLOCK START`, { id: event.content_block.id, name: event.content_block.name });
|
|
4575
4756
|
}
|
|
4576
4757
|
if (event.type === "content_block_delta" && event.delta?.type === "input_json_delta") {
|
|
4577
|
-
|
|
4758
|
+
debugLog("llm", `[${requestId2}] JSON DELTA`, { index: event.index, partialJson: event.delta.partial_json });
|
|
4578
4759
|
}
|
|
4579
4760
|
this.processStreamEvent(event, {
|
|
4580
4761
|
toolCallsMap,
|
|
@@ -4609,25 +4790,25 @@ var LLMClient = class {
|
|
|
4609
4790
|
reader.releaseLock();
|
|
4610
4791
|
}
|
|
4611
4792
|
const toolCalls = [];
|
|
4612
|
-
|
|
4793
|
+
debugLog("llm", `[${requestId2}] PARSING ${toolCallsMap.size} TOOL CALLS`);
|
|
4613
4794
|
for (const [id, toolCall] of toolCallsMap) {
|
|
4614
|
-
|
|
4795
|
+
debugLog("llm", `[${requestId2}] Tool before parse`, { id, name: toolCall.name, pendingJsonLen: toolCall._pendingJson?.length, pendingJson: toolCall._pendingJson });
|
|
4615
4796
|
if (toolCall._pendingJson) {
|
|
4616
4797
|
const parseResult = this.safeParseJson(toolCall._pendingJson);
|
|
4617
4798
|
if (parseResult.success) {
|
|
4618
4799
|
toolCall.input = parseResult.data;
|
|
4619
|
-
|
|
4800
|
+
debugLog("llm", `[${requestId2}] Tool parsed OK`, { id, name: toolCall.name, input: toolCall.input });
|
|
4620
4801
|
} else {
|
|
4621
4802
|
toolCall.input = { _parse_error: parseResult.error, _raw_json: toolCall._pendingJson.slice(0, 500) };
|
|
4622
|
-
|
|
4803
|
+
debugLog("llm", `[${requestId2}] Tool parse FAILED`, { id, name: toolCall.name, error: parseResult.error, raw: toolCall._pendingJson });
|
|
4623
4804
|
}
|
|
4624
4805
|
delete toolCall._pendingJson;
|
|
4625
4806
|
} else {
|
|
4626
|
-
|
|
4807
|
+
debugLog("llm", `[${requestId2}] Tool NO JSON RECEIVED`, { id, name: toolCall.name });
|
|
4627
4808
|
}
|
|
4628
4809
|
toolCalls.push({ id: toolCall.id, name: toolCall.name, input: toolCall.input });
|
|
4629
4810
|
}
|
|
4630
|
-
|
|
4811
|
+
debugLog("llm", `[${requestId2}] FINAL toolCalls`, { count: toolCalls.length, tools: toolCalls.map((t) => ({ id: t.id, name: t.name, input: t.input })) });
|
|
4631
4812
|
return {
|
|
4632
4813
|
content: fullContent,
|
|
4633
4814
|
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
@@ -4674,9 +4855,9 @@ var LLMClient = class {
|
|
|
4674
4855
|
const toolCall = Array.from(toolCallsMap.values()).find((t) => t._index === index);
|
|
4675
4856
|
if (toolCall) {
|
|
4676
4857
|
toolCall._pendingJson = (toolCall._pendingJson || "") + event.delta.partial_json;
|
|
4677
|
-
|
|
4858
|
+
debugLog("llm", `[${requestId}] JSON DELTA applied`, { index, toolId: toolCall.id, json: event.delta.partial_json });
|
|
4678
4859
|
} else {
|
|
4679
|
-
|
|
4860
|
+
debugLog("llm", `[${requestId}] JSON DELTA no tool found for index`, { index });
|
|
4680
4861
|
}
|
|
4681
4862
|
}
|
|
4682
4863
|
}
|
|
@@ -4758,7 +4939,7 @@ function getLLMClient() {
|
|
|
4758
4939
|
return llmInstance;
|
|
4759
4940
|
}
|
|
4760
4941
|
function logLLM(message, data) {
|
|
4761
|
-
|
|
4942
|
+
debugLog("llm", message, data);
|
|
4762
4943
|
}
|
|
4763
4944
|
|
|
4764
4945
|
// src/agents/core-agent.ts
|
|
@@ -5193,7 +5374,7 @@ Please decide how to handle this error and continue.`;
|
|
|
5193
5374
|
|
|
5194
5375
|
// src/agents/prompt-builder.ts
|
|
5195
5376
|
import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
|
|
5196
|
-
import { join as join5, dirname as
|
|
5377
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
5197
5378
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5198
5379
|
|
|
5199
5380
|
// src/shared/constants/prompts.ts
|
|
@@ -5227,7 +5408,7 @@ var INITIAL_TASKS = {
|
|
|
5227
5408
|
};
|
|
5228
5409
|
|
|
5229
5410
|
// src/agents/prompt-builder.ts
|
|
5230
|
-
var __dirname3 =
|
|
5411
|
+
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
5231
5412
|
var PROMPTS_DIR = join5(__dirname3, "prompts");
|
|
5232
5413
|
var PHASE_PROMPT_MAP = {
|
|
5233
5414
|
recon: "recon.md",
|
|
@@ -6326,6 +6507,7 @@ var EXIT_CODE = {
|
|
|
6326
6507
|
|
|
6327
6508
|
// src/platform/tui/main.tsx
|
|
6328
6509
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
6510
|
+
initDebugLogger();
|
|
6329
6511
|
var program = new Command();
|
|
6330
6512
|
program.name("pentesting").version(APP_VERSION).description(APP_DESCRIPTION).option("--dangerously-skip-permissions", "Skip all permission prompts (dangerous!)").option("-t, --target <target>", "Set initial target");
|
|
6331
6513
|
program.command("interactive", { isDefault: true }).alias("i").description("Start interactive TUI mode").action(async () => {
|