pentesting 0.53.0 → 0.54.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 +282 -127
- package/dist/prompts/base.md +131 -568
- package/dist/prompts/{ctf-mode.md → offensive-playbook.md} +40 -101
- package/dist/prompts/orchestrator.md +83 -263
- package/dist/prompts/strategy.md +88 -608
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -12,7 +12,7 @@ import { Command } from "commander";
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
|
|
14
14
|
// src/platform/tui/app.tsx
|
|
15
|
-
import { useState as
|
|
15
|
+
import { useState as useState6, useCallback as useCallback4, useEffect as useEffect5, useRef as useRef6 } from "react";
|
|
16
16
|
import { Box as Box6, useInput as useInput2, useApp, useStdout } from "ink";
|
|
17
17
|
|
|
18
18
|
// src/platform/tui/hooks/useAgent.ts
|
|
@@ -33,6 +33,7 @@ var TOOL_TIMEOUTS = {
|
|
|
33
33
|
/** Full web application scan */
|
|
34
34
|
WEB_SCAN: 3e5
|
|
35
35
|
};
|
|
36
|
+
var CURL_MAX_TIME_SEC = 600;
|
|
36
37
|
|
|
37
38
|
// src/shared/constants/limits.ts
|
|
38
39
|
var DISPLAY_LIMITS = {
|
|
@@ -188,8 +189,6 @@ var MEMORY_LIMITS = {
|
|
|
188
189
|
TECHNIQUE_FAILURE_DECAY: 30,
|
|
189
190
|
/** Auto-prune threshold: techniques below this confidence are discarded */
|
|
190
191
|
TECHNIQUE_PRUNE_THRESHOLD: 10,
|
|
191
|
-
/** @deprecated Superseded by fingerprint-based matching in extractFingerprint(). Kept for reference. */
|
|
192
|
-
COMMAND_MATCH_WORDS: 3,
|
|
193
192
|
/** Maximum unverified techniques to show in prompt */
|
|
194
193
|
PROMPT_UNVERIFIED_TECHNIQUES: 10,
|
|
195
194
|
/**
|
|
@@ -197,13 +196,7 @@ var MEMORY_LIMITS = {
|
|
|
197
196
|
* WHY: journal.ts had this as a module-local constant (50).
|
|
198
197
|
* Centralizing enables consistent rotation across journal JSON + turn MD files.
|
|
199
198
|
*/
|
|
200
|
-
MAX_TURN_ENTRIES: 50
|
|
201
|
-
/**
|
|
202
|
-
* Maximum raw output files kept in .pentesting/outputs/.
|
|
203
|
-
* WHY: journal.ts had this as a module-local constant (30).
|
|
204
|
-
* Output files are large raw dumps; the journal entries retain their summaries.
|
|
205
|
-
*/
|
|
206
|
-
MAX_OUTPUT_FILES: 30
|
|
199
|
+
MAX_TURN_ENTRIES: 50
|
|
207
200
|
};
|
|
208
201
|
|
|
209
202
|
// src/shared/constants/patterns.ts
|
|
@@ -349,7 +342,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
349
342
|
|
|
350
343
|
// src/shared/constants/agent.ts
|
|
351
344
|
var APP_NAME = "Pentest AI";
|
|
352
|
-
var APP_VERSION = "0.
|
|
345
|
+
var APP_VERSION = "0.54.1";
|
|
353
346
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
354
347
|
var LLM_ROLES = {
|
|
355
348
|
SYSTEM: "system",
|
|
@@ -789,6 +782,7 @@ var UI_COMMANDS = {
|
|
|
789
782
|
LOGS: "logs",
|
|
790
783
|
LOGS_SHORT: "l",
|
|
791
784
|
CTF: "ctf",
|
|
785
|
+
TOR: "tor",
|
|
792
786
|
AUTO: "auto",
|
|
793
787
|
GRAPH: "graph",
|
|
794
788
|
GRAPH_SHORT: "g",
|
|
@@ -1738,8 +1732,7 @@ var ENV_KEYS = {
|
|
|
1738
1732
|
SEARCH_API_KEY: "SEARCH_API_KEY",
|
|
1739
1733
|
SEARCH_API_URL: "SEARCH_API_URL",
|
|
1740
1734
|
THINKING: "PENTEST_THINKING",
|
|
1741
|
-
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
1742
|
-
TOR: "PENTEST_TOR"
|
|
1735
|
+
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
1743
1736
|
};
|
|
1744
1737
|
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
1745
1738
|
var DEFAULT_MODEL = "glm-4.7";
|
|
@@ -1782,11 +1775,82 @@ var TOR_PROXY = {
|
|
|
1782
1775
|
/** Flags for the wrapper command (-q = quiet, no banners) */
|
|
1783
1776
|
WRAPPER_FLAGS: "-q"
|
|
1784
1777
|
};
|
|
1778
|
+
var torEnabled = false;
|
|
1785
1779
|
function isTorEnabled() {
|
|
1786
|
-
return
|
|
1780
|
+
return torEnabled;
|
|
1781
|
+
}
|
|
1782
|
+
function setTorEnabled(enabled) {
|
|
1783
|
+
torEnabled = enabled;
|
|
1784
|
+
}
|
|
1785
|
+
function checkTorLeakRisk(command) {
|
|
1786
|
+
if (!isTorEnabled()) return { safe: true };
|
|
1787
|
+
if (/\bping\b/.test(command)) {
|
|
1788
|
+
return {
|
|
1789
|
+
safe: false,
|
|
1790
|
+
reason: "ping uses ICMP \u2014 bypasses Tor, real IP exposed to target",
|
|
1791
|
+
suggestion: "Use TCP probe instead: curl --socks5-hostname 127.0.0.1:9050 -s --connect-timeout 5 http://<target>:<port>"
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
if (/\btraceroute\b|\btracepath\b|\bmtr\b/.test(command)) {
|
|
1795
|
+
return {
|
|
1796
|
+
safe: false,
|
|
1797
|
+
reason: "traceroute/tracepath/mtr uses ICMP/UDP \u2014 bypasses Tor, real IP exposed",
|
|
1798
|
+
suggestion: "Skip traceroute when Tor is enabled (reveals all hops including your IP)"
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
if (/\bdig\b/.test(command) || /\bnslookup\b/.test(command) || /(?:^|[;&|\s])host(?:\s|$)/.test(command)) {
|
|
1802
|
+
return {
|
|
1803
|
+
safe: false,
|
|
1804
|
+
reason: "dig/host/nslookup use UDP \u2014 DNS query bypasses Tor, real IP visible to DNS server",
|
|
1805
|
+
suggestion: `Use DNS-over-HTTPS: curl --socks5-hostname 127.0.0.1:9050 "https://dns.google/resolve?name=<host>&type=A"`
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
if (/\bnmap\b/.test(command) && /\s-sU\b/.test(command)) {
|
|
1809
|
+
return {
|
|
1810
|
+
safe: false,
|
|
1811
|
+
reason: "nmap -sU (UDP scan) bypasses Tor \u2014 UDP is not routed through SOCKS5",
|
|
1812
|
+
suggestion: "Skip UDP scan with Tor ON. Use TCP scan: proxychains4 -q nmap -sT -Pn"
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
return { safe: true };
|
|
1787
1816
|
}
|
|
1788
1817
|
function wrapCommandForTor(command) {
|
|
1789
1818
|
if (!isTorEnabled()) return command;
|
|
1819
|
+
const SOCKS = `${TOR_PROXY.SOCKS_HOST}:${TOR_PROXY.SOCKS_PORT}`;
|
|
1820
|
+
if (/\bcurl\b/.test(command)) {
|
|
1821
|
+
if (/--socks5|--proxy\b|-x\s/.test(command)) return command;
|
|
1822
|
+
return command.replace(/\bcurl\b/, `curl --socks5-hostname ${SOCKS}`);
|
|
1823
|
+
}
|
|
1824
|
+
if (/\bwget\b/.test(command)) {
|
|
1825
|
+
if (/--execute.*proxy|http_proxy|https_proxy/i.test(command)) return command;
|
|
1826
|
+
return command.replace(
|
|
1827
|
+
/\bwget\b/,
|
|
1828
|
+
`wget -e use_proxy=yes -e http_proxy=socks5h://${SOCKS} -e https_proxy=socks5h://${SOCKS}`
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
if (/\bsqlmap\b/.test(command)) {
|
|
1832
|
+
if (/--tor\b|--proxy\b/.test(command)) return command;
|
|
1833
|
+
return command.replace(
|
|
1834
|
+
/\bsqlmap\b/,
|
|
1835
|
+
`sqlmap --tor --tor-type=SOCKS5 --tor-port=${TOR_PROXY.SOCKS_PORT}`
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
if (/\bgobuster\b/.test(command)) {
|
|
1839
|
+
if (/--proxy\b/.test(command)) return command;
|
|
1840
|
+
return command.replace(/\bgobuster\b/, `gobuster --proxy socks5://${SOCKS}`);
|
|
1841
|
+
}
|
|
1842
|
+
if (/\bffuf\b/.test(command)) {
|
|
1843
|
+
if (/-x\s/.test(command)) return command;
|
|
1844
|
+
return command.replace(/\bffuf\b/, `ffuf -x socks5://${SOCKS}`);
|
|
1845
|
+
}
|
|
1846
|
+
if (/\bnmap\b/.test(command)) {
|
|
1847
|
+
let nmapCmd = command;
|
|
1848
|
+
nmapCmd = nmapCmd.replace(/\s-s[SA]\b/g, " -sT");
|
|
1849
|
+
if (!/\s-Pn\b/.test(nmapCmd)) {
|
|
1850
|
+
nmapCmd = nmapCmd.replace(/\bnmap\b/, "nmap -Pn");
|
|
1851
|
+
}
|
|
1852
|
+
return `${TOR_PROXY.WRAPPER_CMD} ${TOR_PROXY.WRAPPER_FLAGS} ${nmapCmd}`;
|
|
1853
|
+
}
|
|
1790
1854
|
return `${TOR_PROXY.WRAPPER_CMD} ${TOR_PROXY.WRAPPER_FLAGS} ${command}`;
|
|
1791
1855
|
}
|
|
1792
1856
|
function getTorBrowserArgs() {
|
|
@@ -1840,6 +1904,16 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1840
1904
|
error: `Command blocked: ${validation.error}`
|
|
1841
1905
|
};
|
|
1842
1906
|
}
|
|
1907
|
+
const torLeak = checkTorLeakRisk(fullCommand);
|
|
1908
|
+
if (!torLeak.safe) {
|
|
1909
|
+
return {
|
|
1910
|
+
success: false,
|
|
1911
|
+
output: "",
|
|
1912
|
+
error: `\u{1F6D1} TOR IP LEAK BLOCKED
|
|
1913
|
+
Reason: ${torLeak.reason}
|
|
1914
|
+
Suggestion: ${torLeak.suggestion}`
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1843
1917
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1844
1918
|
const maxRetries = options.maxRetries ?? AGENT_LIMITS.MAX_INSTALL_RETRIES;
|
|
1845
1919
|
const toolName = command.split(/[\s/]/).pop() || command;
|
|
@@ -1877,16 +1951,22 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1877
1951
|
}
|
|
1878
1952
|
return lastResult;
|
|
1879
1953
|
}
|
|
1954
|
+
function injectCurlMaxTime(command, timeoutSec) {
|
|
1955
|
+
if (!/\bcurl\b/.test(command)) return command;
|
|
1956
|
+
if (/--max-time\b|-m\s+\d/.test(command)) return command;
|
|
1957
|
+
return command.replace(/\bcurl\b/, `curl --max-time ${timeoutSec}`);
|
|
1958
|
+
}
|
|
1880
1959
|
async function executeCommandOnce(command, options = {}) {
|
|
1881
1960
|
return new Promise((resolve) => {
|
|
1882
1961
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1962
|
+
const safeCommand = injectCurlMaxTime(command, CURL_MAX_TIME_SEC);
|
|
1883
1963
|
globalEventEmitter?.({
|
|
1884
1964
|
type: COMMAND_EVENT_TYPES.COMMAND_START,
|
|
1885
|
-
message: `Executing: ${
|
|
1965
|
+
message: `Executing: ${safeCommand.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${safeCommand.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
|
|
1886
1966
|
});
|
|
1887
|
-
const execCommand = wrapCommandForTor(
|
|
1967
|
+
const execCommand = wrapCommandForTor(safeCommand);
|
|
1888
1968
|
const child = spawn2("sh", ["-c", execCommand], {
|
|
1889
|
-
|
|
1969
|
+
detached: true,
|
|
1890
1970
|
env: { ...process.env, ...options.env },
|
|
1891
1971
|
cwd: options.cwd
|
|
1892
1972
|
});
|
|
@@ -1894,6 +1974,20 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
1894
1974
|
let stderr = "";
|
|
1895
1975
|
let inputHandled = false;
|
|
1896
1976
|
let processTerminated = false;
|
|
1977
|
+
let timedOut = false;
|
|
1978
|
+
const killTimer = setTimeout(() => {
|
|
1979
|
+
if (processTerminated) return;
|
|
1980
|
+
timedOut = true;
|
|
1981
|
+
processTerminated = true;
|
|
1982
|
+
try {
|
|
1983
|
+
process.kill(-child.pid, "SIGKILL");
|
|
1984
|
+
} catch {
|
|
1985
|
+
try {
|
|
1986
|
+
child.kill("SIGKILL");
|
|
1987
|
+
} catch {
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}, timeout);
|
|
1897
1991
|
const checkForInputPrompt = async (data) => {
|
|
1898
1992
|
if (inputHandled || !globalInputHandler || processTerminated) return;
|
|
1899
1993
|
for (const pattern of INPUT_PROMPT_PATTERNS) {
|
|
@@ -1927,7 +2021,21 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
1927
2021
|
checkForInputPrompt(text);
|
|
1928
2022
|
});
|
|
1929
2023
|
child.on("close", (code) => {
|
|
2024
|
+
clearTimeout(killTimer);
|
|
1930
2025
|
processTerminated = true;
|
|
2026
|
+
if (timedOut) {
|
|
2027
|
+
globalEventEmitter?.({
|
|
2028
|
+
type: COMMAND_EVENT_TYPES.COMMAND_FAILED,
|
|
2029
|
+
message: `Command timed out after ${Math.round(timeout / 1e3)}s`
|
|
2030
|
+
});
|
|
2031
|
+
resolve({
|
|
2032
|
+
success: false,
|
|
2033
|
+
output: stdout.trim(),
|
|
2034
|
+
error: `Command timed out after ${Math.round(timeout / 1e3)}s. Output so far:
|
|
2035
|
+
${(stdout + stderr).trim().slice(-500)}`
|
|
2036
|
+
});
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
1931
2039
|
if (code === 0) {
|
|
1932
2040
|
globalEventEmitter?.({
|
|
1933
2041
|
type: COMMAND_EVENT_TYPES.COMMAND_SUCCESS,
|
|
@@ -1951,6 +2059,7 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
1951
2059
|
}
|
|
1952
2060
|
});
|
|
1953
2061
|
child.on("error", (err) => {
|
|
2062
|
+
clearTimeout(killTimer);
|
|
1954
2063
|
processTerminated = true;
|
|
1955
2064
|
globalEventEmitter?.({
|
|
1956
2065
|
type: COMMAND_EVENT_TYPES.COMMAND_ERROR,
|
|
@@ -2202,6 +2311,16 @@ function startBackgroundProcess(command, options = {}) {
|
|
|
2202
2311
|
const stderrFile = createTempFile(FILE_EXTENSIONS.STDERR);
|
|
2203
2312
|
const stdinFile = createTempFile(FILE_EXTENSIONS.STDIN);
|
|
2204
2313
|
const { tags, port, role, isInteractive } = detectProcessRole(command);
|
|
2314
|
+
const torLeak = checkTorLeakRisk(command);
|
|
2315
|
+
if (!torLeak.safe) {
|
|
2316
|
+
for (const f of [stdoutFile, stderrFile, stdinFile]) {
|
|
2317
|
+
try {
|
|
2318
|
+
unlinkSync2(f);
|
|
2319
|
+
} catch {
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
throw new Error(`\u{1F6D1} TOR IP LEAK BLOCKED \u2014 ${torLeak.reason}. ${torLeak.suggestion}`);
|
|
2323
|
+
}
|
|
2205
2324
|
const torCommand = wrapCommandForTor(command);
|
|
2206
2325
|
let wrappedCmd;
|
|
2207
2326
|
if (isInteractive) {
|
|
@@ -2644,7 +2763,7 @@ var StateSerializer = class {
|
|
|
2644
2763
|
lines.push(resourceInfo);
|
|
2645
2764
|
}
|
|
2646
2765
|
if (state.isCtfMode()) {
|
|
2647
|
-
lines.push(`
|
|
2766
|
+
lines.push(`Flag Detection: ON \u{1F3F4}`);
|
|
2648
2767
|
const flags = state.getFlags();
|
|
2649
2768
|
if (flags.length > 0) {
|
|
2650
2769
|
lines.push(`Flags Found (${flags.length}):`);
|
|
@@ -2653,6 +2772,16 @@ var StateSerializer = class {
|
|
|
2653
2772
|
}
|
|
2654
2773
|
}
|
|
2655
2774
|
}
|
|
2775
|
+
if (isTorEnabled()) {
|
|
2776
|
+
lines.push(
|
|
2777
|
+
`Tor Proxy: ON \u{1F9C5}
|
|
2778
|
+
Standard tools auto-proxied (curl, wget, nmap, sqlmap, gobuster, ffuf, hydra, etc.)
|
|
2779
|
+
Custom scripts: route ALL target connections through SOCKS5 127.0.0.1:9050.
|
|
2780
|
+
BLOCKED (leak real IP): ping, traceroute, dig, nslookup, nmap -sU`
|
|
2781
|
+
);
|
|
2782
|
+
} else {
|
|
2783
|
+
lines.push(`Tor Proxy: OFF \u2014 direct connections.`);
|
|
2784
|
+
}
|
|
2656
2785
|
lines.push(`Phase: ${state.getPhase()}`);
|
|
2657
2786
|
return lines.join("\n");
|
|
2658
2787
|
}
|
|
@@ -2815,10 +2944,6 @@ var AttackGraph = class {
|
|
|
2815
2944
|
}
|
|
2816
2945
|
this.failedPaths.push(`${fromId} \u2192 ${toId}${reason ? ` (${reason})` : ""}`);
|
|
2817
2946
|
}
|
|
2818
|
-
// Backward-compatible alias
|
|
2819
|
-
markExploited(nodeId) {
|
|
2820
|
-
this.markSucceeded(nodeId);
|
|
2821
|
-
}
|
|
2822
2947
|
// ─── Domain-Specific Registration ───────────────────────────
|
|
2823
2948
|
/**
|
|
2824
2949
|
* Record a host discovery.
|
|
@@ -2877,7 +3002,7 @@ var AttackGraph = class {
|
|
|
2877
3002
|
hasExploit
|
|
2878
3003
|
});
|
|
2879
3004
|
for (const [id, node] of this.nodes) {
|
|
2880
|
-
if (node.type ===
|
|
3005
|
+
if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
|
|
2881
3006
|
this.addEdge(id, vulnId, "has_vulnerability", 0.8);
|
|
2882
3007
|
}
|
|
2883
3008
|
}
|
|
@@ -2919,7 +3044,7 @@ var AttackGraph = class {
|
|
|
2919
3044
|
...data
|
|
2920
3045
|
});
|
|
2921
3046
|
for (const [id, node] of this.nodes) {
|
|
2922
|
-
if (node.type ===
|
|
3047
|
+
if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
|
|
2923
3048
|
const hostIp = String(node.data.ip || node.data.host || "");
|
|
2924
3049
|
const hostname = String(node.data.hostname || "");
|
|
2925
3050
|
if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
|
|
@@ -3339,23 +3464,6 @@ var WorkingMemory = class {
|
|
|
3339
3464
|
(e) => e.category === "failure" && e.fingerprint != null && fingerprintsMatch(e.fingerprint, fp)
|
|
3340
3465
|
);
|
|
3341
3466
|
}
|
|
3342
|
-
/**
|
|
3343
|
-
* Get all previous attempts (success & failure) for a specific tool+target vector.
|
|
3344
|
-
* Returns the full history so the LLM can see what parameter combinations were tried.
|
|
3345
|
-
*
|
|
3346
|
-
* TODO: Wire up in tools.ts to pass vector history to strategist for smarter retries.
|
|
3347
|
-
*/
|
|
3348
|
-
getAttemptsForVector(tool, target) {
|
|
3349
|
-
const lowerTool = tool.toLowerCase();
|
|
3350
|
-
return this.entries.filter((e) => {
|
|
3351
|
-
if (e.fingerprint) {
|
|
3352
|
-
const matchTool = e.fingerprint.tool === lowerTool;
|
|
3353
|
-
const matchTarget = !target || e.fingerprint.target.includes(target);
|
|
3354
|
-
return matchTool && matchTarget;
|
|
3355
|
-
}
|
|
3356
|
-
return String(e.context.tool || "").toLowerCase() === lowerTool;
|
|
3357
|
-
});
|
|
3358
|
-
}
|
|
3359
3467
|
/** Internal prune helper (used by both add() and recordFailure()) */
|
|
3360
3468
|
pruneIfNeeded() {
|
|
3361
3469
|
if (this.entries.length > this.maxEntries) {
|
|
@@ -3383,26 +3491,6 @@ var WorkingMemory = class {
|
|
|
3383
3491
|
}
|
|
3384
3492
|
return count;
|
|
3385
3493
|
}
|
|
3386
|
-
/**
|
|
3387
|
-
* Get consecutive failures for a specific vector (tool+target).
|
|
3388
|
-
* Returns count of sequential failures where all attempts used the same tool+target.
|
|
3389
|
-
*
|
|
3390
|
-
* TODO: Wire up in tools.ts for per-vector threshold checks alongside global consecutive count.
|
|
3391
|
-
*/
|
|
3392
|
-
getConsecutiveVectorFailures(tool, target) {
|
|
3393
|
-
const lowerTool = tool.toLowerCase();
|
|
3394
|
-
let count = 0;
|
|
3395
|
-
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
3396
|
-
const e = this.entries[i];
|
|
3397
|
-
if (e.category !== "failure") break;
|
|
3398
|
-
if (e.fingerprint && e.fingerprint.tool === lowerTool && (!target || e.fingerprint.target.includes(target))) {
|
|
3399
|
-
count++;
|
|
3400
|
-
} else {
|
|
3401
|
-
break;
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
|
-
return count;
|
|
3405
|
-
}
|
|
3406
3494
|
/**
|
|
3407
3495
|
* Format for prompt injection.
|
|
3408
3496
|
*/
|
|
@@ -3842,7 +3930,7 @@ var SharedState = class {
|
|
|
3842
3930
|
missionSummary: "",
|
|
3843
3931
|
missionChecklist: [],
|
|
3844
3932
|
ctfMode: true,
|
|
3845
|
-
//
|
|
3933
|
+
// Flag auto-detection ON by default
|
|
3846
3934
|
flags: [],
|
|
3847
3935
|
startedAt: now,
|
|
3848
3936
|
// Auto-configure from PENTEST_DURATION env (seconds).
|
|
@@ -4018,7 +4106,7 @@ var SharedState = class {
|
|
|
4018
4106
|
getPhase() {
|
|
4019
4107
|
return this.data.currentPhase;
|
|
4020
4108
|
}
|
|
4021
|
-
// ---
|
|
4109
|
+
// --- Flag Detection ---
|
|
4022
4110
|
setCtfMode(shouldEnable) {
|
|
4023
4111
|
this.data.ctfMode = shouldEnable;
|
|
4024
4112
|
}
|
|
@@ -5240,10 +5328,27 @@ All ports freed. All children killed.`
|
|
|
5240
5328
|
},
|
|
5241
5329
|
{
|
|
5242
5330
|
name: TOOL_NAMES.WRITE_FILE,
|
|
5243
|
-
description:
|
|
5331
|
+
description: `Write content to a file. Creates parent directories automatically.
|
|
5332
|
+
|
|
5333
|
+
Primary use: Write custom exploit scripts, fuzzers, scanners, payloads.
|
|
5334
|
+
|
|
5335
|
+
Workflow:
|
|
5336
|
+
1. write_file({ path: "/tmp/exploit.py", content: "..." })
|
|
5337
|
+
2. run_cmd({ command: "python3 /tmp/exploit.py" })
|
|
5338
|
+
3. read_file to review output
|
|
5339
|
+
|
|
5340
|
+
\u{1F9C5} TOR ON \u2014 MANDATORY: Any script that opens a network connection to the target
|
|
5341
|
+
MUST route through Tor SOCKS5 (127.0.0.1:9050). No exceptions.
|
|
5342
|
+
You are responsible for using the correct proxy method for the language you choose.
|
|
5343
|
+
Proxy patterns are in <current-state>. Ignoring this leaks your real IP.
|
|
5344
|
+
|
|
5345
|
+
\u26A1 TOR OFF: Write scripts normally. Direct connections are fine.
|
|
5346
|
+
|
|
5347
|
+
Standard tools (curl/wget/nmap/sqlmap/hydra etc.) via run_cmd are
|
|
5348
|
+
auto-proxied by the system \u2014 no extra work needed for those.`,
|
|
5244
5349
|
parameters: {
|
|
5245
|
-
path: { type: "string", description: "Absolute path
|
|
5246
|
-
content: { type: "string", description: "File content" }
|
|
5350
|
+
path: { type: "string", description: "Absolute file path (e.g., /tmp/exploit.py)" },
|
|
5351
|
+
content: { type: "string", description: "File content to write" }
|
|
5247
5352
|
},
|
|
5248
5353
|
required: ["path", "content"],
|
|
5249
5354
|
execute: async (params) => writeFileContent(params.path, params.content)
|
|
@@ -7544,16 +7649,20 @@ Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt
|
|
|
7544
7649
|
if (wordlist === "rockyou") wordlistPath = WORDLISTS.ROCKYOU;
|
|
7545
7650
|
const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
|
|
7546
7651
|
if (background) {
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7652
|
+
try {
|
|
7653
|
+
const proc = startBackgroundProcess(cmd, {
|
|
7654
|
+
description: `Cracking hashes: ${hashes.slice(0, DISPLAY_LIMITS.HASH_PREVIEW_LENGTH)}...`,
|
|
7655
|
+
purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
|
|
7656
|
+
});
|
|
7657
|
+
return {
|
|
7658
|
+
success: true,
|
|
7659
|
+
output: `Hash cracking started in background (ID: ${proc.id}).
|
|
7554
7660
|
Command: ${cmd}
|
|
7555
7661
|
Check status with: bg_process({ action: "status", process_id: "${proc.id}" })`
|
|
7556
|
-
|
|
7662
|
+
};
|
|
7663
|
+
} catch (err) {
|
|
7664
|
+
return { success: false, output: "", error: `Failed to start: ${err}` };
|
|
7665
|
+
}
|
|
7557
7666
|
} else {
|
|
7558
7667
|
return runCommand(cmd);
|
|
7559
7668
|
}
|
|
@@ -10761,7 +10870,7 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
10761
10870
|
}
|
|
10762
10871
|
}
|
|
10763
10872
|
// ─────────────────────────────────────────────────────────────────
|
|
10764
|
-
// SUBSECTION:
|
|
10873
|
+
// SUBSECTION: Flag Detection
|
|
10765
10874
|
// ─────────────────────────────────────────────────────────────────
|
|
10766
10875
|
scanForFlags(output) {
|
|
10767
10876
|
if (!this.state.isCtfMode()) return;
|
|
@@ -11099,12 +11208,12 @@ Phase: ${phase} | Targets: ${targets} | Findings: ${findings}
|
|
|
11099
11208
|
|
|
11100
11209
|
${direction}
|
|
11101
11210
|
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11211
|
+
PICK ANY \u2014 do whatever fits best (no order, all are valid):
|
|
11212
|
+
\u2022 Brute-force with wordlists (hydra/hashcat/ffuf + rockyou/seclists)
|
|
11213
|
+
\u2022 web_search for techniques
|
|
11214
|
+
\u2022 Try a completely different approach
|
|
11215
|
+
\u2022 Probe for unknown vulns
|
|
11216
|
+
\u2022 ask_user for hints
|
|
11108
11217
|
|
|
11109
11218
|
ACT NOW \u2014 EXECUTE.`;
|
|
11110
11219
|
}
|
|
@@ -11211,7 +11320,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
11211
11320
|
// src/shared/constants/prompts.ts
|
|
11212
11321
|
var PROMPT_PATHS = {
|
|
11213
11322
|
BASE: "base.md",
|
|
11214
|
-
|
|
11323
|
+
OFFENSIVE_PLAYBOOK: "offensive-playbook.md",
|
|
11215
11324
|
AGENT_FILES: {
|
|
11216
11325
|
ORCHESTRATOR: "orchestrator.md",
|
|
11217
11326
|
RECON: "recon.md",
|
|
@@ -11689,9 +11798,9 @@ var PHASE_PROMPT_MAP = {
|
|
|
11689
11798
|
};
|
|
11690
11799
|
var CORE_KNOWLEDGE_FILES = [
|
|
11691
11800
|
AGENT_FILES.STRATEGY,
|
|
11692
|
-
// Attack prioritization, first-turn protocol, upgrade loop
|
|
11801
|
+
// Attack prioritization, first-turn protocol, upgrade loop (~2K tok)
|
|
11693
11802
|
AGENT_FILES.ORCHESTRATOR,
|
|
11694
|
-
//
|
|
11803
|
+
// Kill chain position, phase transitions, multi-target (~2K tok)
|
|
11695
11804
|
AGENT_FILES.EVASION,
|
|
11696
11805
|
// Detection avoidance (always relevant)
|
|
11697
11806
|
AGENT_FILES.ZERO_DAY,
|
|
@@ -11731,9 +11840,10 @@ var PromptBuilder = class {
|
|
|
11731
11840
|
* Build complete prompt for LLM iteration (async).
|
|
11732
11841
|
*
|
|
11733
11842
|
* Layers (phase-aware, enhanced with D-CIPHER meta-prompting):
|
|
11734
|
-
* 1. base.md — Core identity, rules,
|
|
11843
|
+
* 1. base.md — Core identity, rules, OODA protocol, anti-hallucination (~2.7K tok)
|
|
11844
|
+
* 1b. Offensive playbook — attack methodology, time management, aggression rules (~2K tok)
|
|
11735
11845
|
* 2. Phase-specific prompt — current phase's full specialist knowledge (~2K tok)
|
|
11736
|
-
* 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~
|
|
11846
|
+
* 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~8K tok)
|
|
11737
11847
|
* 4. Phase-relevant techniques — only attack techniques for current phase (~4-8K tok)
|
|
11738
11848
|
* 5. Scope constraints
|
|
11739
11849
|
* 6. Current state (targets, findings, loot, active processes)
|
|
@@ -11753,7 +11863,7 @@ var PromptBuilder = class {
|
|
|
11753
11863
|
async build(userInput, phase) {
|
|
11754
11864
|
const fragments = [
|
|
11755
11865
|
this.loadPromptFile(PROMPT_PATHS.BASE),
|
|
11756
|
-
this.
|
|
11866
|
+
this.loadOffensivePlaybook(),
|
|
11757
11867
|
this.loadPhasePrompt(phase),
|
|
11758
11868
|
this.loadCoreKnowledge(phase),
|
|
11759
11869
|
this.loadPhaseRelevantTechniques(phase),
|
|
@@ -11786,15 +11896,14 @@ var PromptBuilder = class {
|
|
|
11786
11896
|
return fragments.filter((f) => !!f).join("\n\n");
|
|
11787
11897
|
}
|
|
11788
11898
|
/**
|
|
11789
|
-
* Load
|
|
11790
|
-
* Adds ~3K tokens of
|
|
11899
|
+
* Load offensive playbook — attack methodology always active.
|
|
11900
|
+
* Adds ~3K tokens of time management, technique quick-starts, aggression rules.
|
|
11791
11901
|
*/
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
return content ? `<ctf-mode active="true">
|
|
11902
|
+
loadOffensivePlaybook() {
|
|
11903
|
+
const content = this.loadPromptFile(PROMPT_PATHS.OFFENSIVE_PLAYBOOK);
|
|
11904
|
+
return content ? `<offensive-playbook>
|
|
11796
11905
|
${content}
|
|
11797
|
-
</
|
|
11906
|
+
</offensive-playbook>` : "";
|
|
11798
11907
|
}
|
|
11799
11908
|
/**
|
|
11800
11909
|
* Load a prompt file from src/agents/prompts/
|
|
@@ -13017,7 +13126,8 @@ var COMMAND_DEFINITIONS = [
|
|
|
13017
13126
|
{ name: "paths", alias: "p", description: "Show ranked attack paths" },
|
|
13018
13127
|
{ name: "assets", alias: "a", description: "List background processes" },
|
|
13019
13128
|
{ name: "logs", alias: "l", args: "<id>", description: "Show logs for an asset" },
|
|
13020
|
-
{ name: "ctf", description: "Toggle
|
|
13129
|
+
{ name: "ctf", description: "Toggle auto flag detection" },
|
|
13130
|
+
{ name: "tor", description: "Toggle Tor proxy routing" },
|
|
13021
13131
|
{ name: "auto", description: "Toggle auto-approve mode" },
|
|
13022
13132
|
{ name: "clear", alias: "c", description: "Reset session & clean workspace" },
|
|
13023
13133
|
{ name: "help", alias: "h", description: "Show detailed help" },
|
|
@@ -13042,7 +13152,8 @@ ${COMMAND_DEFINITIONS.map((cmd) => {
|
|
|
13042
13152
|
\u2022 Auto-install missing tools (apt/brew)
|
|
13043
13153
|
\u2022 Transparent command execution
|
|
13044
13154
|
\u2022 Interactive sudo password input
|
|
13045
|
-
\u2022
|
|
13155
|
+
\u2022 Flag detection: Auto-detect flags/proofs in tool output (/ctf to toggle)
|
|
13156
|
+
\u2022 Tor proxy: Route all traffic through Tor SOCKS proxy (/tor to toggle, default OFF)
|
|
13046
13157
|
\u2022 Attack graph: Tracks discovered paths & prevents repeating failures
|
|
13047
13158
|
|
|
13048
13159
|
\u2500\u2500 Tips \u2500\u2500
|
|
@@ -13068,6 +13179,7 @@ var useAgentState = () => {
|
|
|
13068
13179
|
const retryCountRef = useRef(0);
|
|
13069
13180
|
const tokenAccumRef = useRef(0);
|
|
13070
13181
|
const lastStepTokensRef = useRef(0);
|
|
13182
|
+
const toolStartedAtRef = useRef(0);
|
|
13071
13183
|
const addMessage = useCallback((type, content) => {
|
|
13072
13184
|
const id = Math.random().toString(36).substring(7);
|
|
13073
13185
|
setMessages((prev) => [...prev, { id, type, content, timestamp: /* @__PURE__ */ new Date() }]);
|
|
@@ -13130,7 +13242,8 @@ var useAgentState = () => {
|
|
|
13130
13242
|
addMessage,
|
|
13131
13243
|
resetCumulativeCounters,
|
|
13132
13244
|
manageTimer,
|
|
13133
|
-
clearAllTimers
|
|
13245
|
+
clearAllTimers,
|
|
13246
|
+
toolStartedAtRef
|
|
13134
13247
|
};
|
|
13135
13248
|
};
|
|
13136
13249
|
|
|
@@ -13149,19 +13262,22 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
13149
13262
|
retryCountRef,
|
|
13150
13263
|
tokenAccumRef,
|
|
13151
13264
|
lastStepTokensRef,
|
|
13152
|
-
clearAllTimers
|
|
13265
|
+
clearAllTimers,
|
|
13266
|
+
toolStartedAtRef
|
|
13153
13267
|
} = state;
|
|
13154
13268
|
const reasoningBufferRef = useRef2("");
|
|
13155
13269
|
useEffect(() => {
|
|
13156
13270
|
const events = eventsRef.current;
|
|
13157
13271
|
const onToolCall = (e) => {
|
|
13158
13272
|
if (NOISE_CLASSIFICATION.LOW_VISIBILITY.includes(e.data.toolName)) return;
|
|
13273
|
+
toolStartedAtRef.current = Date.now();
|
|
13159
13274
|
setCurrentStatus(`${e.data.toolName}\u2026`);
|
|
13160
13275
|
const inputStr = formatToolInput(e.data.toolName, e.data.input);
|
|
13161
13276
|
const label = inputStr ? `${toDisplayName(e.data.toolName)}(${inputStr})` : `${toDisplayName(e.data.toolName)}`;
|
|
13162
13277
|
addMessage("tool", label);
|
|
13163
13278
|
};
|
|
13164
13279
|
const onToolResult = (e) => {
|
|
13280
|
+
toolStartedAtRef.current = 0;
|
|
13165
13281
|
if (NOISE_CLASSIFICATION.LOW_VISIBILITY.includes(e.data.toolName)) {
|
|
13166
13282
|
return;
|
|
13167
13283
|
}
|
|
@@ -13320,6 +13436,7 @@ ${firstLine}`);
|
|
|
13320
13436
|
tokenAccumRef,
|
|
13321
13437
|
lastStepTokensRef,
|
|
13322
13438
|
clearAllTimers,
|
|
13439
|
+
toolStartedAtRef,
|
|
13323
13440
|
eventsRef
|
|
13324
13441
|
]);
|
|
13325
13442
|
};
|
|
@@ -13759,7 +13876,7 @@ var MessageList = memo(({ messages }) => {
|
|
|
13759
13876
|
});
|
|
13760
13877
|
|
|
13761
13878
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
13762
|
-
import { memo as memo3 } from "react";
|
|
13879
|
+
import { memo as memo3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
13763
13880
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
13764
13881
|
|
|
13765
13882
|
// src/platform/tui/components/MusicSpinner.tsx
|
|
@@ -13785,26 +13902,50 @@ var StatusDisplay = memo3(({
|
|
|
13785
13902
|
retryState,
|
|
13786
13903
|
isProcessing,
|
|
13787
13904
|
currentStatus,
|
|
13788
|
-
elapsedTime,
|
|
13789
13905
|
currentTokens
|
|
13790
13906
|
}) => {
|
|
13791
13907
|
const truncateError = (err) => {
|
|
13792
13908
|
return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
|
|
13793
13909
|
};
|
|
13794
|
-
const
|
|
13910
|
+
const [statusElapsed, setStatusElapsed] = useState4(0);
|
|
13911
|
+
const statusTimerRef = useRef4(null);
|
|
13912
|
+
const statusStartRef = useRef4(Date.now());
|
|
13913
|
+
useEffect4(() => {
|
|
13914
|
+
if (statusTimerRef.current) clearInterval(statusTimerRef.current);
|
|
13915
|
+
if (isProcessing && currentStatus) {
|
|
13916
|
+
statusStartRef.current = Date.now();
|
|
13917
|
+
setStatusElapsed(0);
|
|
13918
|
+
statusTimerRef.current = setInterval(() => {
|
|
13919
|
+
setStatusElapsed(Math.floor((Date.now() - statusStartRef.current) / 1e3));
|
|
13920
|
+
}, 1e3);
|
|
13921
|
+
} else {
|
|
13922
|
+
setStatusElapsed(0);
|
|
13923
|
+
statusTimerRef.current = null;
|
|
13924
|
+
}
|
|
13925
|
+
return () => {
|
|
13926
|
+
if (statusTimerRef.current) clearInterval(statusTimerRef.current);
|
|
13927
|
+
};
|
|
13928
|
+
}, [currentStatus, isProcessing]);
|
|
13929
|
+
const buildMeta = () => {
|
|
13930
|
+
const parts = [];
|
|
13931
|
+
if (statusElapsed > 0) parts.push(formatDuration2(statusElapsed * 1e3));
|
|
13932
|
+
if (currentTokens > 0) parts.push(`\u2191 ${formatTokens(currentTokens)} tokens`);
|
|
13933
|
+
return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
|
|
13934
|
+
};
|
|
13935
|
+
const meta = buildMeta();
|
|
13795
13936
|
const isThinkingStatus = currentStatus.startsWith("Reasoning");
|
|
13796
13937
|
const statusLines = currentStatus ? currentStatus.split("\n").filter(Boolean) : [];
|
|
13797
13938
|
const statusMain = statusLines[0] || "Processing...";
|
|
13798
13939
|
if (retryState.status === "retrying") {
|
|
13799
13940
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13800
|
-
/* @__PURE__ */ jsx4(Text4, { color: THEME.yellow, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.yellow }) }),
|
|
13801
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.yellow, bold: true, children: [
|
|
13941
|
+
/* @__PURE__ */ jsx4(Text4, { color: THEME.yellow, wrap: "truncate", children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.yellow }) }),
|
|
13942
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.yellow, bold: true, wrap: "truncate", children: [
|
|
13802
13943
|
" \u27F3 Retry #",
|
|
13803
13944
|
retryState.attempt,
|
|
13804
13945
|
"/",
|
|
13805
13946
|
retryState.maxRetries
|
|
13806
13947
|
] }),
|
|
13807
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13948
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, wrap: "truncate", children: [
|
|
13808
13949
|
" \u2014 ",
|
|
13809
13950
|
retryState.countdown,
|
|
13810
13951
|
"s \xB7 ",
|
|
@@ -13815,12 +13956,12 @@ var StatusDisplay = memo3(({
|
|
|
13815
13956
|
if (isProcessing) {
|
|
13816
13957
|
const previewText = isThinkingStatus && statusLines.length > 1 ? `${statusMain} \u2014 ${statusLines[statusLines.length - 1]}` : statusMain;
|
|
13817
13958
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13818
|
-
/* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
|
|
13819
|
-
/* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
|
|
13959
|
+
/* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, wrap: "truncate", children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
|
|
13960
|
+
/* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, wrap: "truncate", children: [
|
|
13820
13961
|
" ",
|
|
13821
13962
|
previewText
|
|
13822
13963
|
] }),
|
|
13823
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13964
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, wrap: "truncate", children: [
|
|
13824
13965
|
" ",
|
|
13825
13966
|
meta
|
|
13826
13967
|
] })
|
|
@@ -13830,7 +13971,7 @@ var StatusDisplay = memo3(({
|
|
|
13830
13971
|
});
|
|
13831
13972
|
|
|
13832
13973
|
// src/platform/tui/components/ChatInput.tsx
|
|
13833
|
-
import { useMemo, useCallback as useCallback3, useRef as
|
|
13974
|
+
import { useMemo, useCallback as useCallback3, useRef as useRef5, memo as memo4, useState as useState5 } from "react";
|
|
13834
13975
|
import { Box as Box4, Text as Text5, useInput } from "ink";
|
|
13835
13976
|
import TextInput from "ink-text-input";
|
|
13836
13977
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
@@ -13853,17 +13994,17 @@ var ChatInput = memo4(({
|
|
|
13853
13994
|
return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
|
|
13854
13995
|
}, [isSlashMode, partialCmd, hasArgs]);
|
|
13855
13996
|
const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
|
|
13856
|
-
const suggestionsRef =
|
|
13997
|
+
const suggestionsRef = useRef5(suggestions);
|
|
13857
13998
|
suggestionsRef.current = suggestions;
|
|
13858
|
-
const isSlashModeRef =
|
|
13999
|
+
const isSlashModeRef = useRef5(isSlashMode);
|
|
13859
14000
|
isSlashModeRef.current = isSlashMode;
|
|
13860
|
-
const hasArgsRef =
|
|
14001
|
+
const hasArgsRef = useRef5(hasArgs);
|
|
13861
14002
|
hasArgsRef.current = hasArgs;
|
|
13862
|
-
const inputRequestRef =
|
|
14003
|
+
const inputRequestRef = useRef5(inputRequest);
|
|
13863
14004
|
inputRequestRef.current = inputRequest;
|
|
13864
|
-
const onChangeRef =
|
|
14005
|
+
const onChangeRef = useRef5(onChange);
|
|
13865
14006
|
onChangeRef.current = onChange;
|
|
13866
|
-
const [inputKey, setInputKey] =
|
|
14007
|
+
const [inputKey, setInputKey] = useState5(0);
|
|
13867
14008
|
useInput(useCallback3((_input, key) => {
|
|
13868
14009
|
if (inputRequestRef.current.status === "active") return;
|
|
13869
14010
|
if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
|
|
@@ -13968,6 +14109,7 @@ var Footer = memo5(({ phase, targets, findings, todo, elapsedTime, isProcessing
|
|
|
13968
14109
|
paddingX: 1,
|
|
13969
14110
|
marginTop: 0,
|
|
13970
14111
|
justifyContent: "space-between",
|
|
14112
|
+
overflow: "hidden",
|
|
13971
14113
|
children: [
|
|
13972
14114
|
/* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
|
|
13973
14115
|
/* @__PURE__ */ jsxs5(Text6, { color: THEME.gray, children: [
|
|
@@ -14003,9 +14145,9 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
14003
14145
|
const { exit } = useApp();
|
|
14004
14146
|
const { stdout } = useStdout();
|
|
14005
14147
|
const terminalWidth = stdout?.columns ?? 80;
|
|
14006
|
-
const [input, setInput] =
|
|
14007
|
-
const [secretInput, setSecretInput] =
|
|
14008
|
-
const [autoApproveMode, setAutoApproveMode] =
|
|
14148
|
+
const [input, setInput] = useState6("");
|
|
14149
|
+
const [secretInput, setSecretInput] = useState6("");
|
|
14150
|
+
const [autoApproveMode, setAutoApproveMode] = useState6(autoApprove);
|
|
14009
14151
|
const {
|
|
14010
14152
|
agent,
|
|
14011
14153
|
messages,
|
|
@@ -14024,11 +14166,11 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
14024
14166
|
addMessage,
|
|
14025
14167
|
refreshStats
|
|
14026
14168
|
} = useAgent(autoApproveMode, target);
|
|
14027
|
-
const isProcessingRef =
|
|
14169
|
+
const isProcessingRef = useRef6(isProcessing);
|
|
14028
14170
|
isProcessingRef.current = isProcessing;
|
|
14029
|
-
const autoApproveModeRef =
|
|
14171
|
+
const autoApproveModeRef = useRef6(autoApproveMode);
|
|
14030
14172
|
autoApproveModeRef.current = autoApproveMode;
|
|
14031
|
-
const inputRequestRef =
|
|
14173
|
+
const inputRequestRef = useRef6(inputRequest);
|
|
14032
14174
|
inputRequestRef.current = inputRequest;
|
|
14033
14175
|
const handleExit = useCallback4(() => {
|
|
14034
14176
|
const ir = inputRequestRef.current;
|
|
@@ -14168,7 +14310,12 @@ ${procData.stdout || "(no output)"}
|
|
|
14168
14310
|
break;
|
|
14169
14311
|
case UI_COMMANDS.CTF:
|
|
14170
14312
|
const ctfEnabled = agent.toggleCtfMode();
|
|
14171
|
-
addMessage("system", ctfEnabled ? "
|
|
14313
|
+
addMessage("system", ctfEnabled ? "\u{1F3F4} Flag auto-detection ON" : "\u{1F3F4} Flag auto-detection OFF");
|
|
14314
|
+
break;
|
|
14315
|
+
case UI_COMMANDS.TOR:
|
|
14316
|
+
const newTorState = !isTorEnabled();
|
|
14317
|
+
setTorEnabled(newTorState);
|
|
14318
|
+
addMessage("system", newTorState ? "\u{1F9C5} Tor proxy ON \u2014 target traffic routed through SOCKS5 (proxychains4 / native flags)" : "\u{1F9C5} Tor proxy OFF \u2014 direct connections");
|
|
14172
14319
|
break;
|
|
14173
14320
|
case UI_COMMANDS.GRAPH:
|
|
14174
14321
|
case UI_COMMANDS.GRAPH_SHORT:
|
|
@@ -14220,8 +14367,8 @@ ${procData.stdout || "(no output)"}
|
|
|
14220
14367
|
setInputRequest({ status: "inactive" });
|
|
14221
14368
|
setSecretInput("");
|
|
14222
14369
|
}, [addMessage, setInputRequest]);
|
|
14223
|
-
const ctrlCTimerRef =
|
|
14224
|
-
const ctrlCPressedRef =
|
|
14370
|
+
const ctrlCTimerRef = useRef6(null);
|
|
14371
|
+
const ctrlCPressedRef = useRef6(false);
|
|
14225
14372
|
const handleCtrlC = useCallback4(() => {
|
|
14226
14373
|
if (ctrlCPressedRef.current) {
|
|
14227
14374
|
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
@@ -14236,7 +14383,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14236
14383
|
ctrlCTimerRef.current = null;
|
|
14237
14384
|
}, 3e3);
|
|
14238
14385
|
}, [handleExit, addMessage, abort]);
|
|
14239
|
-
|
|
14386
|
+
useEffect5(() => {
|
|
14240
14387
|
return () => {
|
|
14241
14388
|
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
14242
14389
|
};
|
|
@@ -14248,7 +14395,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14248
14395
|
}
|
|
14249
14396
|
if (key.ctrl && ch === "c") handleCtrlC();
|
|
14250
14397
|
}, [cancelInputRequest, abort, handleCtrlC]));
|
|
14251
|
-
|
|
14398
|
+
useEffect5(() => {
|
|
14252
14399
|
const onSignal = () => handleCtrlC();
|
|
14253
14400
|
process.on("SIGINT", onSignal);
|
|
14254
14401
|
process.on("SIGTERM", onSignal);
|
|
@@ -14257,16 +14404,21 @@ ${procData.stdout || "(no output)"}
|
|
|
14257
14404
|
process.off("SIGTERM", onSignal);
|
|
14258
14405
|
};
|
|
14259
14406
|
}, [handleCtrlC]);
|
|
14407
|
+
const isSlashMode = input.startsWith("/");
|
|
14408
|
+
const partialCmd = isSlashMode ? input.slice(1).split(" ")[0] : "";
|
|
14409
|
+
const hasArgs = isSlashMode && input.includes(" ");
|
|
14410
|
+
const suggestionCount = isSlashMode && !hasArgs && inputRequest.status !== "active" ? Math.min(getMatchingCommands(partialCmd).length, MAX_SUGGESTIONS) : 0;
|
|
14411
|
+
const previewHeight = suggestionCount > 0 ? suggestionCount + 2 : 0;
|
|
14412
|
+
const bottomHeight = 6 + previewHeight;
|
|
14260
14413
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
|
|
14261
14414
|
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
|
|
14262
|
-
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height:
|
|
14415
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: bottomHeight, children: [
|
|
14263
14416
|
/* @__PURE__ */ jsx7(
|
|
14264
14417
|
StatusDisplay,
|
|
14265
14418
|
{
|
|
14266
14419
|
retryState,
|
|
14267
14420
|
isProcessing,
|
|
14268
14421
|
currentStatus,
|
|
14269
|
-
elapsedTime,
|
|
14270
14422
|
currentTokens
|
|
14271
14423
|
}
|
|
14272
14424
|
),
|
|
@@ -14325,6 +14477,9 @@ var CLI_SCAN_TYPES = Object.freeze([
|
|
|
14325
14477
|
import gradient from "gradient-string";
|
|
14326
14478
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
14327
14479
|
initDebugLogger();
|
|
14480
|
+
if (process.env.PENTEST_TOR === "true") {
|
|
14481
|
+
setTorEnabled(true);
|
|
14482
|
+
}
|
|
14328
14483
|
var _configErrors = validateRequiredConfig();
|
|
14329
14484
|
if (_configErrors.length > 0) {
|
|
14330
14485
|
_configErrors.forEach((e) => console.error(chalk.hex(HEX.red)(e)));
|