pentesting 0.48.3 → 0.49.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.
- package/dist/main.js +98 -47
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -13,7 +13,7 @@ import chalk from "chalk";
|
|
|
13
13
|
|
|
14
14
|
// src/platform/tui/app.tsx
|
|
15
15
|
import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef5 } from "react";
|
|
16
|
-
import { Box as Box6, useInput as useInput2, useApp } from "ink";
|
|
16
|
+
import { Box as Box6, useInput as useInput2, useApp, useStdout } from "ink";
|
|
17
17
|
|
|
18
18
|
// src/platform/tui/hooks/useAgent.ts
|
|
19
19
|
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef3 } from "react";
|
|
@@ -331,7 +331,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
331
331
|
|
|
332
332
|
// src/shared/constants/agent.ts
|
|
333
333
|
var APP_NAME = "Pentest AI";
|
|
334
|
-
var APP_VERSION = "0.
|
|
334
|
+
var APP_VERSION = "0.49.1";
|
|
335
335
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
336
336
|
var LLM_ROLES = {
|
|
337
337
|
SYSTEM: "system",
|
|
@@ -2920,6 +2920,8 @@ var AttackGraph = class {
|
|
|
2920
2920
|
// ─── TUI Visualization ──────────────────────────────────────
|
|
2921
2921
|
/**
|
|
2922
2922
|
* Generate ASCII visualization for TUI /graph command.
|
|
2923
|
+
* WHY no truncation: /graph is the user's audit view — all nodes must be visible.
|
|
2924
|
+
* Truncation is only for toPrompt() (LLM context budget).
|
|
2923
2925
|
*/
|
|
2924
2926
|
toASCII() {
|
|
2925
2927
|
if (this.nodes.size === 0) return "(Empty attack graph \u2014 no discoveries yet)";
|
|
@@ -2945,17 +2947,13 @@ var AttackGraph = class {
|
|
|
2945
2947
|
[NODE_STATUS.SUCCEEDED]: "\u25CF",
|
|
2946
2948
|
[NODE_STATUS.FAILED]: "\u2717"
|
|
2947
2949
|
};
|
|
2948
|
-
|
|
2950
|
+
const typeSummary = Object.entries(groups).map(([t, ns]) => `${typeIcons[t] || "\xB7"} ${ns.length}`).join(" ");
|
|
2951
|
+
lines.push(`\u2502 ${typeSummary}`);
|
|
2949
2952
|
for (const [type, nodes] of Object.entries(groups)) {
|
|
2950
|
-
if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) {
|
|
2951
|
-
lines.push(`\u2502 ... and ${this.nodes.size - nodeCount} more nodes`);
|
|
2952
|
-
break;
|
|
2953
|
-
}
|
|
2954
2953
|
const icon = typeIcons[type] || "\xB7";
|
|
2955
2954
|
lines.push(`\u2502`);
|
|
2956
2955
|
lines.push(`\u2502 ${icon} ${type.toUpperCase()} (${nodes.length})`);
|
|
2957
2956
|
for (const node of nodes) {
|
|
2958
|
-
if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) break;
|
|
2959
2957
|
const sIcon = statusIcons[node.status] || "?";
|
|
2960
2958
|
const fail = node.status === NODE_STATUS.FAILED && node.failReason ? ` \u2014 ${node.failReason}` : "";
|
|
2961
2959
|
let detail = "";
|
|
@@ -2982,7 +2980,6 @@ var AttackGraph = class {
|
|
|
2982
2980
|
return `${eName}${eStatus}`;
|
|
2983
2981
|
}).join(", ")}` : "";
|
|
2984
2982
|
lines.push(`\u2502 ${sIcon} ${node.label}${detail}${fail}${edgeStr}`);
|
|
2985
|
-
nodeCount++;
|
|
2986
2983
|
}
|
|
2987
2984
|
}
|
|
2988
2985
|
const succeededNodes = Array.from(this.nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED);
|
|
@@ -3816,19 +3813,27 @@ var AgentEventEmitter = class {
|
|
|
3816
3813
|
}
|
|
3817
3814
|
}
|
|
3818
3815
|
/**
|
|
3819
|
-
* Emit an event
|
|
3816
|
+
* Emit an event.
|
|
3817
|
+
* WHY try-catch: Listeners must NEVER crash the emitter.
|
|
3818
|
+
* If a TUI listener throws (e.g., setState after unmount), the agent loop must survive.
|
|
3820
3819
|
*/
|
|
3821
3820
|
emit(event) {
|
|
3822
3821
|
const listeners = this.listeners.get(event.type);
|
|
3823
3822
|
if (listeners) {
|
|
3824
3823
|
for (const listener of listeners) {
|
|
3825
|
-
|
|
3824
|
+
try {
|
|
3825
|
+
listener(event);
|
|
3826
|
+
} catch {
|
|
3827
|
+
}
|
|
3826
3828
|
}
|
|
3827
3829
|
}
|
|
3828
3830
|
const anyListeners = this.listeners.get("*");
|
|
3829
3831
|
if (anyListeners) {
|
|
3830
3832
|
for (const listener of anyListeners) {
|
|
3831
|
-
|
|
3833
|
+
try {
|
|
3834
|
+
listener(event);
|
|
3835
|
+
} catch {
|
|
3836
|
+
}
|
|
3832
3837
|
}
|
|
3833
3838
|
}
|
|
3834
3839
|
}
|
|
@@ -10120,10 +10125,6 @@ function parseAnalystMemo(response) {
|
|
|
10120
10125
|
}
|
|
10121
10126
|
function formatAnalystDigest(digest, filePath, originalChars) {
|
|
10122
10127
|
return [
|
|
10123
|
-
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
10124
|
-
"\u2551 ANALYST DIGEST (Independent LLM analysis) \u2551",
|
|
10125
|
-
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
10126
|
-
"",
|
|
10127
10128
|
digest,
|
|
10128
10129
|
"",
|
|
10129
10130
|
`\u{1F4C2} Full output saved: ${filePath} (${originalChars} chars)`,
|
|
@@ -12571,8 +12572,14 @@ var useAgentState = () => {
|
|
|
12571
12572
|
}
|
|
12572
12573
|
}, []);
|
|
12573
12574
|
const clearAllTimers = useCallback(() => {
|
|
12574
|
-
if (timerRef.current)
|
|
12575
|
-
|
|
12575
|
+
if (timerRef.current) {
|
|
12576
|
+
clearInterval(timerRef.current);
|
|
12577
|
+
timerRef.current = null;
|
|
12578
|
+
}
|
|
12579
|
+
if (retryCountdownRef.current) {
|
|
12580
|
+
clearInterval(retryCountdownRef.current);
|
|
12581
|
+
retryCountdownRef.current = null;
|
|
12582
|
+
}
|
|
12576
12583
|
}, []);
|
|
12577
12584
|
return {
|
|
12578
12585
|
// State
|
|
@@ -12838,7 +12845,10 @@ function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCount
|
|
|
12838
12845
|
remaining -= 1;
|
|
12839
12846
|
if (remaining <= 0) {
|
|
12840
12847
|
setRetryState({ status: "idle" });
|
|
12841
|
-
if (retryCountdownRef.current)
|
|
12848
|
+
if (retryCountdownRef.current) {
|
|
12849
|
+
clearInterval(retryCountdownRef.current);
|
|
12850
|
+
retryCountdownRef.current = null;
|
|
12851
|
+
}
|
|
12842
12852
|
} else {
|
|
12843
12853
|
setRetryState((prev) => prev.status === "retrying" ? { ...prev, countdown: remaining } : prev);
|
|
12844
12854
|
}
|
|
@@ -12906,25 +12916,33 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
12906
12916
|
}
|
|
12907
12917
|
}, [agent, target]);
|
|
12908
12918
|
useAgentEvents(agent, eventsRef, state);
|
|
12919
|
+
const abortedRef = useRef3(false);
|
|
12909
12920
|
const executeTask = useCallback2(async (task) => {
|
|
12921
|
+
abortedRef.current = false;
|
|
12910
12922
|
setIsProcessing(true);
|
|
12911
12923
|
manageTimer("start");
|
|
12912
12924
|
setCurrentStatus("Thinking");
|
|
12913
12925
|
resetCumulativeCounters();
|
|
12914
12926
|
try {
|
|
12915
12927
|
const response = await agent.execute(task);
|
|
12928
|
+
if (abortedRef.current) return;
|
|
12916
12929
|
const meta = lastResponseMetaRef.current;
|
|
12917
12930
|
const suffix = meta ? ` ${formatMeta(meta.durationMs || 0, (meta.tokens?.input || 0) + (meta.tokens?.output || 0))}` : "";
|
|
12918
12931
|
addMessage("ai", response + suffix);
|
|
12919
12932
|
} catch (e) {
|
|
12920
|
-
|
|
12933
|
+
if (!abortedRef.current) {
|
|
12934
|
+
addMessage("error", e instanceof Error ? e.message : String(e));
|
|
12935
|
+
}
|
|
12921
12936
|
} finally {
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12937
|
+
if (!abortedRef.current) {
|
|
12938
|
+
manageTimer("stop");
|
|
12939
|
+
setIsProcessing(false);
|
|
12940
|
+
setCurrentStatus("");
|
|
12941
|
+
}
|
|
12925
12942
|
}
|
|
12926
12943
|
}, [agent, addMessage, manageTimer, resetCumulativeCounters, setIsProcessing, lastResponseMetaRef, setCurrentStatus]);
|
|
12927
12944
|
const abort = useCallback2(() => {
|
|
12945
|
+
abortedRef.current = true;
|
|
12928
12946
|
agent.abort();
|
|
12929
12947
|
setIsProcessing(false);
|
|
12930
12948
|
manageTimer("stop");
|
|
@@ -13353,6 +13371,7 @@ var ChatInput = memo4(({
|
|
|
13353
13371
|
paddingX: showPreview ? 1 : 0,
|
|
13354
13372
|
marginBottom: 0,
|
|
13355
13373
|
height: showPreview ? void 0 : 0,
|
|
13374
|
+
overflowX: "hidden",
|
|
13356
13375
|
children: showPreview && suggestions.map((cmd, i) => {
|
|
13357
13376
|
const isFirst = i === 0;
|
|
13358
13377
|
const nameColor = isFirst ? THEME.white : THEME.gray;
|
|
@@ -13380,6 +13399,7 @@ var ChatInput = memo4(({
|
|
|
13380
13399
|
borderStyle: "single",
|
|
13381
13400
|
borderColor: inputRequest.status === "active" ? THEME.yellow : THEME.border.default,
|
|
13382
13401
|
paddingX: 1,
|
|
13402
|
+
overflowX: "hidden",
|
|
13383
13403
|
children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
13384
13404
|
/* @__PURE__ */ jsx5(Text5, { color: THEME.yellow, children: "[auth]" }),
|
|
13385
13405
|
/* @__PURE__ */ jsxs4(Text5, { color: THEME.gray, children: [
|
|
@@ -13469,6 +13489,8 @@ var footer_default = Footer;
|
|
|
13469
13489
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
13470
13490
|
var App = ({ autoApprove = false, target }) => {
|
|
13471
13491
|
const { exit } = useApp();
|
|
13492
|
+
const { stdout } = useStdout();
|
|
13493
|
+
const terminalWidth = stdout?.columns ?? 80;
|
|
13472
13494
|
const [input, setInput] = useState5("");
|
|
13473
13495
|
const [secretInput, setSecretInput] = useState5("");
|
|
13474
13496
|
const [autoApproveMode, setAutoApproveMode] = useState5(autoApprove);
|
|
@@ -13498,12 +13520,15 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
13498
13520
|
inputRequestRef.current = inputRequest;
|
|
13499
13521
|
const handleExit = useCallback4(() => {
|
|
13500
13522
|
const ir = inputRequestRef.current;
|
|
13501
|
-
if (ir.status === "active")
|
|
13523
|
+
if (ir.status === "active") {
|
|
13524
|
+
ir.resolve(null);
|
|
13525
|
+
setInputRequest({ status: "inactive" });
|
|
13526
|
+
}
|
|
13502
13527
|
cleanupAllProcesses().catch(() => {
|
|
13503
13528
|
});
|
|
13504
13529
|
exit();
|
|
13505
13530
|
setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
|
|
13506
|
-
}, [exit]);
|
|
13531
|
+
}, [exit, setInputRequest]);
|
|
13507
13532
|
const handleCommand = useCallback4(async (cmd, args) => {
|
|
13508
13533
|
switch (cmd) {
|
|
13509
13534
|
case UI_COMMANDS.HELP:
|
|
@@ -13562,28 +13587,54 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
13562
13587
|
addMessage("system", "No findings.");
|
|
13563
13588
|
break;
|
|
13564
13589
|
}
|
|
13565
|
-
const
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13590
|
+
const severityOrder = ["critical", "high", "medium", "low", "info"];
|
|
13591
|
+
const severityIcons = {
|
|
13592
|
+
critical: "\u{1F534}",
|
|
13593
|
+
high: "\u{1F7E0}",
|
|
13594
|
+
medium: "\u{1F7E1}",
|
|
13595
|
+
low: "\u{1F7E2}",
|
|
13596
|
+
info: "\u26AA"
|
|
13597
|
+
};
|
|
13598
|
+
const grouped = {};
|
|
13599
|
+
for (const f of findings) {
|
|
13600
|
+
const sev = f.severity.toLowerCase();
|
|
13601
|
+
if (!grouped[sev]) grouped[sev] = [];
|
|
13602
|
+
grouped[sev].push(f);
|
|
13603
|
+
}
|
|
13604
|
+
const findingLines = [];
|
|
13605
|
+
const sevCounts = severityOrder.filter((s) => grouped[s]?.length).map((s) => `${severityIcons[s]} ${s.toUpperCase()}: ${grouped[s].length}`).join(" ");
|
|
13606
|
+
findingLines.push(`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500 ${sevCounts} \u2500\u2500\u2500`);
|
|
13607
|
+
findingLines.push("");
|
|
13608
|
+
for (const sev of severityOrder) {
|
|
13609
|
+
const group = grouped[sev];
|
|
13610
|
+
if (!group?.length) continue;
|
|
13611
|
+
const icon = severityIcons[sev] || "\u2022";
|
|
13612
|
+
findingLines.push(`${icon} \u2500\u2500 ${sev.toUpperCase()} (${group.length}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
13585
13613
|
findingLines.push("");
|
|
13586
|
-
|
|
13614
|
+
group.forEach((f, i) => {
|
|
13615
|
+
const verified = f.isVerified ? `\u2713 Verified` : `? Unverified`;
|
|
13616
|
+
const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
|
|
13617
|
+
const cat = f.category ? ` \u2502 ${f.category}` : "";
|
|
13618
|
+
findingLines.push(` [${i + 1}] ${f.title}`);
|
|
13619
|
+
findingLines.push(` ${verified}${atk}${cat}`);
|
|
13620
|
+
if (f.affected.length > 0) {
|
|
13621
|
+
findingLines.push(` Affected: ${f.affected.join(", ")}`);
|
|
13622
|
+
}
|
|
13623
|
+
if (f.description) {
|
|
13624
|
+
findingLines.push(` ${f.description}`);
|
|
13625
|
+
}
|
|
13626
|
+
if (f.evidence.length > 0) {
|
|
13627
|
+
findingLines.push(` Evidence:`);
|
|
13628
|
+
f.evidence.forEach((e) => {
|
|
13629
|
+
findingLines.push(` \u25B8 ${e}`);
|
|
13630
|
+
});
|
|
13631
|
+
}
|
|
13632
|
+
if (f.remediation) {
|
|
13633
|
+
findingLines.push(` Fix: ${f.remediation}`);
|
|
13634
|
+
}
|
|
13635
|
+
findingLines.push("");
|
|
13636
|
+
});
|
|
13637
|
+
}
|
|
13587
13638
|
addMessage("system", findingLines.join("\n"));
|
|
13588
13639
|
break;
|
|
13589
13640
|
case UI_COMMANDS.ASSETS:
|
|
@@ -13693,7 +13744,7 @@ ${procData.stdout || "(no output)"}
|
|
|
13693
13744
|
process.off("SIGTERM", onSignal);
|
|
13694
13745
|
};
|
|
13695
13746
|
}, [handleCtrlC]);
|
|
13696
|
-
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
13747
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
|
|
13697
13748
|
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom: 1, flexGrow: 1, children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
|
|
13698
13749
|
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
13699
13750
|
/* @__PURE__ */ jsx7(
|