pentesting 0.52.2 → 0.54.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/dist/main.js +373 -194
- package/dist/network/prompt.md +3 -3
- package/dist/prompts/base.md +131 -568
- package/dist/prompts/evasion.md +1 -1
- package/dist/prompts/{ctf-mode.md → offensive-playbook.md} +40 -101
- package/dist/prompts/orchestrator.md +83 -263
- package/dist/prompts/recon.md +1 -1
- package/dist/prompts/strategy.md +88 -608
- package/package.json +3 -2
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.0";
|
|
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",
|
|
@@ -931,7 +925,6 @@ var DEFAULT_DIRECTIVE_FOCUS = PHASES.RECON;
|
|
|
931
925
|
var ALLOWED_BINARIES = /* @__PURE__ */ new Set([
|
|
932
926
|
// Network scanning
|
|
933
927
|
"nmap",
|
|
934
|
-
"masscan",
|
|
935
928
|
"rustscan",
|
|
936
929
|
// Web scanning
|
|
937
930
|
"ffuf",
|
|
@@ -1557,7 +1550,6 @@ function extractBinary(command) {
|
|
|
1557
1550
|
import { spawn } from "child_process";
|
|
1558
1551
|
var TOOL_PACKAGE_MAP = {
|
|
1559
1552
|
"nmap": { apt: "nmap", brew: "nmap" },
|
|
1560
|
-
"masscan": { apt: "masscan", brew: "masscan" },
|
|
1561
1553
|
"rustscan": { apt: "rustscan", brew: "rustscan" },
|
|
1562
1554
|
"ffuf": { apt: "ffuf", brew: "ffuf" },
|
|
1563
1555
|
"gobuster": { apt: "gobuster", brew: "gobuster" },
|
|
@@ -1732,6 +1724,161 @@ async function installTool(toolName, eventEmitter, inputHandler) {
|
|
|
1732
1724
|
});
|
|
1733
1725
|
}
|
|
1734
1726
|
|
|
1727
|
+
// src/shared/utils/config.ts
|
|
1728
|
+
var ENV_KEYS = {
|
|
1729
|
+
API_KEY: "PENTEST_API_KEY",
|
|
1730
|
+
BASE_URL: "PENTEST_BASE_URL",
|
|
1731
|
+
MODEL: "PENTEST_MODEL",
|
|
1732
|
+
SEARCH_API_KEY: "SEARCH_API_KEY",
|
|
1733
|
+
SEARCH_API_URL: "SEARCH_API_URL",
|
|
1734
|
+
THINKING: "PENTEST_THINKING",
|
|
1735
|
+
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
1736
|
+
};
|
|
1737
|
+
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
1738
|
+
var DEFAULT_MODEL = "glm-4.7";
|
|
1739
|
+
function getApiKey() {
|
|
1740
|
+
return process.env[ENV_KEYS.API_KEY] || "";
|
|
1741
|
+
}
|
|
1742
|
+
function getBaseUrl() {
|
|
1743
|
+
return process.env[ENV_KEYS.BASE_URL] || void 0;
|
|
1744
|
+
}
|
|
1745
|
+
function getModel() {
|
|
1746
|
+
return process.env[ENV_KEYS.MODEL] || "";
|
|
1747
|
+
}
|
|
1748
|
+
function getSearchApiKey() {
|
|
1749
|
+
if (process.env[ENV_KEYS.SEARCH_API_KEY]) {
|
|
1750
|
+
return process.env[ENV_KEYS.SEARCH_API_KEY];
|
|
1751
|
+
}
|
|
1752
|
+
return process.env[ENV_KEYS.API_KEY];
|
|
1753
|
+
}
|
|
1754
|
+
function getSearchApiUrl() {
|
|
1755
|
+
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
1756
|
+
}
|
|
1757
|
+
function isZaiProvider() {
|
|
1758
|
+
const baseUrl = getBaseUrl() || "";
|
|
1759
|
+
return baseUrl.includes("z.ai");
|
|
1760
|
+
}
|
|
1761
|
+
function isThinkingEnabled() {
|
|
1762
|
+
return process.env[ENV_KEYS.THINKING] === "true";
|
|
1763
|
+
}
|
|
1764
|
+
function getThinkingBudget() {
|
|
1765
|
+
const val = parseInt(process.env[ENV_KEYS.THINKING_BUDGET] || "", 10);
|
|
1766
|
+
return isNaN(val) ? 8e3 : Math.max(1024, val);
|
|
1767
|
+
}
|
|
1768
|
+
var TOR_PROXY = {
|
|
1769
|
+
/** SOCKS5 proxy host (Tor daemon listens here) */
|
|
1770
|
+
SOCKS_HOST: "127.0.0.1",
|
|
1771
|
+
/** SOCKS5 proxy port */
|
|
1772
|
+
SOCKS_PORT: 9050,
|
|
1773
|
+
/** Shell wrapper command for proxying CLI tools */
|
|
1774
|
+
WRAPPER_CMD: "proxychains4",
|
|
1775
|
+
/** Flags for the wrapper command (-q = quiet, no banners) */
|
|
1776
|
+
WRAPPER_FLAGS: "-q"
|
|
1777
|
+
};
|
|
1778
|
+
var torEnabled = false;
|
|
1779
|
+
function isTorEnabled() {
|
|
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 };
|
|
1816
|
+
}
|
|
1817
|
+
function wrapCommandForTor(command) {
|
|
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
|
+
}
|
|
1854
|
+
return `${TOR_PROXY.WRAPPER_CMD} ${TOR_PROXY.WRAPPER_FLAGS} ${command}`;
|
|
1855
|
+
}
|
|
1856
|
+
function getTorBrowserArgs() {
|
|
1857
|
+
if (!isTorEnabled()) return [];
|
|
1858
|
+
return [`--proxy-server=socks5://${TOR_PROXY.SOCKS_HOST}:${TOR_PROXY.SOCKS_PORT}`];
|
|
1859
|
+
}
|
|
1860
|
+
function validateRequiredConfig() {
|
|
1861
|
+
const errors = [];
|
|
1862
|
+
if (!getApiKey()) {
|
|
1863
|
+
errors.push(
|
|
1864
|
+
`[config] PENTEST_API_KEY is required.
|
|
1865
|
+
Export it before running:
|
|
1866
|
+
export PENTEST_API_KEY=your_api_key
|
|
1867
|
+
For z.ai: get your key at https://api.z.ai`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
const budgetRaw = process.env[ENV_KEYS.THINKING_BUDGET];
|
|
1871
|
+
if (budgetRaw !== void 0 && budgetRaw !== "") {
|
|
1872
|
+
const parsed = parseInt(budgetRaw, 10);
|
|
1873
|
+
if (isNaN(parsed) || parsed < 1024) {
|
|
1874
|
+
errors.push(
|
|
1875
|
+
`[config] PENTEST_THINKING_BUDGET must be an integer \u2265 1024 (got: "${budgetRaw}")`
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
return errors;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1735
1882
|
// src/engine/tools-base.ts
|
|
1736
1883
|
var globalEventEmitter = null;
|
|
1737
1884
|
function setCommandEventEmitter(emitter) {
|
|
@@ -1757,6 +1904,16 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1757
1904
|
error: `Command blocked: ${validation.error}`
|
|
1758
1905
|
};
|
|
1759
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
|
+
}
|
|
1760
1917
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1761
1918
|
const maxRetries = options.maxRetries ?? AGENT_LIMITS.MAX_INSTALL_RETRIES;
|
|
1762
1919
|
const toolName = command.split(/[\s/]/).pop() || command;
|
|
@@ -1794,15 +1951,22 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1794
1951
|
}
|
|
1795
1952
|
return lastResult;
|
|
1796
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
|
+
}
|
|
1797
1959
|
async function executeCommandOnce(command, options = {}) {
|
|
1798
1960
|
return new Promise((resolve) => {
|
|
1799
1961
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1962
|
+
const safeCommand = injectCurlMaxTime(command, CURL_MAX_TIME_SEC);
|
|
1800
1963
|
globalEventEmitter?.({
|
|
1801
1964
|
type: COMMAND_EVENT_TYPES.COMMAND_START,
|
|
1802
|
-
message: `Executing: ${
|
|
1965
|
+
message: `Executing: ${safeCommand.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${safeCommand.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
|
|
1803
1966
|
});
|
|
1804
|
-
const
|
|
1805
|
-
|
|
1967
|
+
const execCommand = wrapCommandForTor(safeCommand);
|
|
1968
|
+
const child = spawn2("sh", ["-c", execCommand], {
|
|
1969
|
+
detached: true,
|
|
1806
1970
|
env: { ...process.env, ...options.env },
|
|
1807
1971
|
cwd: options.cwd
|
|
1808
1972
|
});
|
|
@@ -1810,6 +1974,20 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
1810
1974
|
let stderr = "";
|
|
1811
1975
|
let inputHandled = false;
|
|
1812
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);
|
|
1813
1991
|
const checkForInputPrompt = async (data) => {
|
|
1814
1992
|
if (inputHandled || !globalInputHandler || processTerminated) return;
|
|
1815
1993
|
for (const pattern of INPUT_PROMPT_PATTERNS) {
|
|
@@ -1843,7 +2021,21 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
1843
2021
|
checkForInputPrompt(text);
|
|
1844
2022
|
});
|
|
1845
2023
|
child.on("close", (code) => {
|
|
2024
|
+
clearTimeout(killTimer);
|
|
1846
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
|
+
}
|
|
1847
2039
|
if (code === 0) {
|
|
1848
2040
|
globalEventEmitter?.({
|
|
1849
2041
|
type: COMMAND_EVENT_TYPES.COMMAND_SUCCESS,
|
|
@@ -1867,6 +2059,7 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
1867
2059
|
}
|
|
1868
2060
|
});
|
|
1869
2061
|
child.on("error", (err) => {
|
|
2062
|
+
clearTimeout(killTimer);
|
|
1870
2063
|
processTerminated = true;
|
|
1871
2064
|
globalEventEmitter?.({
|
|
1872
2065
|
type: COMMAND_EVENT_TYPES.COMMAND_ERROR,
|
|
@@ -2118,12 +2311,23 @@ function startBackgroundProcess(command, options = {}) {
|
|
|
2118
2311
|
const stderrFile = createTempFile(FILE_EXTENSIONS.STDERR);
|
|
2119
2312
|
const stdinFile = createTempFile(FILE_EXTENSIONS.STDIN);
|
|
2120
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
|
+
}
|
|
2324
|
+
const torCommand = wrapCommandForTor(command);
|
|
2121
2325
|
let wrappedCmd;
|
|
2122
2326
|
if (isInteractive) {
|
|
2123
2327
|
writeFileSync3(stdinFile, "", "utf-8");
|
|
2124
|
-
wrappedCmd = `tail -f ${stdinFile} | ${
|
|
2328
|
+
wrappedCmd = `tail -f ${stdinFile} | ${torCommand} > ${stdoutFile} 2> ${stderrFile}`;
|
|
2125
2329
|
} else {
|
|
2126
|
-
wrappedCmd = `${
|
|
2330
|
+
wrappedCmd = `${torCommand} > ${stdoutFile} 2> ${stderrFile}`;
|
|
2127
2331
|
}
|
|
2128
2332
|
const child = spawn3("sh", ["-c", wrappedCmd], {
|
|
2129
2333
|
detached: true,
|
|
@@ -2559,7 +2763,7 @@ var StateSerializer = class {
|
|
|
2559
2763
|
lines.push(resourceInfo);
|
|
2560
2764
|
}
|
|
2561
2765
|
if (state.isCtfMode()) {
|
|
2562
|
-
lines.push(`
|
|
2766
|
+
lines.push(`Flag Detection: ON \u{1F3F4}`);
|
|
2563
2767
|
const flags = state.getFlags();
|
|
2564
2768
|
if (flags.length > 0) {
|
|
2565
2769
|
lines.push(`Flags Found (${flags.length}):`);
|
|
@@ -2568,6 +2772,16 @@ var StateSerializer = class {
|
|
|
2568
2772
|
}
|
|
2569
2773
|
}
|
|
2570
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
|
+
}
|
|
2571
2785
|
lines.push(`Phase: ${state.getPhase()}`);
|
|
2572
2786
|
return lines.join("\n");
|
|
2573
2787
|
}
|
|
@@ -2730,10 +2944,6 @@ var AttackGraph = class {
|
|
|
2730
2944
|
}
|
|
2731
2945
|
this.failedPaths.push(`${fromId} \u2192 ${toId}${reason ? ` (${reason})` : ""}`);
|
|
2732
2946
|
}
|
|
2733
|
-
// Backward-compatible alias
|
|
2734
|
-
markExploited(nodeId) {
|
|
2735
|
-
this.markSucceeded(nodeId);
|
|
2736
|
-
}
|
|
2737
2947
|
// ─── Domain-Specific Registration ───────────────────────────
|
|
2738
2948
|
/**
|
|
2739
2949
|
* Record a host discovery.
|
|
@@ -2792,7 +3002,7 @@ var AttackGraph = class {
|
|
|
2792
3002
|
hasExploit
|
|
2793
3003
|
});
|
|
2794
3004
|
for (const [id, node] of this.nodes) {
|
|
2795
|
-
if (node.type ===
|
|
3005
|
+
if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
|
|
2796
3006
|
this.addEdge(id, vulnId, "has_vulnerability", 0.8);
|
|
2797
3007
|
}
|
|
2798
3008
|
}
|
|
@@ -2834,7 +3044,7 @@ var AttackGraph = class {
|
|
|
2834
3044
|
...data
|
|
2835
3045
|
});
|
|
2836
3046
|
for (const [id, node] of this.nodes) {
|
|
2837
|
-
if (node.type ===
|
|
3047
|
+
if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
|
|
2838
3048
|
const hostIp = String(node.data.ip || node.data.host || "");
|
|
2839
3049
|
const hostname = String(node.data.hostname || "");
|
|
2840
3050
|
if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
|
|
@@ -3254,23 +3464,6 @@ var WorkingMemory = class {
|
|
|
3254
3464
|
(e) => e.category === "failure" && e.fingerprint != null && fingerprintsMatch(e.fingerprint, fp)
|
|
3255
3465
|
);
|
|
3256
3466
|
}
|
|
3257
|
-
/**
|
|
3258
|
-
* Get all previous attempts (success & failure) for a specific tool+target vector.
|
|
3259
|
-
* Returns the full history so the LLM can see what parameter combinations were tried.
|
|
3260
|
-
*
|
|
3261
|
-
* TODO: Wire up in tools.ts to pass vector history to strategist for smarter retries.
|
|
3262
|
-
*/
|
|
3263
|
-
getAttemptsForVector(tool, target) {
|
|
3264
|
-
const lowerTool = tool.toLowerCase();
|
|
3265
|
-
return this.entries.filter((e) => {
|
|
3266
|
-
if (e.fingerprint) {
|
|
3267
|
-
const matchTool = e.fingerprint.tool === lowerTool;
|
|
3268
|
-
const matchTarget = !target || e.fingerprint.target.includes(target);
|
|
3269
|
-
return matchTool && matchTarget;
|
|
3270
|
-
}
|
|
3271
|
-
return String(e.context.tool || "").toLowerCase() === lowerTool;
|
|
3272
|
-
});
|
|
3273
|
-
}
|
|
3274
3467
|
/** Internal prune helper (used by both add() and recordFailure()) */
|
|
3275
3468
|
pruneIfNeeded() {
|
|
3276
3469
|
if (this.entries.length > this.maxEntries) {
|
|
@@ -3298,26 +3491,6 @@ var WorkingMemory = class {
|
|
|
3298
3491
|
}
|
|
3299
3492
|
return count;
|
|
3300
3493
|
}
|
|
3301
|
-
/**
|
|
3302
|
-
* Get consecutive failures for a specific vector (tool+target).
|
|
3303
|
-
* Returns count of sequential failures where all attempts used the same tool+target.
|
|
3304
|
-
*
|
|
3305
|
-
* TODO: Wire up in tools.ts for per-vector threshold checks alongside global consecutive count.
|
|
3306
|
-
*/
|
|
3307
|
-
getConsecutiveVectorFailures(tool, target) {
|
|
3308
|
-
const lowerTool = tool.toLowerCase();
|
|
3309
|
-
let count = 0;
|
|
3310
|
-
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
3311
|
-
const e = this.entries[i];
|
|
3312
|
-
if (e.category !== "failure") break;
|
|
3313
|
-
if (e.fingerprint && e.fingerprint.tool === lowerTool && (!target || e.fingerprint.target.includes(target))) {
|
|
3314
|
-
count++;
|
|
3315
|
-
} else {
|
|
3316
|
-
break;
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
return count;
|
|
3320
|
-
}
|
|
3321
3494
|
/**
|
|
3322
3495
|
* Format for prompt injection.
|
|
3323
3496
|
*/
|
|
@@ -3757,7 +3930,7 @@ var SharedState = class {
|
|
|
3757
3930
|
missionSummary: "",
|
|
3758
3931
|
missionChecklist: [],
|
|
3759
3932
|
ctfMode: true,
|
|
3760
|
-
//
|
|
3933
|
+
// Flag auto-detection ON by default
|
|
3761
3934
|
flags: [],
|
|
3762
3935
|
startedAt: now,
|
|
3763
3936
|
// Auto-configure from PENTEST_DURATION env (seconds).
|
|
@@ -3933,7 +4106,7 @@ var SharedState = class {
|
|
|
3933
4106
|
getPhase() {
|
|
3934
4107
|
return this.data.currentPhase;
|
|
3935
4108
|
}
|
|
3936
|
-
// ---
|
|
4109
|
+
// --- Flag Detection ---
|
|
3937
4110
|
setCtfMode(shouldEnable) {
|
|
3938
4111
|
this.data.ctfMode = shouldEnable;
|
|
3939
4112
|
}
|
|
@@ -4288,7 +4461,6 @@ var NOISE_CLASSIFICATION = {
|
|
|
4288
4461
|
"dns_spoof",
|
|
4289
4462
|
"traffic_intercept",
|
|
4290
4463
|
"nmap",
|
|
4291
|
-
"masscan",
|
|
4292
4464
|
"nuclei",
|
|
4293
4465
|
"nikto"
|
|
4294
4466
|
],
|
|
@@ -5156,10 +5328,27 @@ All ports freed. All children killed.`
|
|
|
5156
5328
|
},
|
|
5157
5329
|
{
|
|
5158
5330
|
name: TOOL_NAMES.WRITE_FILE,
|
|
5159
|
-
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.`,
|
|
5160
5349
|
parameters: {
|
|
5161
|
-
path: { type: "string", description: "Absolute path
|
|
5162
|
-
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" }
|
|
5163
5352
|
},
|
|
5164
5353
|
required: ["path", "content"],
|
|
5165
5354
|
execute: async (params) => writeFileContent(params.path, params.content)
|
|
@@ -5594,69 +5783,6 @@ ${formatValidation(validation)}${rawOverride !== void 0 ? ` [confidence overridd
|
|
|
5594
5783
|
// src/engine/tools/intel-utils.ts
|
|
5595
5784
|
import { execFileSync } from "child_process";
|
|
5596
5785
|
|
|
5597
|
-
// src/shared/utils/config.ts
|
|
5598
|
-
var ENV_KEYS = {
|
|
5599
|
-
API_KEY: "PENTEST_API_KEY",
|
|
5600
|
-
BASE_URL: "PENTEST_BASE_URL",
|
|
5601
|
-
MODEL: "PENTEST_MODEL",
|
|
5602
|
-
SEARCH_API_KEY: "SEARCH_API_KEY",
|
|
5603
|
-
SEARCH_API_URL: "SEARCH_API_URL",
|
|
5604
|
-
THINKING: "PENTEST_THINKING",
|
|
5605
|
-
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
5606
|
-
};
|
|
5607
|
-
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
5608
|
-
var DEFAULT_MODEL = "glm-4.7";
|
|
5609
|
-
function getApiKey() {
|
|
5610
|
-
return process.env[ENV_KEYS.API_KEY] || "";
|
|
5611
|
-
}
|
|
5612
|
-
function getBaseUrl() {
|
|
5613
|
-
return process.env[ENV_KEYS.BASE_URL] || void 0;
|
|
5614
|
-
}
|
|
5615
|
-
function getModel() {
|
|
5616
|
-
return process.env[ENV_KEYS.MODEL] || "";
|
|
5617
|
-
}
|
|
5618
|
-
function getSearchApiKey() {
|
|
5619
|
-
if (process.env[ENV_KEYS.SEARCH_API_KEY]) {
|
|
5620
|
-
return process.env[ENV_KEYS.SEARCH_API_KEY];
|
|
5621
|
-
}
|
|
5622
|
-
return process.env[ENV_KEYS.API_KEY];
|
|
5623
|
-
}
|
|
5624
|
-
function getSearchApiUrl() {
|
|
5625
|
-
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
5626
|
-
}
|
|
5627
|
-
function isZaiProvider() {
|
|
5628
|
-
const baseUrl = getBaseUrl() || "";
|
|
5629
|
-
return baseUrl.includes("z.ai");
|
|
5630
|
-
}
|
|
5631
|
-
function isThinkingEnabled() {
|
|
5632
|
-
return process.env[ENV_KEYS.THINKING] === "true";
|
|
5633
|
-
}
|
|
5634
|
-
function getThinkingBudget() {
|
|
5635
|
-
const val = parseInt(process.env[ENV_KEYS.THINKING_BUDGET] || "", 10);
|
|
5636
|
-
return isNaN(val) ? 8e3 : Math.max(1024, val);
|
|
5637
|
-
}
|
|
5638
|
-
function validateRequiredConfig() {
|
|
5639
|
-
const errors = [];
|
|
5640
|
-
if (!getApiKey()) {
|
|
5641
|
-
errors.push(
|
|
5642
|
-
`[config] PENTEST_API_KEY is required.
|
|
5643
|
-
Export it before running:
|
|
5644
|
-
export PENTEST_API_KEY=your_api_key
|
|
5645
|
-
For z.ai: get your key at https://api.z.ai`
|
|
5646
|
-
);
|
|
5647
|
-
}
|
|
5648
|
-
const budgetRaw = process.env[ENV_KEYS.THINKING_BUDGET];
|
|
5649
|
-
if (budgetRaw !== void 0 && budgetRaw !== "") {
|
|
5650
|
-
const parsed = parseInt(budgetRaw, 10);
|
|
5651
|
-
if (isNaN(parsed) || parsed < 1024) {
|
|
5652
|
-
errors.push(
|
|
5653
|
-
`[config] PENTEST_THINKING_BUDGET must be an integer \u2265 1024 (got: "${budgetRaw}")`
|
|
5654
|
-
);
|
|
5655
|
-
}
|
|
5656
|
-
}
|
|
5657
|
-
return errors;
|
|
5658
|
-
}
|
|
5659
|
-
|
|
5660
5786
|
// src/shared/constants/search-api.const.ts
|
|
5661
5787
|
var SEARCH_URL_PATTERN = {
|
|
5662
5788
|
GLM: "bigmodel.cn",
|
|
@@ -5880,7 +6006,7 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
5880
6006
|
(async () => {
|
|
5881
6007
|
const browser = await chromium.launch({
|
|
5882
6008
|
headless: true,
|
|
5883
|
-
args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}']
|
|
6009
|
+
args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}', ${JSON.stringify(getTorBrowserArgs()).slice(1, -1)}].filter(Boolean)
|
|
5884
6010
|
});
|
|
5885
6011
|
|
|
5886
6012
|
const context = await browser.newContext({
|
|
@@ -6066,7 +6192,10 @@ async function fillAndSubmitForm(url, formData, options = {}) {
|
|
|
6066
6192
|
const { chromium } = require(${safePlaywrightPath});
|
|
6067
6193
|
|
|
6068
6194
|
(async () => {
|
|
6069
|
-
const browser = await chromium.launch({
|
|
6195
|
+
const browser = await chromium.launch({
|
|
6196
|
+
headless: true,
|
|
6197
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox', ${JSON.stringify(getTorBrowserArgs()).slice(1, -1)}].filter(Boolean)
|
|
6198
|
+
});
|
|
6070
6199
|
const page = await browser.newPage();
|
|
6071
6200
|
|
|
6072
6201
|
try {
|
|
@@ -7520,16 +7649,20 @@ Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt
|
|
|
7520
7649
|
if (wordlist === "rockyou") wordlistPath = WORDLISTS.ROCKYOU;
|
|
7521
7650
|
const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
|
|
7522
7651
|
if (background) {
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
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}).
|
|
7530
7660
|
Command: ${cmd}
|
|
7531
7661
|
Check status with: bg_process({ action: "status", process_id: "${proc.id}" })`
|
|
7532
|
-
|
|
7662
|
+
};
|
|
7663
|
+
} catch (err) {
|
|
7664
|
+
return { success: false, output: "", error: `Failed to start: ${err}` };
|
|
7665
|
+
}
|
|
7533
7666
|
} else {
|
|
7534
7667
|
return runCommand(cmd);
|
|
7535
7668
|
}
|
|
@@ -10737,7 +10870,7 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
10737
10870
|
}
|
|
10738
10871
|
}
|
|
10739
10872
|
// ─────────────────────────────────────────────────────────────────
|
|
10740
|
-
// SUBSECTION:
|
|
10873
|
+
// SUBSECTION: Flag Detection
|
|
10741
10874
|
// ─────────────────────────────────────────────────────────────────
|
|
10742
10875
|
scanForFlags(output) {
|
|
10743
10876
|
if (!this.state.isCtfMode()) return;
|
|
@@ -11075,12 +11208,12 @@ Phase: ${phase} | Targets: ${targets} | Findings: ${findings}
|
|
|
11075
11208
|
|
|
11076
11209
|
${direction}
|
|
11077
11210
|
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
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
|
|
11084
11217
|
|
|
11085
11218
|
ACT NOW \u2014 EXECUTE.`;
|
|
11086
11219
|
}
|
|
@@ -11187,7 +11320,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
11187
11320
|
// src/shared/constants/prompts.ts
|
|
11188
11321
|
var PROMPT_PATHS = {
|
|
11189
11322
|
BASE: "base.md",
|
|
11190
|
-
|
|
11323
|
+
OFFENSIVE_PLAYBOOK: "offensive-playbook.md",
|
|
11191
11324
|
AGENT_FILES: {
|
|
11192
11325
|
ORCHESTRATOR: "orchestrator.md",
|
|
11193
11326
|
RECON: "recon.md",
|
|
@@ -11665,9 +11798,9 @@ var PHASE_PROMPT_MAP = {
|
|
|
11665
11798
|
};
|
|
11666
11799
|
var CORE_KNOWLEDGE_FILES = [
|
|
11667
11800
|
AGENT_FILES.STRATEGY,
|
|
11668
|
-
// Attack prioritization, first-turn protocol, upgrade loop
|
|
11801
|
+
// Attack prioritization, first-turn protocol, upgrade loop (~2K tok)
|
|
11669
11802
|
AGENT_FILES.ORCHESTRATOR,
|
|
11670
|
-
//
|
|
11803
|
+
// Kill chain position, phase transitions, multi-target (~2K tok)
|
|
11671
11804
|
AGENT_FILES.EVASION,
|
|
11672
11805
|
// Detection avoidance (always relevant)
|
|
11673
11806
|
AGENT_FILES.ZERO_DAY,
|
|
@@ -11707,9 +11840,10 @@ var PromptBuilder = class {
|
|
|
11707
11840
|
* Build complete prompt for LLM iteration (async).
|
|
11708
11841
|
*
|
|
11709
11842
|
* Layers (phase-aware, enhanced with D-CIPHER meta-prompting):
|
|
11710
|
-
* 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)
|
|
11711
11845
|
* 2. Phase-specific prompt — current phase's full specialist knowledge (~2K tok)
|
|
11712
|
-
* 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~
|
|
11846
|
+
* 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~8K tok)
|
|
11713
11847
|
* 4. Phase-relevant techniques — only attack techniques for current phase (~4-8K tok)
|
|
11714
11848
|
* 5. Scope constraints
|
|
11715
11849
|
* 6. Current state (targets, findings, loot, active processes)
|
|
@@ -11729,7 +11863,7 @@ var PromptBuilder = class {
|
|
|
11729
11863
|
async build(userInput, phase) {
|
|
11730
11864
|
const fragments = [
|
|
11731
11865
|
this.loadPromptFile(PROMPT_PATHS.BASE),
|
|
11732
|
-
this.
|
|
11866
|
+
this.loadOffensivePlaybook(),
|
|
11733
11867
|
this.loadPhasePrompt(phase),
|
|
11734
11868
|
this.loadCoreKnowledge(phase),
|
|
11735
11869
|
this.loadPhaseRelevantTechniques(phase),
|
|
@@ -11762,15 +11896,14 @@ var PromptBuilder = class {
|
|
|
11762
11896
|
return fragments.filter((f) => !!f).join("\n\n");
|
|
11763
11897
|
}
|
|
11764
11898
|
/**
|
|
11765
|
-
* Load
|
|
11766
|
-
* Adds ~3K tokens of
|
|
11899
|
+
* Load offensive playbook — attack methodology always active.
|
|
11900
|
+
* Adds ~3K tokens of time management, technique quick-starts, aggression rules.
|
|
11767
11901
|
*/
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
return content ? `<ctf-mode active="true">
|
|
11902
|
+
loadOffensivePlaybook() {
|
|
11903
|
+
const content = this.loadPromptFile(PROMPT_PATHS.OFFENSIVE_PLAYBOOK);
|
|
11904
|
+
return content ? `<offensive-playbook>
|
|
11772
11905
|
${content}
|
|
11773
|
-
</
|
|
11906
|
+
</offensive-playbook>` : "";
|
|
11774
11907
|
}
|
|
11775
11908
|
/**
|
|
11776
11909
|
* Load a prompt file from src/agents/prompts/
|
|
@@ -12993,7 +13126,8 @@ var COMMAND_DEFINITIONS = [
|
|
|
12993
13126
|
{ name: "paths", alias: "p", description: "Show ranked attack paths" },
|
|
12994
13127
|
{ name: "assets", alias: "a", description: "List background processes" },
|
|
12995
13128
|
{ name: "logs", alias: "l", args: "<id>", description: "Show logs for an asset" },
|
|
12996
|
-
{ name: "ctf", description: "Toggle
|
|
13129
|
+
{ name: "ctf", description: "Toggle auto flag detection" },
|
|
13130
|
+
{ name: "tor", description: "Toggle Tor proxy routing" },
|
|
12997
13131
|
{ name: "auto", description: "Toggle auto-approve mode" },
|
|
12998
13132
|
{ name: "clear", alias: "c", description: "Reset session & clean workspace" },
|
|
12999
13133
|
{ name: "help", alias: "h", description: "Show detailed help" },
|
|
@@ -13018,7 +13152,8 @@ ${COMMAND_DEFINITIONS.map((cmd) => {
|
|
|
13018
13152
|
\u2022 Auto-install missing tools (apt/brew)
|
|
13019
13153
|
\u2022 Transparent command execution
|
|
13020
13154
|
\u2022 Interactive sudo password input
|
|
13021
|
-
\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)
|
|
13022
13157
|
\u2022 Attack graph: Tracks discovered paths & prevents repeating failures
|
|
13023
13158
|
|
|
13024
13159
|
\u2500\u2500 Tips \u2500\u2500
|
|
@@ -13044,6 +13179,7 @@ var useAgentState = () => {
|
|
|
13044
13179
|
const retryCountRef = useRef(0);
|
|
13045
13180
|
const tokenAccumRef = useRef(0);
|
|
13046
13181
|
const lastStepTokensRef = useRef(0);
|
|
13182
|
+
const toolStartedAtRef = useRef(0);
|
|
13047
13183
|
const addMessage = useCallback((type, content) => {
|
|
13048
13184
|
const id = Math.random().toString(36).substring(7);
|
|
13049
13185
|
setMessages((prev) => [...prev, { id, type, content, timestamp: /* @__PURE__ */ new Date() }]);
|
|
@@ -13106,7 +13242,8 @@ var useAgentState = () => {
|
|
|
13106
13242
|
addMessage,
|
|
13107
13243
|
resetCumulativeCounters,
|
|
13108
13244
|
manageTimer,
|
|
13109
|
-
clearAllTimers
|
|
13245
|
+
clearAllTimers,
|
|
13246
|
+
toolStartedAtRef
|
|
13110
13247
|
};
|
|
13111
13248
|
};
|
|
13112
13249
|
|
|
@@ -13125,19 +13262,22 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
13125
13262
|
retryCountRef,
|
|
13126
13263
|
tokenAccumRef,
|
|
13127
13264
|
lastStepTokensRef,
|
|
13128
|
-
clearAllTimers
|
|
13265
|
+
clearAllTimers,
|
|
13266
|
+
toolStartedAtRef
|
|
13129
13267
|
} = state;
|
|
13130
13268
|
const reasoningBufferRef = useRef2("");
|
|
13131
13269
|
useEffect(() => {
|
|
13132
13270
|
const events = eventsRef.current;
|
|
13133
13271
|
const onToolCall = (e) => {
|
|
13134
13272
|
if (NOISE_CLASSIFICATION.LOW_VISIBILITY.includes(e.data.toolName)) return;
|
|
13273
|
+
toolStartedAtRef.current = Date.now();
|
|
13135
13274
|
setCurrentStatus(`${e.data.toolName}\u2026`);
|
|
13136
13275
|
const inputStr = formatToolInput(e.data.toolName, e.data.input);
|
|
13137
13276
|
const label = inputStr ? `${toDisplayName(e.data.toolName)}(${inputStr})` : `${toDisplayName(e.data.toolName)}`;
|
|
13138
13277
|
addMessage("tool", label);
|
|
13139
13278
|
};
|
|
13140
13279
|
const onToolResult = (e) => {
|
|
13280
|
+
toolStartedAtRef.current = 0;
|
|
13141
13281
|
if (NOISE_CLASSIFICATION.LOW_VISIBILITY.includes(e.data.toolName)) {
|
|
13142
13282
|
return;
|
|
13143
13283
|
}
|
|
@@ -13296,6 +13436,7 @@ ${firstLine}`);
|
|
|
13296
13436
|
tokenAccumRef,
|
|
13297
13437
|
lastStepTokensRef,
|
|
13298
13438
|
clearAllTimers,
|
|
13439
|
+
toolStartedAtRef,
|
|
13299
13440
|
eventsRef
|
|
13300
13441
|
]);
|
|
13301
13442
|
};
|
|
@@ -13735,7 +13876,7 @@ var MessageList = memo(({ messages }) => {
|
|
|
13735
13876
|
});
|
|
13736
13877
|
|
|
13737
13878
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
13738
|
-
import { memo as memo3 } from "react";
|
|
13879
|
+
import { memo as memo3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
13739
13880
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
13740
13881
|
|
|
13741
13882
|
// src/platform/tui/components/MusicSpinner.tsx
|
|
@@ -13761,26 +13902,50 @@ var StatusDisplay = memo3(({
|
|
|
13761
13902
|
retryState,
|
|
13762
13903
|
isProcessing,
|
|
13763
13904
|
currentStatus,
|
|
13764
|
-
elapsedTime,
|
|
13765
13905
|
currentTokens
|
|
13766
13906
|
}) => {
|
|
13767
13907
|
const truncateError = (err) => {
|
|
13768
13908
|
return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
|
|
13769
13909
|
};
|
|
13770
|
-
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();
|
|
13771
13936
|
const isThinkingStatus = currentStatus.startsWith("Reasoning");
|
|
13772
13937
|
const statusLines = currentStatus ? currentStatus.split("\n").filter(Boolean) : [];
|
|
13773
13938
|
const statusMain = statusLines[0] || "Processing...";
|
|
13774
13939
|
if (retryState.status === "retrying") {
|
|
13775
13940
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13776
|
-
/* @__PURE__ */ jsx4(Text4, { color: THEME.yellow, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.yellow }) }),
|
|
13777
|
-
/* @__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: [
|
|
13778
13943
|
" \u27F3 Retry #",
|
|
13779
13944
|
retryState.attempt,
|
|
13780
13945
|
"/",
|
|
13781
13946
|
retryState.maxRetries
|
|
13782
13947
|
] }),
|
|
13783
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13948
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, wrap: "truncate", children: [
|
|
13784
13949
|
" \u2014 ",
|
|
13785
13950
|
retryState.countdown,
|
|
13786
13951
|
"s \xB7 ",
|
|
@@ -13791,12 +13956,12 @@ var StatusDisplay = memo3(({
|
|
|
13791
13956
|
if (isProcessing) {
|
|
13792
13957
|
const previewText = isThinkingStatus && statusLines.length > 1 ? `${statusMain} \u2014 ${statusLines[statusLines.length - 1]}` : statusMain;
|
|
13793
13958
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13794
|
-
/* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
|
|
13795
|
-
/* @__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: [
|
|
13796
13961
|
" ",
|
|
13797
13962
|
previewText
|
|
13798
13963
|
] }),
|
|
13799
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13964
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, wrap: "truncate", children: [
|
|
13800
13965
|
" ",
|
|
13801
13966
|
meta
|
|
13802
13967
|
] })
|
|
@@ -13806,7 +13971,7 @@ var StatusDisplay = memo3(({
|
|
|
13806
13971
|
});
|
|
13807
13972
|
|
|
13808
13973
|
// src/platform/tui/components/ChatInput.tsx
|
|
13809
|
-
import { useMemo, useCallback as useCallback3, useRef as
|
|
13974
|
+
import { useMemo, useCallback as useCallback3, useRef as useRef5, memo as memo4, useState as useState5 } from "react";
|
|
13810
13975
|
import { Box as Box4, Text as Text5, useInput } from "ink";
|
|
13811
13976
|
import TextInput from "ink-text-input";
|
|
13812
13977
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
@@ -13829,17 +13994,17 @@ var ChatInput = memo4(({
|
|
|
13829
13994
|
return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
|
|
13830
13995
|
}, [isSlashMode, partialCmd, hasArgs]);
|
|
13831
13996
|
const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
|
|
13832
|
-
const suggestionsRef =
|
|
13997
|
+
const suggestionsRef = useRef5(suggestions);
|
|
13833
13998
|
suggestionsRef.current = suggestions;
|
|
13834
|
-
const isSlashModeRef =
|
|
13999
|
+
const isSlashModeRef = useRef5(isSlashMode);
|
|
13835
14000
|
isSlashModeRef.current = isSlashMode;
|
|
13836
|
-
const hasArgsRef =
|
|
14001
|
+
const hasArgsRef = useRef5(hasArgs);
|
|
13837
14002
|
hasArgsRef.current = hasArgs;
|
|
13838
|
-
const inputRequestRef =
|
|
14003
|
+
const inputRequestRef = useRef5(inputRequest);
|
|
13839
14004
|
inputRequestRef.current = inputRequest;
|
|
13840
|
-
const onChangeRef =
|
|
14005
|
+
const onChangeRef = useRef5(onChange);
|
|
13841
14006
|
onChangeRef.current = onChange;
|
|
13842
|
-
const [inputKey, setInputKey] =
|
|
14007
|
+
const [inputKey, setInputKey] = useState5(0);
|
|
13843
14008
|
useInput(useCallback3((_input, key) => {
|
|
13844
14009
|
if (inputRequestRef.current.status === "active") return;
|
|
13845
14010
|
if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
|
|
@@ -13944,6 +14109,7 @@ var Footer = memo5(({ phase, targets, findings, todo, elapsedTime, isProcessing
|
|
|
13944
14109
|
paddingX: 1,
|
|
13945
14110
|
marginTop: 0,
|
|
13946
14111
|
justifyContent: "space-between",
|
|
14112
|
+
overflow: "hidden",
|
|
13947
14113
|
children: [
|
|
13948
14114
|
/* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
|
|
13949
14115
|
/* @__PURE__ */ jsxs5(Text6, { color: THEME.gray, children: [
|
|
@@ -13979,9 +14145,9 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
13979
14145
|
const { exit } = useApp();
|
|
13980
14146
|
const { stdout } = useStdout();
|
|
13981
14147
|
const terminalWidth = stdout?.columns ?? 80;
|
|
13982
|
-
const [input, setInput] =
|
|
13983
|
-
const [secretInput, setSecretInput] =
|
|
13984
|
-
const [autoApproveMode, setAutoApproveMode] =
|
|
14148
|
+
const [input, setInput] = useState6("");
|
|
14149
|
+
const [secretInput, setSecretInput] = useState6("");
|
|
14150
|
+
const [autoApproveMode, setAutoApproveMode] = useState6(autoApprove);
|
|
13985
14151
|
const {
|
|
13986
14152
|
agent,
|
|
13987
14153
|
messages,
|
|
@@ -14000,11 +14166,11 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
14000
14166
|
addMessage,
|
|
14001
14167
|
refreshStats
|
|
14002
14168
|
} = useAgent(autoApproveMode, target);
|
|
14003
|
-
const isProcessingRef =
|
|
14169
|
+
const isProcessingRef = useRef6(isProcessing);
|
|
14004
14170
|
isProcessingRef.current = isProcessing;
|
|
14005
|
-
const autoApproveModeRef =
|
|
14171
|
+
const autoApproveModeRef = useRef6(autoApproveMode);
|
|
14006
14172
|
autoApproveModeRef.current = autoApproveMode;
|
|
14007
|
-
const inputRequestRef =
|
|
14173
|
+
const inputRequestRef = useRef6(inputRequest);
|
|
14008
14174
|
inputRequestRef.current = inputRequest;
|
|
14009
14175
|
const handleExit = useCallback4(() => {
|
|
14010
14176
|
const ir = inputRequestRef.current;
|
|
@@ -14144,7 +14310,12 @@ ${procData.stdout || "(no output)"}
|
|
|
14144
14310
|
break;
|
|
14145
14311
|
case UI_COMMANDS.CTF:
|
|
14146
14312
|
const ctfEnabled = agent.toggleCtfMode();
|
|
14147
|
-
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");
|
|
14148
14319
|
break;
|
|
14149
14320
|
case UI_COMMANDS.GRAPH:
|
|
14150
14321
|
case UI_COMMANDS.GRAPH_SHORT:
|
|
@@ -14196,8 +14367,8 @@ ${procData.stdout || "(no output)"}
|
|
|
14196
14367
|
setInputRequest({ status: "inactive" });
|
|
14197
14368
|
setSecretInput("");
|
|
14198
14369
|
}, [addMessage, setInputRequest]);
|
|
14199
|
-
const ctrlCTimerRef =
|
|
14200
|
-
const ctrlCPressedRef =
|
|
14370
|
+
const ctrlCTimerRef = useRef6(null);
|
|
14371
|
+
const ctrlCPressedRef = useRef6(false);
|
|
14201
14372
|
const handleCtrlC = useCallback4(() => {
|
|
14202
14373
|
if (ctrlCPressedRef.current) {
|
|
14203
14374
|
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
@@ -14212,7 +14383,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14212
14383
|
ctrlCTimerRef.current = null;
|
|
14213
14384
|
}, 3e3);
|
|
14214
14385
|
}, [handleExit, addMessage, abort]);
|
|
14215
|
-
|
|
14386
|
+
useEffect5(() => {
|
|
14216
14387
|
return () => {
|
|
14217
14388
|
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
14218
14389
|
};
|
|
@@ -14224,7 +14395,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14224
14395
|
}
|
|
14225
14396
|
if (key.ctrl && ch === "c") handleCtrlC();
|
|
14226
14397
|
}, [cancelInputRequest, abort, handleCtrlC]));
|
|
14227
|
-
|
|
14398
|
+
useEffect5(() => {
|
|
14228
14399
|
const onSignal = () => handleCtrlC();
|
|
14229
14400
|
process.on("SIGINT", onSignal);
|
|
14230
14401
|
process.on("SIGTERM", onSignal);
|
|
@@ -14233,16 +14404,21 @@ ${procData.stdout || "(no output)"}
|
|
|
14233
14404
|
process.off("SIGTERM", onSignal);
|
|
14234
14405
|
};
|
|
14235
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;
|
|
14236
14413
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
|
|
14237
14414
|
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
|
|
14238
|
-
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height:
|
|
14415
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: bottomHeight, children: [
|
|
14239
14416
|
/* @__PURE__ */ jsx7(
|
|
14240
14417
|
StatusDisplay,
|
|
14241
14418
|
{
|
|
14242
14419
|
retryState,
|
|
14243
14420
|
isProcessing,
|
|
14244
14421
|
currentStatus,
|
|
14245
|
-
elapsedTime,
|
|
14246
14422
|
currentTokens
|
|
14247
14423
|
}
|
|
14248
14424
|
),
|
|
@@ -14301,6 +14477,9 @@ var CLI_SCAN_TYPES = Object.freeze([
|
|
|
14301
14477
|
import gradient from "gradient-string";
|
|
14302
14478
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
14303
14479
|
initDebugLogger();
|
|
14480
|
+
if (process.env.PENTEST_TOR === "true") {
|
|
14481
|
+
setTorEnabled(true);
|
|
14482
|
+
}
|
|
14304
14483
|
var _configErrors = validateRequiredConfig();
|
|
14305
14484
|
if (_configErrors.length > 0) {
|
|
14306
14485
|
_configErrors.forEach((e) => console.error(chalk.hex(HEX.red)(e)));
|