blitzstrike 1.0.62 → 1.0.65
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/CHANGELOG.md +50 -0
- package/bench/enterprise/php-complex/plugin.php +34 -0
- package/dist/index.js +217 -14
- package/docs/ARCHITECTURE-AUDIT.md +88 -0
- package/package.json +1 -1
- package/skills/bs-adversarial-review.md +42 -0
- package/skills/bs-orchestrate-engagement.md +42 -0
- package/skills/bs-scope-gate.md +32 -0
- package/skills/bs-source-audit.md +50 -0
- package/skills/bs-verify-finding.md +38 -0
- package/skills/bs-write-report.md +33 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,56 @@ All notable changes to Blitz Strike are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [1.0.65] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Fixed — breadth-scanner false positives (live MCP verification)
|
|
11
|
+
|
|
12
|
+
Found via an LLM-driven verification run (agent → `blitz_scan`/`eagle_eye2`):
|
|
13
|
+
|
|
14
|
+
- **Sink tokens in comments/strings no longer reported.** `scanFile`/
|
|
15
|
+
`traceFunction`/`grepInFunctions` now match against a comment- and
|
|
16
|
+
string-literal-stripped view of the source (positions + line numbers
|
|
17
|
+
preserved). A `// use extract($_REQUEST)` comment or a
|
|
18
|
+
`"https://…/system("` string is not a sink.
|
|
19
|
+
- Rebuilt `dist/` so the fix ships in the published bundle (a long-running MCP
|
|
20
|
+
server must be restarted to pick up new `dist/` code — skills load fresh from
|
|
21
|
+
`skills/`, but compiled engine code is loaded once at startup).
|
|
22
|
+
|
|
23
|
+
## [1.0.64] — 2026-09-13
|
|
24
|
+
|
|
25
|
+
### Added — agent-native skills (LLM-driven orchestration doctrine)
|
|
26
|
+
|
|
27
|
+
Six skills that teach the LLM agent how to drive Blitz Strike's deterministic
|
|
28
|
+
tools end-to-end (the "LLM is the brain, Blitz Strike is the hands" model):
|
|
29
|
+
|
|
30
|
+
- `bs-orchestrate-engagement` — the full cycle: scope → recon → analyze →
|
|
31
|
+
verify → review → report, no-babysitting.
|
|
32
|
+
- `bs-scope-gate` — hard scope/authorization gate before any live action.
|
|
33
|
+
- `bs-source-audit` — drive taint + route-confusion + complex-bugs analyzers.
|
|
34
|
+
- `bs-verify-finding` — marker + negative-control live verification.
|
|
35
|
+
- `bs-adversarial-review` — the ten §15 questions + confidence + dedup.
|
|
36
|
+
- `bs-write-report` — submission-ready report (root cause, CVSS, remediation).
|
|
37
|
+
|
|
38
|
+
Plus `docs/ARCHITECTURE-AUDIT.md` — the Mission Control → LLM-driven toolbelt
|
|
39
|
+
classification (TOOL / SKILL / REDUNDANT).
|
|
40
|
+
|
|
41
|
+
## [1.0.63] — 2026-09-13
|
|
42
|
+
|
|
43
|
+
### Added — complex-bug detection (`src/complex-bugs.ts`)
|
|
44
|
+
|
|
45
|
+
Three "hard" vulnerability classes beyond source→sink taint:
|
|
46
|
+
|
|
47
|
+
- **Deserialization → POP gadget chain** — `unserialize()` of attacker-controlled
|
|
48
|
+
data when a magic method (`__destruct`/`__wakeup`/`__toString`/`__invoke`/`__call`)
|
|
49
|
+
is present in the file (object injection → RCE pivot).
|
|
50
|
+
- **Type juggling** — loose comparison (`==`/`!=`) of user input against a
|
|
51
|
+
hash/secret (PHP `0e…` collision auth bypass); strict `===` is ignored.
|
|
52
|
+
- **Mass assignment** — `extract()` of request data without `EXTR_SKIP`, or
|
|
53
|
+
`parse_str()` of user input (variable injection).
|
|
54
|
+
|
|
55
|
+
Wired into the `@source` agent as `vulnerability` graph nodes. Added a
|
|
56
|
+
`php-complex` fixture to the enterprise benchmark.
|
|
57
|
+
|
|
8
58
|
## [1.0.62] — 2026-09-13
|
|
9
59
|
|
|
10
60
|
### Added — route-confusion & dispatch-abuse detection
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Plugin helper (simplified) with multiple complex-bug patterns.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 1. Deserialization → object injection (magic method present)
|
|
7
|
+
function restore_session($payload) {
|
|
8
|
+
$data = $_POST['data'];
|
|
9
|
+
$obj = unserialize($data);
|
|
10
|
+
return $obj;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class CacheManager {
|
|
14
|
+
public function __destruct() {
|
|
15
|
+
// Magic method — a POP gadget can pivot object injection to RCE here.
|
|
16
|
+
system("rm -rf /tmp/cache");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 2. Type juggling — loose comparison of user input against a hash
|
|
21
|
+
function verify_signature() {
|
|
22
|
+
$sig = $_GET['sig'];
|
|
23
|
+
$secret = get_option('signing_secret');
|
|
24
|
+
if ($sig == $secret) { // <-- loose == , 0e... collision bypass
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3. Mass assignment — extract() of request data without EXTR_SKIP
|
|
31
|
+
function bootstrap_options() {
|
|
32
|
+
extract($_REQUEST); // <-- variable injection
|
|
33
|
+
return $admin; // $admin is attacker-controlled
|
|
34
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6944,6 +6944,65 @@ function lineno(text, pos) {
|
|
|
6944
6944
|
}
|
|
6945
6945
|
return n;
|
|
6946
6946
|
}
|
|
6947
|
+
function stripComments(text) {
|
|
6948
|
+
const out = text.split("");
|
|
6949
|
+
const n = text.length;
|
|
6950
|
+
let i = 0;
|
|
6951
|
+
while (i < n) {
|
|
6952
|
+
const c = text[i];
|
|
6953
|
+
const nc = text[i + 1];
|
|
6954
|
+
if (c === "'" || c === '"') {
|
|
6955
|
+
const q = c;
|
|
6956
|
+
out[i] = " ";
|
|
6957
|
+
i++;
|
|
6958
|
+
while (i < n && text[i] !== q) {
|
|
6959
|
+
if (text[i] === "\\") {
|
|
6960
|
+
out[i] = " ";
|
|
6961
|
+
i++;
|
|
6962
|
+
if (i < n)
|
|
6963
|
+
out[i] = " ";
|
|
6964
|
+
i++;
|
|
6965
|
+
continue;
|
|
6966
|
+
}
|
|
6967
|
+
if (text[i] !== `
|
|
6968
|
+
`)
|
|
6969
|
+
out[i] = " ";
|
|
6970
|
+
i++;
|
|
6971
|
+
}
|
|
6972
|
+
if (i < n)
|
|
6973
|
+
out[i] = " ";
|
|
6974
|
+
i++;
|
|
6975
|
+
continue;
|
|
6976
|
+
}
|
|
6977
|
+
if (c === "/" && nc === "/" || c === "#") {
|
|
6978
|
+
while (i < n && text[i] !== `
|
|
6979
|
+
`) {
|
|
6980
|
+
out[i] = " ";
|
|
6981
|
+
i++;
|
|
6982
|
+
}
|
|
6983
|
+
continue;
|
|
6984
|
+
}
|
|
6985
|
+
if (c === "/" && nc === "*") {
|
|
6986
|
+
out[i] = " ";
|
|
6987
|
+
out[i + 1] = " ";
|
|
6988
|
+
i += 2;
|
|
6989
|
+
while (i < n && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
6990
|
+
if (text[i] !== `
|
|
6991
|
+
`)
|
|
6992
|
+
out[i] = " ";
|
|
6993
|
+
i++;
|
|
6994
|
+
}
|
|
6995
|
+
if (i < n) {
|
|
6996
|
+
out[i] = " ";
|
|
6997
|
+
out[i + 1] = " ";
|
|
6998
|
+
i += 2;
|
|
6999
|
+
}
|
|
7000
|
+
continue;
|
|
7001
|
+
}
|
|
7002
|
+
i++;
|
|
7003
|
+
}
|
|
7004
|
+
return out.join("");
|
|
7005
|
+
}
|
|
6947
7006
|
function iterSourceFiles(root, maxFiles = 5000) {
|
|
6948
7007
|
const out = [];
|
|
6949
7008
|
const walk = (dir) => {
|
|
@@ -7005,23 +7064,24 @@ function scanFile(path) {
|
|
|
7005
7064
|
} catch {
|
|
7006
7065
|
return { file: path, endpoints: [], sinks: [], auth_gates_present: [], error: "unreadable" };
|
|
7007
7066
|
}
|
|
7067
|
+
const code = stripComments(text);
|
|
7008
7068
|
const endpoints = [];
|
|
7009
7069
|
for (const hook of NOPRIV_HOOKS) {
|
|
7010
7070
|
const re = new RegExp(escapeRegExp(hook), "g");
|
|
7011
7071
|
let m;
|
|
7012
|
-
while ((m = re.exec(
|
|
7013
|
-
endpoints.push({ hook, line: lineno(
|
|
7072
|
+
while ((m = re.exec(code)) !== null) {
|
|
7073
|
+
endpoints.push({ hook, line: lineno(code, m.index) });
|
|
7014
7074
|
}
|
|
7015
7075
|
}
|
|
7016
7076
|
const sinks = [];
|
|
7017
7077
|
for (const [sink, bugclass] of Object.entries(SINKS)) {
|
|
7018
7078
|
const re = new RegExp(escapeRegExp(sink), "g");
|
|
7019
7079
|
let m;
|
|
7020
|
-
while ((m = re.exec(
|
|
7021
|
-
sinks.push({ sink, class: bugclass, line: lineno(
|
|
7080
|
+
while ((m = re.exec(code)) !== null) {
|
|
7081
|
+
sinks.push({ sink, class: bugclass, line: lineno(code, m.index) });
|
|
7022
7082
|
}
|
|
7023
7083
|
}
|
|
7024
|
-
const gates = AUTH_GATES.filter((g) =>
|
|
7084
|
+
const gates = AUTH_GATES.filter((g) => code.includes(g));
|
|
7025
7085
|
const lines = text.split(`
|
|
7026
7086
|
`).length;
|
|
7027
7087
|
return { file: path, lines, endpoints, sinks, auth_gates_present: gates };
|
|
@@ -7042,22 +7102,23 @@ function traceFunction(path, symbol) {
|
|
|
7042
7102
|
continue;
|
|
7043
7103
|
const [start, end] = bounds;
|
|
7044
7104
|
const body = text.slice(start, end);
|
|
7105
|
+
const bodyCode = stripComments(body);
|
|
7045
7106
|
const bodyStartLine = lineno(text, start);
|
|
7046
7107
|
const bodyEndLine = lineno(text, end);
|
|
7047
7108
|
const innerSinks = [];
|
|
7048
7109
|
for (const [sink, bugclass] of Object.entries(SINKS)) {
|
|
7049
7110
|
const re = new RegExp(escapeRegExp(sink), "g");
|
|
7050
7111
|
let sm;
|
|
7051
|
-
while ((sm = re.exec(
|
|
7112
|
+
while ((sm = re.exec(bodyCode)) !== null) {
|
|
7052
7113
|
innerSinks.push({
|
|
7053
7114
|
sink,
|
|
7054
7115
|
class: bugclass,
|
|
7055
|
-
line: bodyStartLine +
|
|
7116
|
+
line: bodyStartLine + bodyCode.slice(0, sm.index).split(`
|
|
7056
7117
|
`).length - 1
|
|
7057
7118
|
});
|
|
7058
7119
|
}
|
|
7059
7120
|
}
|
|
7060
|
-
const innerGates = AUTH_GATES.filter((g) =>
|
|
7121
|
+
const innerGates = AUTH_GATES.filter((g) => bodyCode.includes(g));
|
|
7061
7122
|
results.push({
|
|
7062
7123
|
line: bodyStartLine,
|
|
7063
7124
|
line_end: bodyEndLine,
|
|
@@ -7085,11 +7146,12 @@ function grepInFunctions(root, sink, maxHits = 50) {
|
|
|
7085
7146
|
} catch {
|
|
7086
7147
|
continue;
|
|
7087
7148
|
}
|
|
7149
|
+
const code = stripComments(text);
|
|
7088
7150
|
const re = new RegExp(escapeRegExp(sink), "g");
|
|
7089
7151
|
let m;
|
|
7090
|
-
while ((m = re.exec(
|
|
7091
|
-
const line = lineno(
|
|
7092
|
-
const head =
|
|
7152
|
+
while ((m = re.exec(code)) !== null) {
|
|
7153
|
+
const line = lineno(code, m.index);
|
|
7154
|
+
const head = code.slice(0, m.index);
|
|
7093
7155
|
const funcRe = /function\s+&?\w+\s*\(/g;
|
|
7094
7156
|
const funcs = [];
|
|
7095
7157
|
let fm;
|
|
@@ -7098,10 +7160,10 @@ function grepInFunctions(root, sink, maxHits = 50) {
|
|
|
7098
7160
|
if (funcs.length === 0)
|
|
7099
7161
|
continue;
|
|
7100
7162
|
const fstart = funcs[funcs.length - 1].index;
|
|
7101
|
-
const bounds = findFunctionBounds(
|
|
7163
|
+
const bounds = findFunctionBounds(code, fstart);
|
|
7102
7164
|
if (!bounds || !(bounds[0] <= m.index && m.index < bounds[1]))
|
|
7103
7165
|
continue;
|
|
7104
|
-
const body =
|
|
7166
|
+
const body = code.slice(bounds[0], bounds[1]);
|
|
7105
7167
|
const gates = AUTH_GATES.filter((g) => body.includes(g));
|
|
7106
7168
|
hits.push({
|
|
7107
7169
|
file: p,
|
|
@@ -37186,7 +37248,7 @@ function detectRouteConfusion(code, file) {
|
|
|
37186
37248
|
}
|
|
37187
37249
|
var SOURCE_TOKENS, DYNAMIC_DISPATCH_RE, DYNAMIC_METHOD_RE, BATCH_LOOP_RE, ROUTE_MATCH_RE, NORMALIZERS;
|
|
37188
37250
|
var init_route_confusion = __esm(() => {
|
|
37189
|
-
SOURCE_TOKENS = /\$_(GET|POST|REQUEST|COOKIE|FILES)\b|\$request->|\$req->|request\.(get|input|query|post)|getParameter\(|req\.(query|body|params)/;
|
|
37251
|
+
SOURCE_TOKENS = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post)|getParameter\(|req\.(query|body|params)/;
|
|
37190
37252
|
DYNAMIC_DISPATCH_RE = /\b(call_user_func|call_user_func_array|forward|dispatch|resolve_route|invoke)\s*\(\s*(\$|\[)/gi;
|
|
37191
37253
|
DYNAMIC_METHOD_RE = /(->|::)\s*\$[A-Za-z_]/g;
|
|
37192
37254
|
BATCH_LOOP_RE = /\bforeach\s*\([^)]*(requests|batch|sub_requests|operations)\b/gi;
|
|
@@ -37194,6 +37256,124 @@ var init_route_confusion = __esm(() => {
|
|
|
37194
37256
|
NORMALIZERS = /\b(strtolower|strtoupper|rtrim|urldecode|rawurldecode|parse_url|normalize|canonical)\b/i;
|
|
37195
37257
|
});
|
|
37196
37258
|
|
|
37259
|
+
// src/complex-bugs.ts
|
|
37260
|
+
function lines2(code) {
|
|
37261
|
+
return code.split(`
|
|
37262
|
+
`);
|
|
37263
|
+
}
|
|
37264
|
+
function lineNo2(code, pos) {
|
|
37265
|
+
let n = 1;
|
|
37266
|
+
for (let i = 0;i < pos; i++)
|
|
37267
|
+
if (code.charCodeAt(i) === 10)
|
|
37268
|
+
n++;
|
|
37269
|
+
return n;
|
|
37270
|
+
}
|
|
37271
|
+
function detectDeserialization(code, file, out) {
|
|
37272
|
+
if (!MAGIC_METHODS.test(code))
|
|
37273
|
+
return;
|
|
37274
|
+
const ls = lines2(code);
|
|
37275
|
+
const re = /\b(unserialize|maybe_unserialize)\s*\(/g;
|
|
37276
|
+
let m;
|
|
37277
|
+
while ((m = re.exec(code)) !== null) {
|
|
37278
|
+
const ln = lineNo2(code, m.index);
|
|
37279
|
+
const l = ls[ln - 1] ?? "";
|
|
37280
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
37281
|
+
`);
|
|
37282
|
+
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
37283
|
+
continue;
|
|
37284
|
+
out.push({
|
|
37285
|
+
file,
|
|
37286
|
+
line: ln,
|
|
37287
|
+
type: "deserialization",
|
|
37288
|
+
category: "Deserialization → POP gadget chain",
|
|
37289
|
+
severity: "high",
|
|
37290
|
+
evidence: l.trim(),
|
|
37291
|
+
detail: `\`${m[1]}()\` of attacker-controlled data AND a magic method is present — object injection can pivot to RCE via the gadget chain.`
|
|
37292
|
+
});
|
|
37293
|
+
}
|
|
37294
|
+
}
|
|
37295
|
+
function detectTypeJuggling(code, file, out) {
|
|
37296
|
+
const ls = lines2(code);
|
|
37297
|
+
const re = /(?<![=!<>])(==|!=)(?!=)/g;
|
|
37298
|
+
let m;
|
|
37299
|
+
while ((m = re.exec(code)) !== null) {
|
|
37300
|
+
const ln = lineNo2(code, m.index);
|
|
37301
|
+
const l = ls[ln - 1] ?? "";
|
|
37302
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
37303
|
+
`);
|
|
37304
|
+
const hasSource = SOURCE_TOKENS2.test(l) || SOURCE_TOKENS2.test(scope);
|
|
37305
|
+
const hasHash = /\b(md5|sha1|sha256|hash|crc32|crypt|password_verify|strcmp|hash_equals|secret|token|nonce)\s*\(?/i.test(l) || /\b(secret|token|nonce|hash)\b/i.test(l);
|
|
37306
|
+
if (!hasSource || !hasHash)
|
|
37307
|
+
continue;
|
|
37308
|
+
out.push({
|
|
37309
|
+
file,
|
|
37310
|
+
line: ln,
|
|
37311
|
+
type: "type_juggling",
|
|
37312
|
+
category: "Type juggling (loose comparison)",
|
|
37313
|
+
severity: "medium",
|
|
37314
|
+
evidence: l.trim(),
|
|
37315
|
+
detail: 'Loose comparison of user input against a hash/secret — PHP type juggling (`0e…`, `"str" == 0`) may bypass the check. Use strict `===`.'
|
|
37316
|
+
});
|
|
37317
|
+
}
|
|
37318
|
+
}
|
|
37319
|
+
function detectMassAssignment(code, file, out) {
|
|
37320
|
+
const ls = lines2(code);
|
|
37321
|
+
const extractRe = /\bextract\s*\(\s*[^)]*(REQUEST|POST|GET|COOKIE|FILES|\$_(REQUEST|POST|GET|COOKIE|FILES))/g;
|
|
37322
|
+
let m;
|
|
37323
|
+
while ((m = extractRe.exec(code)) !== null) {
|
|
37324
|
+
const ln = lineNo2(code, m.index);
|
|
37325
|
+
const l = ls[ln - 1] ?? "";
|
|
37326
|
+
if (/EXTR_SKIP|EXTR_PREFIX_ALL/.test(l))
|
|
37327
|
+
continue;
|
|
37328
|
+
out.push({
|
|
37329
|
+
file,
|
|
37330
|
+
line: ln,
|
|
37331
|
+
type: "mass_assignment",
|
|
37332
|
+
category: "Mass assignment (variable injection)",
|
|
37333
|
+
severity: "high",
|
|
37334
|
+
evidence: l.trim(),
|
|
37335
|
+
detail: "`extract()` of request data without EXTR_SKIP — attacker can overwrite arbitrary variables."
|
|
37336
|
+
});
|
|
37337
|
+
}
|
|
37338
|
+
const parseRe = /\bparse_str\s*\(/g;
|
|
37339
|
+
while ((m = parseRe.exec(code)) !== null) {
|
|
37340
|
+
const ln = lineNo2(code, m.index);
|
|
37341
|
+
const l = ls[ln - 1] ?? "";
|
|
37342
|
+
const scope = ls.slice(Math.max(0, ln - 3), ln).join(`
|
|
37343
|
+
`);
|
|
37344
|
+
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
37345
|
+
continue;
|
|
37346
|
+
out.push({
|
|
37347
|
+
file,
|
|
37348
|
+
line: ln,
|
|
37349
|
+
type: "mass_assignment",
|
|
37350
|
+
category: "Mass assignment (variable injection)",
|
|
37351
|
+
severity: "medium",
|
|
37352
|
+
evidence: l.trim(),
|
|
37353
|
+
detail: "`parse_str()` of user input into the symbol table — variable injection."
|
|
37354
|
+
});
|
|
37355
|
+
}
|
|
37356
|
+
}
|
|
37357
|
+
function detectComplexBugs(code, file) {
|
|
37358
|
+
const out = [];
|
|
37359
|
+
detectDeserialization(code, file, out);
|
|
37360
|
+
detectTypeJuggling(code, file, out);
|
|
37361
|
+
detectMassAssignment(code, file, out);
|
|
37362
|
+
const seen = new Set;
|
|
37363
|
+
return out.filter((f) => {
|
|
37364
|
+
const key = `${f.type}:${f.line}`;
|
|
37365
|
+
if (seen.has(key))
|
|
37366
|
+
return false;
|
|
37367
|
+
seen.add(key);
|
|
37368
|
+
return true;
|
|
37369
|
+
});
|
|
37370
|
+
}
|
|
37371
|
+
var SOURCE_TOKENS2, MAGIC_METHODS;
|
|
37372
|
+
var init_complex_bugs = __esm(() => {
|
|
37373
|
+
SOURCE_TOKENS2 = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post|data)|getParameter\(|req\.(query|body|params)/;
|
|
37374
|
+
MAGIC_METHODS = /\b(__destruct|__wakeup|__toString|__invoke|__call|__get|__set|__isset|__unset|__sleep)\s*\(/;
|
|
37375
|
+
});
|
|
37376
|
+
|
|
37197
37377
|
// src/agents.ts
|
|
37198
37378
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
37199
37379
|
function emptyGraph() {
|
|
@@ -37218,6 +37398,7 @@ var init_agents = __esm(() => {
|
|
|
37218
37398
|
init_eagle2();
|
|
37219
37399
|
init_universal_taint();
|
|
37220
37400
|
init_route_confusion();
|
|
37401
|
+
init_complex_bugs();
|
|
37221
37402
|
init_adapters();
|
|
37222
37403
|
init_active();
|
|
37223
37404
|
init_strike();
|
|
@@ -37324,6 +37505,21 @@ var init_agents = __esm(() => {
|
|
|
37324
37505
|
graph = upsertNode(graph, nodeId, "route", { file: f, line: rc.line, type: rc.type, hypothesis: hyp.id });
|
|
37325
37506
|
graph = addEdge2(graph, tid, nodeId, "routes_to");
|
|
37326
37507
|
}
|
|
37508
|
+
for (const cb of detectComplexBugs(code, f).slice(0, 50)) {
|
|
37509
|
+
const hyp = makeHypothesis({
|
|
37510
|
+
type: cb.category,
|
|
37511
|
+
target: `${f}:${cb.line}`,
|
|
37512
|
+
evidence: cb.detail,
|
|
37513
|
+
confidence: cb.severity === "high" ? 0.7 : 0.55,
|
|
37514
|
+
required_validation: "confirm the payload reaches the sink and is exploitable",
|
|
37515
|
+
potential_impact: cb.type
|
|
37516
|
+
});
|
|
37517
|
+
hypotheses.push(hyp);
|
|
37518
|
+
findings += 1;
|
|
37519
|
+
const nodeId = `complex:${f}:${cb.line}:${cb.type}`;
|
|
37520
|
+
graph = upsertNode(graph, nodeId, "vulnerability", { file: f, line: cb.line, type: cb.type, hypothesis: hyp.id });
|
|
37521
|
+
graph = addEdge2(graph, tid, nodeId, "vulnerable_to");
|
|
37522
|
+
}
|
|
37327
37523
|
}
|
|
37328
37524
|
return { ok: true, output: { files: files.length, findings }, hypotheses, graph };
|
|
37329
37525
|
}
|
|
@@ -38267,6 +38463,13 @@ var init_enterprise_benchmark = __esm(() => {
|
|
|
38267
38463
|
expected_vulns: 1,
|
|
38268
38464
|
expected_sinks: ["Route confusion"],
|
|
38269
38465
|
safe_tokens: []
|
|
38466
|
+
},
|
|
38467
|
+
{
|
|
38468
|
+
id: "php-complex",
|
|
38469
|
+
dir: join17(ROOT10, "php-complex"),
|
|
38470
|
+
expected_vulns: 4,
|
|
38471
|
+
expected_sinks: ["Deserialization", "Type juggling", "Mass assignment"],
|
|
38472
|
+
safe_tokens: []
|
|
38270
38473
|
}
|
|
38271
38474
|
];
|
|
38272
38475
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Architecture Audit — Mission Control → LLM-Driven Toolbelt
|
|
2
|
+
|
|
3
|
+
Step 1 of the OMOP-alignment refactor. Every Mission Control component is
|
|
4
|
+
classified into one of:
|
|
5
|
+
|
|
6
|
+
- **TOOL** — a deterministic check the LLM calls via MCP (produces a result the
|
|
7
|
+
LLM reasons over).
|
|
8
|
+
- **SKILL** — "how to run the pentest" doctrine/knowledge the LLM loads
|
|
9
|
+
(becomes `SKILL.md`).
|
|
10
|
+
- **DATA** — static knowledge (patterns/chains/correlations) kept as-is.
|
|
11
|
+
- **REDUNDANT** — deterministic orchestration that duplicates what the LLM
|
|
12
|
+
already does; remove or fold into a TOOL/SKILL.
|
|
13
|
+
|
|
14
|
+
The guiding principle: **the LLM is the brain; Blitz Strike is the deterministic
|
|
15
|
+
hands + knowledge + guardrails.** A deterministic "self-driving loop" that tries
|
|
16
|
+
to BE the brain is the anti-pattern being removed.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## src/mission/ — classification
|
|
21
|
+
|
|
22
|
+
| File | Component | Verdict | Why |
|
|
23
|
+
|------|-----------|---------|-----|
|
|
24
|
+
| `types.ts` | shared types (Mission/Task/Hypothesis/Budget/Graph) | **DATA** | Type contracts. Reshape `Mission`→`Engagement` later, but keep as the shared layer. |
|
|
25
|
+
| `state.ts` | `createMission`/`save`/`load`/`list`/`delete` | **TOOL** | Persistence is useful — repurpose as *engagement/finding state* the LLM drives, not an "autonomous mission" blob. |
|
|
26
|
+
| `taskgraph.ts` | `schedule`/`nextBatch`/`allResolved` | **REDUNDANT** | Task scheduling is the LLM's plan-agent job. |
|
|
27
|
+
| `graph.ts` | Security Graph (nodes/edges/stats/merge) | **TOOL** | The finding-relationship graph is genuinely useful for the LLM to track + query. |
|
|
28
|
+
| `hypothesis.ts` | Hypothesis Engine (make/validate/reject/merge) | **TOOL** | Core moat — evidence-first `observation→hypothesis→validated→rejected` lifecycle. LLM calls these per finding. |
|
|
29
|
+
| `control.ts` | `runMission` loop + `buildInitialTasks` + `replan` | **REDUNDANT** | The self-driving scheduler loop — the LLM drives this instead. |
|
|
30
|
+
| `control.ts` | `scopeAllowed` | **TOOL** | Scope guardrail (keep + expose as a check the LLM MUST call before live actions). |
|
|
31
|
+
| `planner.ts` | `ATTACK_BRANCHES` + escalation graph | **SKILL** | Knowledge: which attack branches/escapes exist. Becomes `SKILL.md` + data. |
|
|
32
|
+
| `planner.ts` | `planNextActions` (ranking) | **TOOL** | Deterministic confidence×value−risk ranking the LLM can use as a hint. |
|
|
33
|
+
| `coverage.ts` | `computeCoverage`/`coverageGaps` | **TOOL** | LLM calls to find un-covered dimensions. `COVERAGE_DIMENSIONS` → **SKILL**. |
|
|
34
|
+
| `recovery.ts` | `diagnoseError`/`recoveryDecision` | **TOOL** | LLM calls on failure to pick retry/alternative. `AGENT_ALTERNATIVES` → **SKILL**. |
|
|
35
|
+
| `memory.ts` | `persistMissionMemory`/`recallForTarget` | **TOOL** | Cross-session memory (keep — the LLM's long-term finding store). |
|
|
36
|
+
| `reviewer.ts` | `reviewFinding`/`reviewValidated` (10 questions) | **TOOL** | Adversarial reviewer — key moat. LLM calls before CONFIRMING a finding. |
|
|
37
|
+
| `router.ts` | `routeTask`/`AGENT_TIER` | **REDUNDANT** | Model routing is the LLM platform's own job (or a small TOOL hint). |
|
|
38
|
+
| `budget.ts` | `budgetStatus`/`budgetOverrun`/`consumeTasks` | **TOOL** | LLM calls to check budget. The "terminate on overrun" auto-decision is **REDUNDANT**. |
|
|
39
|
+
| `termination.ts` | `evaluateTermination`/`applyTermination` | **REDUNDANT** | The LLM decides when to stop; the 7 reasons → **SKILL** doctrine. |
|
|
40
|
+
| `benchmark.ts` + `enterprise-benchmark.ts` | benchmark runners | **DATA/QA** | Keep — not runtime brain; QA harness. |
|
|
41
|
+
| `cli.ts` | `mission` CLI subcommands | **REDUNDANT** | Deterministic-brain CLI. Optional standalone fallback; LLM drives via MCP. |
|
|
42
|
+
| `mcp.ts` | `registerMissionTools` (mission_start/status/…) | **REDUNDANT** | Replace with skill/tool MCP tools (the LLM drives granular tools). |
|
|
43
|
+
|
|
44
|
+
## src/agents.ts — 13 agents
|
|
45
|
+
|
|
46
|
+
Each agent is a deterministic executor = orchestration (REDUNDANT) + detection
|
|
47
|
+
calls (TOOL). The agent's *responsibility + tool list* becomes a **SKILL**.
|
|
48
|
+
|
|
49
|
+
| Agent | Repurpose as SKILL | Kept as TOOL |
|
|
50
|
+
|-------|--------------------|--------------|
|
|
51
|
+
| `commander` | orchestration doctrine (plan → delegate → verify → report) | — (LLM is the commander) |
|
|
52
|
+
| `recon` | passive recon doctrine | `activeScan`/`live_recon` (already MCP tools) |
|
|
53
|
+
| `web` | live web-attack doctrine | `strike_verify`/`browser_validate` |
|
|
54
|
+
| `api` | API recon/authz doctrine | `api` detection tools |
|
|
55
|
+
| `source` | source-audit doctrine | `analyzeDataFlow2` + `detectRouteConfusion` + `detectComplexBugs` |
|
|
56
|
+
| `auth` | auth-gap doctrine | auth detection |
|
|
57
|
+
| `analysis` | multi-language taint doctrine | `analyzeTaintUniversal` |
|
|
58
|
+
| `validator` | verification doctrine (marker + negative) | `strikeVerify` |
|
|
59
|
+
| `reviewer` | adversarial-review doctrine | `reviewFinding` (TOOL above) |
|
|
60
|
+
| `reporter` | report-writing doctrine | report generator |
|
|
61
|
+
| `browser` | browser-session doctrine | `browser-agent.ts` (session ops) |
|
|
62
|
+
| `mobile` | mobile RE doctrine | mobile analysis |
|
|
63
|
+
| `cloud` | cloud-enum doctrine | cloud tools |
|
|
64
|
+
|
|
65
|
+
## src/browser-agent.ts
|
|
66
|
+
|
|
67
|
+
Session-based browser automation → **TOOL** (LLM calls `browser_agent` op).
|
|
68
|
+
Keep as-is.
|
|
69
|
+
|
|
70
|
+
## src/server.ts (69 MCP tools)
|
|
71
|
+
|
|
72
|
+
**KEEP** — the deterministic analysis/verification/evidence tools are the moat.
|
|
73
|
+
Add `instructions` + `next_steps` per tool for LLM ergonomics.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Net outcome
|
|
78
|
+
|
|
79
|
+
- **Keep as TOOL (moat):** taint engine, universal taint, route-confusion,
|
|
80
|
+
complex-bugs, strike_verify, hypothesis engine, security graph, adversarial
|
|
81
|
+
reviewer, coverage, recovery, budget-status, memory, scope gate, browser agent.
|
|
82
|
+
- **Become SKILL:** 13 agent doctrines, reviewer 10-questions, coverage
|
|
83
|
+
dimensions, attack branches, escalation graph, termination reasons, recovery
|
|
84
|
+
alternatives.
|
|
85
|
+
- **Remove (REDUNDANT):** `runMission` loop, task scheduler, replan, model
|
|
86
|
+
router, termination auto-decision, `mission` CLI, `registerMissionTools`.
|
|
87
|
+
|
|
88
|
+
The deterministic analysis stays; the deterministic *orchestration* goes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.65",
|
|
4
4
|
"description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adversarial-review
|
|
3
|
+
description: Use before confirming a verified finding as real. Run the ten adversarial questions, score confidence, dedup shared-root findings, and reject anything that does not survive. This is the anti-hallucination gate between "hypothesis" and "confirmed finding".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adversarial review (anti-hallucination gate)
|
|
7
|
+
|
|
8
|
+
"A hypothesis is not a finding. AI reasoning is not evidence. Severity is not
|
|
9
|
+
confidence." Before any finding is confirmed, attack it from every angle.
|
|
10
|
+
|
|
11
|
+
## The ten questions (§15)
|
|
12
|
+
|
|
13
|
+
For each finding, answer these with evidence — not vibes:
|
|
14
|
+
|
|
15
|
+
1. Is the vulnerable code actually **reachable**?
|
|
16
|
+
2. Is **authentication** required (and did we account for it)?
|
|
17
|
+
3. Is **authorization** enforced elsewhere (would the attacker be blocked)?
|
|
18
|
+
4. Is there **middleware/WAF/sanitizer** protection?
|
|
19
|
+
5. Is the input actually **attacker-controlled**?
|
|
20
|
+
6. Is the **sink** reachable from that input?
|
|
21
|
+
7. Is the observed behavior just **caching** (ETag/304)?
|
|
22
|
+
8. Is the exploit **reproducible** (marker confirmed, not one-off)?
|
|
23
|
+
9. Does the **negative control** remain inert?
|
|
24
|
+
10. Is the **impact** accurately classified (CWE/CVSS, not inflated)?
|
|
25
|
+
|
|
26
|
+
## Verdict
|
|
27
|
+
|
|
28
|
+
- Any **fail** → REJECTED. Drop it, do not soften it.
|
|
29
|
+
- Fewer than 3 clear **passes** → NEEDS_MORE_EVIDENCE. Go back to `verify-finding`.
|
|
30
|
+
- 3+ passes, 0 fails → CONFIRMED.
|
|
31
|
+
|
|
32
|
+
## Scoring + dedup
|
|
33
|
+
|
|
34
|
+
- `confidence_score` — deterministic weighted confidence (evidence, verification,
|
|
35
|
+
reachability). Do not invent a confidence number.
|
|
36
|
+
- `dedup_findings` — collapse findings that share a root cause before reporting.
|
|
37
|
+
- `finding_transition` — advance only via the strict lifecycle.
|
|
38
|
+
|
|
39
|
+
## Why
|
|
40
|
+
|
|
41
|
+
A false positive costs the user their reputation. Rejecting a real bug is
|
|
42
|
+
recoverable (it stays in memory); reporting a fake one is not.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrate-engagement
|
|
3
|
+
description: Use when the user asks for a full security engagement ("fullscan", "audit this", "pentest this", "scan my target") or any end-to-end assessment. You are the orchestrator — Blitz Strike provides the deterministic analysis and verification tools you drive. Run recon → analyze → verify → review → report without babysitting.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Orchestrate an engagement (no-babysitting)
|
|
7
|
+
|
|
8
|
+
You are the brain. Blitz Strike is the hands. Drive the full cycle with the
|
|
9
|
+
deterministic MCP tools; never guess a finding — produce it from tool output.
|
|
10
|
+
|
|
11
|
+
## The cycle
|
|
12
|
+
|
|
13
|
+
1. **Scope gate (always first).** Call `scope_check` before any live action.
|
|
14
|
+
A target outside scope or without authorization → refuse, record, stop.
|
|
15
|
+
Static source paths on disk are always in-scope; live URLs require an
|
|
16
|
+
explicit scope.
|
|
17
|
+
|
|
18
|
+
2. **Recon (passive-first).** For a live target: `live_recon` (fingerprint, WAF,
|
|
19
|
+
endpoints, tech). For a source tree: `blitz_scan` (entry points + sinks),
|
|
20
|
+
`detect_framework`, `list_attack_vectors`.
|
|
21
|
+
|
|
22
|
+
3. **Analyze.** Source → `source-audit` skill (taint + route-confusion +
|
|
23
|
+
complex-bugs). Live → `active_scan` / `enrich_scan`. Consult `tech_correlation`
|
|
24
|
+
/ `cve_correlation` / `nvd_lookup` to map tech → known weakness classes.
|
|
25
|
+
|
|
26
|
+
4. **Verify.** Every hypothesis must pass `verify-finding` before it is a finding.
|
|
27
|
+
"Detection is not proof."
|
|
28
|
+
|
|
29
|
+
5. **Review.** Every verified finding passes `adversarial-review` before it is
|
|
30
|
+
confirmed. "A hypothesis is not a finding."
|
|
31
|
+
|
|
32
|
+
6. **Report.** Emit `generate_report` + `cvss_score` + `write-report`.
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
- One hypothesis per finding; `finding_create` + `finding_transition` for the
|
|
37
|
+
strict lifecycle (candidate → confirmed → rejected).
|
|
38
|
+
- `dedup_findings` before reporting — collapse shared-root duplicates.
|
|
39
|
+
- Persist hard-won knowledge with `remember`; recall it with `memory_lookup`
|
|
40
|
+
before re-discovering the same target/pattern.
|
|
41
|
+
- You are the orchestrator — do not try to reimplement planning in prose; pick
|
|
42
|
+
tools, act, and let tool output drive the next step.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scope-gate
|
|
3
|
+
description: Use BEFORE any live action against a target (recon, scanning, exploitation, verification). Enforce scope and authorization boundaries — never touch a target outside the declared scope, and never run live actions against a target the user has not authorized.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Scope gate
|
|
7
|
+
|
|
8
|
+
Scope is non-negotiable and cannot be bypassed. Before any live HTTP request,
|
|
9
|
+
port scan, or exploitation step, resolve the boundary deterministically.
|
|
10
|
+
|
|
11
|
+
## Procedure
|
|
12
|
+
|
|
13
|
+
1. Call `scope_check` with the target. It returns in-scope / out-of-scope / needs-authorization.
|
|
14
|
+
|
|
15
|
+
2. **Local source paths** are always in-scope (static analysis of code on disk).
|
|
16
|
+
Live URLs, IPs, and domains require an explicit, user-declared scope.
|
|
17
|
+
|
|
18
|
+
3. If `scope_check` says out-of-scope or authorization is missing → **refuse and
|
|
19
|
+
record.** Do not probe, do not "just check one thing," do not find a bypass.
|
|
20
|
+
|
|
21
|
+
## Red lines
|
|
22
|
+
|
|
23
|
+
- Malicious / third-party infrastructure ("cari pelaku") → hold, static analysis
|
|
24
|
+
+ takedown only. Never live-probe.
|
|
25
|
+
- Authorized research targets → live action is fine, but only within the scope
|
|
26
|
+
the user declared.
|
|
27
|
+
- A finding that depends on out-of-scope action is not a finding — drop it.
|
|
28
|
+
|
|
29
|
+
## Why
|
|
30
|
+
|
|
31
|
+
"Scope cannot be bypassed." A tool that ignores scope is worse than a tool that
|
|
32
|
+
misses a bug. The gate is a hard invariant, not a suggestion.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: source-audit
|
|
3
|
+
description: Use when auditing source code (PHP/JS/Python/Java/etc.) for vulnerabilities — static source→sink taint, route confusion/dispatch abuse, deserialization, type juggling, and mass assignment. This is Blitz Strike's deterministic static-analysis moat, distinct from live/dynamic testing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Source audit (deterministic static analysis)
|
|
7
|
+
|
|
8
|
+
Drive Blitz Strike's static analyzers over a source tree and turn each hit into a
|
|
9
|
+
hypothesis. Do not hand-guess; let the tools produce the source→sink evidence.
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
1. **Map the tree.** `blitz_scan <root>` — entry points (nopriv hooks, REST
|
|
14
|
+
routes, controllers) + sink inventory per file. `detect_framework` per file
|
|
15
|
+
to know what conventions apply.
|
|
16
|
+
|
|
17
|
+
2. **Trace data flow.** For each source→sink candidate:
|
|
18
|
+
- `taint_file` / `taint_scan` — variable-level taint tracking per file.
|
|
19
|
+
- `eagle_eye2` — whole-program interprocedural taint (PHP, method-call aware).
|
|
20
|
+
- `trace_data_flow` — classify sources vs sinks across a file.
|
|
21
|
+
- `variant_analysis` — group data-flow candidates by shared root cause.
|
|
22
|
+
|
|
23
|
+
3. **Escalate.** `enrich_scan` — match detected sinks to escalation chains
|
|
24
|
+
(SQLi → RCE, upload → shell, LFI → RCE). `list_chains` to see the full
|
|
25
|
+
escalation taxonomy. `tech_correlation` for framework-specific weaknesses.
|
|
26
|
+
|
|
27
|
+
4. **Deep/complex classes.** Beyond plain taint, look for:
|
|
28
|
+
- **Route confusion** — `call_user_func`, `$obj->$method()`, batch/proxy
|
|
29
|
+
forwarding, dynamic include, route match without normalization.
|
|
30
|
+
- **Deserialization → POP chain** — `unserialize` + `__destruct`/`__wakeup`/
|
|
31
|
+
`__toString` present.
|
|
32
|
+
- **Type juggling** — loose `==`/`!=` of user input vs hash/secret.
|
|
33
|
+
- **Mass assignment** — `extract()` without EXTR_SKIP, `parse_str()` of input.
|
|
34
|
+
|
|
35
|
+
5. **Correlate.** `cve_correlation` + `nvd_lookup` for the framework/pattern to
|
|
36
|
+
confirm the weakness class is real and known.
|
|
37
|
+
|
|
38
|
+
## Pitfalls
|
|
39
|
+
|
|
40
|
+
- A sink is not a finding — a sink **reached by attacker-controlled input** is a
|
|
41
|
+
hypothesis. Check the source provenance before reporting.
|
|
42
|
+
- Framework accessors are sources too (`$request->input()`, `request.GET`,
|
|
43
|
+
`@RequestParam`), not just `$_GET`.
|
|
44
|
+
- Parameterized queries, integer casts (`intval`/`absint`/`(int)`), and ORM calls
|
|
45
|
+
are neutralizers — do not flag a sink that is actually sanitized.
|
|
46
|
+
|
|
47
|
+
## Output
|
|
48
|
+
|
|
49
|
+
Every confirmed sink → `finding_create` with the source→sink chain as evidence,
|
|
50
|
+
then `finding_transition` candidate → validated after `verify-finding`.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-finding
|
|
3
|
+
description: Use when you have a hypothesis and need to prove it is a real, exploitable finding before reporting it. Live verification with a marker + negative control — never report an unverified hypothesis as a finding.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Verify a finding (marker + negative control)
|
|
7
|
+
|
|
8
|
+
"Detection is not proof." A static hit or a scan match is a hypothesis until it
|
|
9
|
+
is live-verified with a distinguishable marker and an inert negative control.
|
|
10
|
+
|
|
11
|
+
## Procedure
|
|
12
|
+
|
|
13
|
+
1. **Pick the right verifier.**
|
|
14
|
+
- Live HTTP → `strike_verify` (sends a payload, checks for a unique marker).
|
|
15
|
+
- Browser/DOM (XSS, auth flows) → `browser_validate`.
|
|
16
|
+
|
|
17
|
+
2. **Marker, not echo.** Use a unique, nonce-like marker (`blitzstrike-<rand>`)
|
|
18
|
+
that cannot be a benign echo. If the response reflects it, that is the signal.
|
|
19
|
+
|
|
20
|
+
3. **Negative control.** Send a payload that is structurally similar but MUST NOT
|
|
21
|
+
trigger (a wrong param, a harmless value). If the control ALSO reflects the
|
|
22
|
+
marker, it is a `false_positive` — the app echoes everything, not exploitable.
|
|
23
|
+
|
|
24
|
+
4. **Interpret.**
|
|
25
|
+
- Marker reflected + control inert → `verdict=confirmed`, attach evidence.
|
|
26
|
+
- Marker reflected + control also reflected → `false_positive`, reject.
|
|
27
|
+
- Nothing → retry once, then `NEEDS_MORE_EVIDENCE`.
|
|
28
|
+
|
|
29
|
+
5. **Record.** `finding_attach_evidence` (redacted, SHA-256 tagged) +
|
|
30
|
+
`finding_transition` to validated/rejected. Use `redact` on any secret before
|
|
31
|
+
it is persisted.
|
|
32
|
+
|
|
33
|
+
## Pitfalls
|
|
34
|
+
|
|
35
|
+
- Never verify by "looks right" — the marker/control pair is the only proof.
|
|
36
|
+
- A blind RCE without a callback channel: tunnel the callback (public tunnel,
|
|
37
|
+
no local IP) and observe it arrive — that is the proof.
|
|
38
|
+
- Keep evidence redacted; raw secrets must never be persisted.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: write-report
|
|
3
|
+
description: Use when finalizing confirmed findings into a submission-ready report (bug bounty, pentest deliverable, or audit). Produce a report with root cause, reproduction, CVSS, impact, and remediation — indistinguishable from a top-tier human hunter.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Write the report
|
|
7
|
+
|
|
8
|
+
Turn confirmed findings into a submission-ready deliverable. A triager should
|
|
9
|
+
not be able to tell an agent wrote it.
|
|
10
|
+
|
|
11
|
+
## Structure per finding
|
|
12
|
+
|
|
13
|
+
1. **Title + summary** — one line, precise, no clickbait.
|
|
14
|
+
2. **Root cause** — the exact code path (source → sink), not a generic description.
|
|
15
|
+
3. **Reproduction steps** — exact request(s) + the marker/control proof from
|
|
16
|
+
`verify-finding`.
|
|
17
|
+
4. **Impact** — what an attacker actually gains (data, RCE, account takeover).
|
|
18
|
+
5. **CVSS** — `cvss_score` with the correct vector string; do not inflate.
|
|
19
|
+
6. **Remediation** — concrete, implementable, framework-appropriate.
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
- `generate_report` — deterministic markdown/JSON report from the findings.
|
|
24
|
+
- `cvss_score` — deterministic CVSS v3.1 vector.
|
|
25
|
+
- `redact` — strip passwords/keys/tokens/cookies before any output.
|
|
26
|
+
- `finding_attach_evidence` — link the redacted, SHA-256-tagged evidence.
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
- Only CONFIRMED findings (passed `adversarial-review`) go in the report.
|
|
31
|
+
- No false-positive spam, no "low-hanging fruit" filler.
|
|
32
|
+
- Never include raw secrets, API keys, or live session tokens.
|
|
33
|
+
- Group by root cause; a report full of duplicates reads as noise.
|