@pentoshi/clai 0.6.0 → 0.7.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/README.md +9 -17
- package/dist/agent/context-manager.d.ts +27 -0
- package/dist/agent/context-manager.js +75 -0
- package/dist/agent/context-manager.js.map +1 -0
- package/dist/agent/runner.d.ts +21 -1
- package/dist/agent/runner.js +176 -73
- package/dist/agent/runner.js.map +1 -1
- package/dist/commands/doctor.js +20 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/update.js +11 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +156 -5
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropic.js +29 -38
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/gemini.js +31 -40
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/http.d.ts +21 -0
- package/dist/llm/http.js +140 -1
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/ollama.js +18 -27
- package/dist/llm/ollama.js.map +1 -1
- package/dist/llm/router.d.ts +7 -0
- package/dist/llm/router.js +14 -23
- package/dist/llm/router.js.map +1 -1
- package/dist/modes/agent.d.ts +4 -2
- package/dist/modes/agent.js +2 -2
- package/dist/modes/agent.js.map +1 -1
- package/dist/modes/ask.js +3 -4
- package/dist/modes/ask.js.map +1 -1
- package/dist/os/pkgmgr.d.ts +7 -1
- package/dist/os/pkgmgr.js +97 -18
- package/dist/os/pkgmgr.js.map +1 -1
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.js +12 -4
- package/dist/prompts/index.js.map +1 -1
- package/dist/repl.d.ts +1 -0
- package/dist/repl.js +283 -43
- package/dist/repl.js.map +1 -1
- package/dist/safety/classifier.d.ts +5 -1
- package/dist/safety/classifier.js +244 -88
- package/dist/safety/classifier.js.map +1 -1
- package/dist/safety/patterns.d.ts +48 -1
- package/dist/safety/patterns.js +140 -7
- package/dist/safety/patterns.js.map +1 -1
- package/dist/store/config.d.ts +21 -3
- package/dist/store/config.js +28 -9
- package/dist/store/config.js.map +1 -1
- package/dist/store/history.d.ts +9 -0
- package/dist/store/history.js +58 -1
- package/dist/store/history.js.map +1 -1
- package/dist/store/keys.d.ts +2 -1
- package/dist/store/keys.js +7 -3
- package/dist/store/keys.js.map +1 -1
- package/dist/store/logs.d.ts +7 -0
- package/dist/store/logs.js +39 -1
- package/dist/store/logs.js.map +1 -1
- package/dist/store/project.d.ts +1 -0
- package/dist/store/project.js +34 -9
- package/dist/store/project.js.map +1 -1
- package/dist/store/scope.d.ts +29 -0
- package/dist/store/scope.js +113 -0
- package/dist/store/scope.js.map +1 -0
- package/dist/tools/fs.d.ts +6 -2
- package/dist/tools/fs.js +99 -87
- package/dist/tools/fs.js.map +1 -1
- package/dist/tools/http.d.ts +5 -3
- package/dist/tools/http.js +170 -31
- package/dist/tools/http.js.map +1 -1
- package/dist/tools/policies/output-policy.d.ts +13 -0
- package/dist/tools/policies/output-policy.js +56 -0
- package/dist/tools/policies/output-policy.js.map +1 -0
- package/dist/tools/reducers/ffuf.d.ts +6 -0
- package/dist/tools/reducers/ffuf.js +74 -0
- package/dist/tools/reducers/ffuf.js.map +1 -0
- package/dist/tools/reducers/generic.d.ts +2 -0
- package/dist/tools/reducers/generic.js +60 -0
- package/dist/tools/reducers/generic.js.map +1 -0
- package/dist/tools/reducers/gobuster.d.ts +2 -0
- package/dist/tools/reducers/gobuster.js +36 -0
- package/dist/tools/reducers/gobuster.js.map +1 -0
- package/dist/tools/reducers/httpx.d.ts +2 -0
- package/dist/tools/reducers/httpx.js +38 -0
- package/dist/tools/reducers/httpx.js.map +1 -0
- package/dist/tools/reducers/nmap.d.ts +7 -0
- package/dist/tools/reducers/nmap.js +82 -0
- package/dist/tools/reducers/nmap.js.map +1 -0
- package/dist/tools/reducers/nuclei.d.ts +2 -0
- package/dist/tools/reducers/nuclei.js +51 -0
- package/dist/tools/reducers/nuclei.js.map +1 -0
- package/dist/tools/reducers/sqlmap.d.ts +2 -0
- package/dist/tools/reducers/sqlmap.js +39 -0
- package/dist/tools/reducers/sqlmap.js.map +1 -0
- package/dist/tools/reducers/subdomains.d.ts +6 -0
- package/dist/tools/reducers/subdomains.js +31 -0
- package/dist/tools/reducers/subdomains.js.map +1 -0
- package/dist/tools/reducers/types.d.ts +14 -0
- package/dist/tools/reducers/types.js +2 -0
- package/dist/tools/reducers/types.js.map +1 -0
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.js +223 -79
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/shell.d.ts +45 -4
- package/dist/tools/shell.js +419 -88
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/validate.d.ts +37 -0
- package/dist/tools/validate.js +144 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/types.d.ts +7 -15
- package/dist/ui/keys.d.ts +21 -0
- package/dist/ui/keys.js +13 -0
- package/dist/ui/keys.js.map +1 -0
- package/dist/ui/output-pane.d.ts +31 -0
- package/dist/ui/output-pane.js +81 -0
- package/dist/ui/output-pane.js.map +1 -0
- package/package.json +1 -1
package/dist/tools/http.js
CHANGED
|
@@ -1,48 +1,187 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
import { lookup } from "node:dns/promises";
|
|
3
|
+
const DEFAULT_MAX_BYTES = 256 * 1024;
|
|
4
|
+
const ALLOWED_METHODS = new Set([
|
|
5
|
+
"GET",
|
|
6
|
+
"HEAD",
|
|
7
|
+
"POST",
|
|
8
|
+
"PUT",
|
|
9
|
+
"PATCH",
|
|
10
|
+
"DELETE",
|
|
11
|
+
"OPTIONS",
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* Block (or require explicit ownership confirmation for) requests that
|
|
15
|
+
* target loopback, private, link-local, or cloud-metadata addresses.
|
|
16
|
+
* This stops the agent from being tricked into SSRF against the host's
|
|
17
|
+
* intranet via a "fetch this URL" prompt.
|
|
18
|
+
*/
|
|
19
|
+
function isBlockedAddress(host) {
|
|
20
|
+
const lower = host.toLowerCase();
|
|
21
|
+
if (lower === "localhost" || lower === "localhost.localdomain")
|
|
22
|
+
return true;
|
|
23
|
+
if (lower === "ip6-localhost" || lower === "ip6-loopback")
|
|
24
|
+
return true;
|
|
25
|
+
if (net.isIPv4(host)) {
|
|
26
|
+
const parts = host.split(".").map((p) => Number(p));
|
|
27
|
+
const [a, b] = parts;
|
|
28
|
+
if (a === 127)
|
|
29
|
+
return true; // loopback
|
|
30
|
+
if (a === 10)
|
|
31
|
+
return true; // RFC1918
|
|
32
|
+
if (a === 172 && b !== undefined && b >= 16 && b <= 31)
|
|
33
|
+
return true; // RFC1918
|
|
34
|
+
if (a === 192 && b === 168)
|
|
35
|
+
return true; // RFC1918
|
|
36
|
+
if (a === 169 && b === 254)
|
|
37
|
+
return true; // link-local + cloud metadata
|
|
38
|
+
if (a === 0)
|
|
39
|
+
return true; // 0.0.0.0/8
|
|
40
|
+
if (a === 100 && b !== undefined && b >= 64 && b <= 127)
|
|
41
|
+
return true; // CGNAT
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (net.isIPv6(host)) {
|
|
45
|
+
if (lower === "::1")
|
|
46
|
+
return true;
|
|
47
|
+
if (lower.startsWith("fe80:"))
|
|
48
|
+
return true; // link-local
|
|
49
|
+
if (lower.startsWith("fc") || lower.startsWith("fd"))
|
|
50
|
+
return true; // ULA
|
|
51
|
+
if (lower.startsWith("::ffff:")) {
|
|
52
|
+
// IPv4-mapped IPv6 — re-check the embedded v4 address.
|
|
53
|
+
const v4 = lower.slice("::ffff:".length);
|
|
54
|
+
return isBlockedAddress(v4);
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
async function resolveHost(host) {
|
|
61
|
+
if (net.isIP(host))
|
|
62
|
+
return host;
|
|
63
|
+
try {
|
|
64
|
+
const result = await lookup(host);
|
|
65
|
+
return result.address;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
1
71
|
export async function httpFetch(url, options = {}) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
72
|
+
let target;
|
|
73
|
+
try {
|
|
74
|
+
target = new URL(url);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { ok: false, output: `Invalid URL: ${url}`, exitCode: 1 };
|
|
78
|
+
}
|
|
79
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
output: `Refusing non-http(s) scheme: ${target.protocol}`,
|
|
83
|
+
exitCode: 1,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
87
|
+
if (!ALLOWED_METHODS.has(method)) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
output: `Unsupported HTTP method: ${method}`,
|
|
91
|
+
exitCode: 1,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// SSRF guard: refuse loopback/private/link-local/metadata destinations
|
|
95
|
+
// unless the caller explicitly attested ownership of the target.
|
|
96
|
+
const hostname = target.hostname.replace(/^\[|\]$/g, "");
|
|
97
|
+
const literalBlocked = isBlockedAddress(hostname);
|
|
98
|
+
let resolvedBlocked = false;
|
|
99
|
+
if (!literalBlocked) {
|
|
100
|
+
const resolved = await resolveHost(hostname);
|
|
101
|
+
resolvedBlocked = Boolean(resolved && isBlockedAddress(resolved));
|
|
102
|
+
}
|
|
103
|
+
if ((literalBlocked || resolvedBlocked) && !options.iOwnThis) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
output: `Refusing to fetch private/loopback/metadata address ${hostname}. Pass iOwnThis=true to override.`,
|
|
107
|
+
exitCode: 1,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const init = { method };
|
|
111
|
+
if (options.body !== undefined && method !== "GET" && method !== "HEAD") {
|
|
112
|
+
init.body = options.body;
|
|
113
|
+
}
|
|
6
114
|
if (options.headers) {
|
|
7
115
|
init.headers = options.headers;
|
|
8
116
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
117
|
+
let response;
|
|
118
|
+
try {
|
|
119
|
+
response = await fetch(url, init);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
output: `Network error: ${error instanceof Error ? error.message : String(error)}`,
|
|
125
|
+
exitCode: 1,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const limit = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
129
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
130
|
+
let collected = "";
|
|
12
131
|
let bytesRead = 0;
|
|
13
132
|
let truncated = false;
|
|
14
133
|
const reader = response.body?.getReader();
|
|
15
134
|
if (reader) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
135
|
+
try {
|
|
136
|
+
while (bytesRead < limit) {
|
|
137
|
+
const { done, value } = await reader.read();
|
|
138
|
+
if (done)
|
|
139
|
+
break;
|
|
140
|
+
if (!value)
|
|
141
|
+
continue;
|
|
142
|
+
const remaining = limit - bytesRead;
|
|
143
|
+
if (value.byteLength > remaining) {
|
|
144
|
+
collected += decoder.decode(value.subarray(0, remaining), { stream: true });
|
|
145
|
+
bytesRead += remaining;
|
|
146
|
+
truncated = true;
|
|
147
|
+
try {
|
|
148
|
+
await reader.cancel();
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// ignore — we're abandoning the body deliberately
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
collected += decoder.decode(value, { stream: true });
|
|
156
|
+
bytesRead += value.byteLength;
|
|
27
157
|
}
|
|
28
|
-
|
|
29
|
-
bytesRead += value.byteLength;
|
|
158
|
+
collected += decoder.decode();
|
|
30
159
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
160
|
+
finally {
|
|
161
|
+
try {
|
|
162
|
+
reader.releaseLock();
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// already released
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// No streaming body (eg HEAD or empty 204). Fall through with empty text.
|
|
171
|
+
collected = "";
|
|
172
|
+
}
|
|
173
|
+
const headerLines = [];
|
|
174
|
+
response.headers.forEach((v, k) => headerLines.push(`${k}: ${v}`));
|
|
175
|
+
const headerBlock = headerLines.length > 0 ? `Headers:\n${headerLines.join("\n")}\n\n` : "";
|
|
176
|
+
const truncNote = truncated
|
|
177
|
+
? `\n... (truncated at ${limit.toLocaleString()} bytes)`
|
|
178
|
+
: "";
|
|
179
|
+
const body = method === "HEAD" ? "" : collected;
|
|
40
180
|
return {
|
|
41
181
|
ok: response.ok,
|
|
42
|
-
output: `${
|
|
182
|
+
output: `${response.status} ${response.statusText} ${response.url}\n${headerBlock}${body}${truncNote}`,
|
|
43
183
|
exitCode: response.status,
|
|
44
184
|
truncated,
|
|
45
|
-
stats: { bytesRead, bytesShown: bytesRead },
|
|
46
185
|
};
|
|
47
186
|
}
|
|
48
187
|
//# sourceMappingURL=http.js.map
|
package/dist/tools/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/tools/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/tools/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Reducer, ReducerOutput } from "../reducers/types.js";
|
|
2
|
+
interface PolicyContext {
|
|
3
|
+
toolName: string;
|
|
4
|
+
command?: string | undefined;
|
|
5
|
+
argv?: string[] | undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pick a reducer based on (a) the tool name and (b) what binary is being
|
|
9
|
+
* invoked. Falls back to the generic line-ranking reducer.
|
|
10
|
+
*/
|
|
11
|
+
export declare function pickReducer(context: PolicyContext): Reducer;
|
|
12
|
+
export declare function reduceToolOutput(raw: string, context: PolicyContext): ReducerOutput;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ffufReducer } from "../reducers/ffuf.js";
|
|
2
|
+
import { genericReducer } from "../reducers/generic.js";
|
|
3
|
+
import { gobusterReducer } from "../reducers/gobuster.js";
|
|
4
|
+
import { httpxReducer } from "../reducers/httpx.js";
|
|
5
|
+
import { nmapReducer } from "../reducers/nmap.js";
|
|
6
|
+
import { nucleiReducer } from "../reducers/nuclei.js";
|
|
7
|
+
import { sqlmapReducer } from "../reducers/sqlmap.js";
|
|
8
|
+
import { subdomainsReducer } from "../reducers/subdomains.js";
|
|
9
|
+
function commandHead(command) {
|
|
10
|
+
return command.trim().split(/\s+/)[0]?.replace(/^.*\//, "") ?? "";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Pick a reducer based on (a) the tool name and (b) what binary is being
|
|
14
|
+
* invoked. Falls back to the generic line-ranking reducer.
|
|
15
|
+
*/
|
|
16
|
+
export function pickReducer(context) {
|
|
17
|
+
if (context.toolName === "net.scan" || context.toolName === "pentest.recon") {
|
|
18
|
+
// Both produce nmap-style text; for recon we still want nmap-shaped parsing
|
|
19
|
+
// for the nmap sub-step.
|
|
20
|
+
return nmapReducer;
|
|
21
|
+
}
|
|
22
|
+
const head = context.command ? commandHead(context.command) : "";
|
|
23
|
+
switch (head) {
|
|
24
|
+
case "nmap":
|
|
25
|
+
return nmapReducer;
|
|
26
|
+
case "ffuf":
|
|
27
|
+
return ffufReducer;
|
|
28
|
+
case "gobuster":
|
|
29
|
+
case "feroxbuster":
|
|
30
|
+
case "dirb":
|
|
31
|
+
case "dirsearch":
|
|
32
|
+
return gobusterReducer;
|
|
33
|
+
case "subfinder":
|
|
34
|
+
case "amass":
|
|
35
|
+
case "sublist3r":
|
|
36
|
+
case "assetfinder":
|
|
37
|
+
return subdomainsReducer;
|
|
38
|
+
case "httpx":
|
|
39
|
+
case "httprobe":
|
|
40
|
+
return httpxReducer;
|
|
41
|
+
case "nuclei":
|
|
42
|
+
return nucleiReducer;
|
|
43
|
+
case "sqlmap":
|
|
44
|
+
return sqlmapReducer;
|
|
45
|
+
default:
|
|
46
|
+
return genericReducer;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function reduceToolOutput(raw, context) {
|
|
50
|
+
const reducer = pickReducer(context);
|
|
51
|
+
return reducer(raw, {
|
|
52
|
+
command: context.command ?? context.toolName,
|
|
53
|
+
argv: context.argv,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=output-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-policy.js","sourceRoot":"","sources":["../../../src/tools/policies/output-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAS9D,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAsB;IAChD,IAAI,OAAO,CAAC,QAAQ,KAAK,UAAU,IAAI,OAAO,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;QAC5E,4EAA4E;QAC5E,yBAAyB;QACzB,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM;YACT,OAAO,WAAW,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,WAAW,CAAC;QACrB,KAAK,UAAU,CAAC;QAChB,KAAK,aAAa,CAAC;QACnB,KAAK,MAAM,CAAC;QACZ,KAAK,WAAW;YACd,OAAO,eAAe,CAAC;QACzB,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,WAAW,CAAC;QACjB,KAAK,aAAa;YAChB,OAAO,iBAAiB,CAAC;QAC3B,KAAK,OAAO,CAAC;QACb,KAAK,UAAU;YACb,OAAO,YAAY,CAAC;QACtB,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC;QACvB,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC;QACvB;YACE,OAAO,cAAc,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,GAAW,EACX,OAAsB;IAEtB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ;QAC5C,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const LINE_RE = /^([^\s]+)\s+\[Status:\s*(\d+),\s*Size:\s*(\d+),\s*Words:\s*(\d+),\s*Lines:\s*(\d+).*\]/;
|
|
2
|
+
/**
|
|
3
|
+
* ffuf supports JSON output (`-of json`) but the default is plain text. We
|
|
4
|
+
* parse both, group by status, and surface the most interesting clusters.
|
|
5
|
+
*/
|
|
6
|
+
export const ffufReducer = (raw) => {
|
|
7
|
+
const results = [];
|
|
8
|
+
// Try JSON first (works when the agent already used `-of json`).
|
|
9
|
+
const jsonStart = raw.indexOf("{");
|
|
10
|
+
if (jsonStart >= 0) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(raw.slice(jsonStart));
|
|
13
|
+
if (parsed.results) {
|
|
14
|
+
for (const r of parsed.results)
|
|
15
|
+
results.push(r);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// fall through to text parsing
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (results.length === 0) {
|
|
23
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
24
|
+
const match = LINE_RE.exec(line);
|
|
25
|
+
if (!match)
|
|
26
|
+
continue;
|
|
27
|
+
results.push({
|
|
28
|
+
url: match[1],
|
|
29
|
+
status: Number(match[2]),
|
|
30
|
+
length: Number(match[3]),
|
|
31
|
+
words: Number(match[4]),
|
|
32
|
+
lines: Number(match[5]),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (results.length === 0) {
|
|
37
|
+
return { summary: "# ffuf — no results parsed" };
|
|
38
|
+
}
|
|
39
|
+
// Cluster by (status, length) so likely-templated wildcard responses fold up.
|
|
40
|
+
const clusters = new Map();
|
|
41
|
+
for (const r of results) {
|
|
42
|
+
const key = `${r.status ?? "?"}:${r.length ?? "?"}`;
|
|
43
|
+
const c = clusters.get(key) ??
|
|
44
|
+
{ status: r.status, length: r.length, samples: [] };
|
|
45
|
+
c.samples.push(r);
|
|
46
|
+
clusters.set(key, c);
|
|
47
|
+
}
|
|
48
|
+
const sorted = [...clusters.values()].sort((a, b) => b.samples.length - a.samples.length);
|
|
49
|
+
const lines = [
|
|
50
|
+
`# ffuf reduced summary — ${results.length} result(s), ${clusters.size} (status,length) cluster(s)`,
|
|
51
|
+
];
|
|
52
|
+
for (const c of sorted.slice(0, 25)) {
|
|
53
|
+
lines.push("");
|
|
54
|
+
lines.push(`## status=${c.status ?? "?"} length=${c.length ?? "?"} — ${c.samples.length} hit(s)`);
|
|
55
|
+
for (const sample of c.samples.slice(0, 5)) {
|
|
56
|
+
lines.push(`- ${sample.url ?? JSON.stringify(sample.input)}`);
|
|
57
|
+
}
|
|
58
|
+
if (c.samples.length > 5) {
|
|
59
|
+
lines.push(`- ... ${c.samples.length - 5} more`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
summary: lines.join("\n"),
|
|
64
|
+
findings: {
|
|
65
|
+
total: results.length,
|
|
66
|
+
clusters: sorted.map((c) => ({
|
|
67
|
+
status: c.status,
|
|
68
|
+
length: c.length,
|
|
69
|
+
count: c.samples.length,
|
|
70
|
+
})),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=ffuf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffuf.js","sourceRoot":"","sources":["../../../src/tools/reducers/ffuf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catch-all reducer that ranks lines by signal so important findings survive
|
|
3
|
+
* truncation. Used when no command-specific reducer matches.
|
|
4
|
+
*/
|
|
5
|
+
const SIGNAL_PATTERNS = [
|
|
6
|
+
{ tag: "credential", re: /\b(?:password|passwd|secret|token|api[_-]?key|bearer)\b/i, weight: 5 },
|
|
7
|
+
{ tag: "vulnerable", re: /\b(?:vulnerable|exploitable|exploit|cve-\d{4}-\d+)/i, weight: 5 },
|
|
8
|
+
{ tag: "error", re: /\b(?:error|failed|denied|forbidden|refused|fatal)\b/i, weight: 4 },
|
|
9
|
+
{ tag: "success", re: /\b(?:found|success|matched|positive|admin)\b/i, weight: 3 },
|
|
10
|
+
{ tag: "open-port", re: /\b\d+\/(?:tcp|udp)\s+open\b/i, weight: 3 },
|
|
11
|
+
{ tag: "http-2xx", re: /\s2\d{2}\b/, weight: 2 },
|
|
12
|
+
{ tag: "http-403", re: /\s403\b/, weight: 2 },
|
|
13
|
+
{ tag: "warning", re: /\bwarning\b/i, weight: 1 },
|
|
14
|
+
];
|
|
15
|
+
function scoreLine(line) {
|
|
16
|
+
let score = 0;
|
|
17
|
+
const tags = [];
|
|
18
|
+
for (const pattern of SIGNAL_PATTERNS) {
|
|
19
|
+
if (pattern.re.test(line)) {
|
|
20
|
+
score += pattern.weight;
|
|
21
|
+
tags.push(pattern.tag);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { score, tags };
|
|
25
|
+
}
|
|
26
|
+
export const genericReducer = (raw, _ctx) => {
|
|
27
|
+
const lines = raw.split(/\r?\n/);
|
|
28
|
+
const scored = lines.map((line, index) => ({
|
|
29
|
+
line,
|
|
30
|
+
index,
|
|
31
|
+
...scoreLine(line),
|
|
32
|
+
}));
|
|
33
|
+
const interesting = scored.filter((x) => x.score > 0);
|
|
34
|
+
const totalLines = lines.length;
|
|
35
|
+
if (interesting.length === 0) {
|
|
36
|
+
// Fall back to head + tail when nothing matched.
|
|
37
|
+
const head = lines.slice(0, 20).join("\n");
|
|
38
|
+
const tail = lines.slice(-10).join("\n");
|
|
39
|
+
return {
|
|
40
|
+
summary: head + (lines.length > 30 ? `\n... (${lines.length} lines total)\n` : "\n") + tail,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const topRanked = interesting
|
|
44
|
+
.sort((a, b) => b.score - a.score || a.index - b.index)
|
|
45
|
+
.slice(0, 40);
|
|
46
|
+
const lineSet = new Set(topRanked.map((x) => x.index));
|
|
47
|
+
// Always include the very last 5 lines so trailing summaries (e.g. "X hosts
|
|
48
|
+
// up", "scan complete") are not dropped.
|
|
49
|
+
for (let i = Math.max(0, lines.length - 5); i < lines.length; i++) {
|
|
50
|
+
lineSet.add(i);
|
|
51
|
+
}
|
|
52
|
+
const ordered = [...lineSet].sort((a, b) => a - b);
|
|
53
|
+
const summaryLines = ordered.map((i) => lines[i]).filter((l) => l !== undefined);
|
|
54
|
+
const dropped = totalLines - ordered.length;
|
|
55
|
+
const header = `# Reduced output (${ordered.length} of ${totalLines} lines, ${dropped} omitted)`;
|
|
56
|
+
return {
|
|
57
|
+
summary: [header, ...summaryLines].join("\n"),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.js","sourceRoot":"","sources":["../../../src/tools/reducers/generic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const LINE_RE = /^(?<path>\S+)\s+\(Status:\s*(?<status>\d+)\)\s+\[Size:\s*(?<size>\d+)\]/;
|
|
2
|
+
export const gobusterReducer = (raw) => {
|
|
3
|
+
const groups = new Map();
|
|
4
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
5
|
+
const match = LINE_RE.exec(line);
|
|
6
|
+
if (!match || !match.groups)
|
|
7
|
+
continue;
|
|
8
|
+
const status = Number(match.groups.status);
|
|
9
|
+
const path = match.groups.path;
|
|
10
|
+
const size = Number(match.groups.size);
|
|
11
|
+
const list = groups.get(status) ?? [];
|
|
12
|
+
list.push({ path, size });
|
|
13
|
+
groups.set(status, list);
|
|
14
|
+
}
|
|
15
|
+
if (groups.size === 0) {
|
|
16
|
+
return { summary: "# gobuster — no paths discovered" };
|
|
17
|
+
}
|
|
18
|
+
const sortedStatuses = [...groups.keys()].sort((a, b) => a - b);
|
|
19
|
+
const lines = [`# gobuster reduced summary — ${sortedStatuses.length} status code(s)`];
|
|
20
|
+
for (const status of sortedStatuses) {
|
|
21
|
+
const entries = groups.get(status);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push(`## Status ${status} — ${entries.length} path(s)`);
|
|
24
|
+
for (const entry of entries.slice(0, 25)) {
|
|
25
|
+
lines.push(`- ${entry.path} (size=${entry.size})`);
|
|
26
|
+
}
|
|
27
|
+
if (entries.length > 25) {
|
|
28
|
+
lines.push(`- ... ${entries.length - 25} more`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
summary: lines.join("\n"),
|
|
33
|
+
findings: { byStatus: Object.fromEntries(groups) },
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=gobuster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gobuster.js","sourceRoot":"","sources":["../../../src/tools/reducers/gobuster.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG,yEAAyE,CAAC;AAE1F,MAAM,CAAC,MAAM,eAAe,GAAY,CAAC,GAAG,EAAiB,EAAE;IAC7D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAiD,CAAC;IACxE,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,SAAS;QACtC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,IAAK,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,cAAc,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,MAAM,KAAK,GAAa,CAAC,gCAAgC,cAAc,CAAC,MAAM,iBAAiB,CAAC,CAAC;IACjG,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,MAAM,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;QAC9D,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;YACzC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,MAAM,GAAG,EAAE,OAAO,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACzB,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE;KACnD,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const httpxReducer = (raw) => {
|
|
2
|
+
const rows = [];
|
|
3
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
4
|
+
const trimmed = line.trim();
|
|
5
|
+
if (!trimmed.startsWith("{"))
|
|
6
|
+
continue;
|
|
7
|
+
try {
|
|
8
|
+
rows.push(JSON.parse(trimmed));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// skip malformed lines
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (rows.length === 0) {
|
|
15
|
+
return { summary: "# httpx — no JSONL rows parsed (pass -json to get structured output)" };
|
|
16
|
+
}
|
|
17
|
+
const lines = [
|
|
18
|
+
`# httpx reduced summary — ${rows.length} URL(s)`,
|
|
19
|
+
];
|
|
20
|
+
for (const row of rows.slice(0, 50)) {
|
|
21
|
+
const url = row.final_url ?? row.url ?? row.input ?? row.host ?? "?";
|
|
22
|
+
const status = row.status_code ?? row.status ?? "?";
|
|
23
|
+
const tech = (row.tech ?? row.technologies ?? []).join(", ");
|
|
24
|
+
const title = row.title ? ` title=${JSON.stringify(row.title)}` : "";
|
|
25
|
+
const cdn = row.cdn_name ?? row.cdn;
|
|
26
|
+
const cdnNote = cdn ? ` cdn=${cdn}` : "";
|
|
27
|
+
const len = row.content_length !== undefined ? ` len=${row.content_length}` : "";
|
|
28
|
+
lines.push(`- ${url} [${status}]${title}${len}${tech ? ` tech=[${tech}]` : ""}${cdnNote}`);
|
|
29
|
+
}
|
|
30
|
+
if (rows.length > 50) {
|
|
31
|
+
lines.push(`- ... ${rows.length - 50} more in artifact`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
summary: lines.join("\n"),
|
|
35
|
+
findings: { count: rows.length },
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=httpx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpx.js","sourceRoot":"","sources":["../../../src/tools/reducers/httpx.ts"],"names":[],"mappings":"AAkBA,MAAM,CAAC,MAAM,YAAY,GAAY,CAAC,GAAG,EAAiB,EAAE;IAC1D,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACvC,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAa,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,sEAAsE,EAAE,CAAC;IAC7F,CAAC;IACD,MAAM,KAAK,GAAa;QACtB,6BAA6B,IAAI,CAAC,MAAM,SAAS;KAClD,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC;QACrE,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QACpD,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC;QACpC,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,MAAM,IAAI,KAAK,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACzB,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE;KACjC,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Reducer } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse plain "nmap -sV" text output into structured findings. We avoid
|
|
4
|
+
* forcing -oX here so the agent can still pass any flags it wants — but if
|
|
5
|
+
* the output looks like XML we just include it verbatim.
|
|
6
|
+
*/
|
|
7
|
+
export declare const nmapReducer: Reducer;
|