blitzstrike 2.4.70 → 2.4.72
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 +39 -0
- package/dist/index.js +411 -121
- package/intelligence/cwe_map.json +10 -0
- package/intelligence/hackbot-arena.json +339 -0
- package/intelligence/patterns.json +57 -0
- package/intelligence/variants.json +24 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16265,6 +16265,8 @@ function detectComplexBugs(code, file) {
|
|
|
16265
16265
|
detectGrpcReflection(code, file, out);
|
|
16266
16266
|
detectDependencyConfusion(code, file, out);
|
|
16267
16267
|
detectMlSupplyChain(code, file, out);
|
|
16268
|
+
detectRustUnsafe(code, file, out);
|
|
16269
|
+
detectRustFormatString(code, file, out);
|
|
16268
16270
|
out.push(...detectWrongSanitizer(code, file));
|
|
16269
16271
|
out.push(...detectMissingAuthz(code, file));
|
|
16270
16272
|
const seen = new Set;
|
|
@@ -16753,6 +16755,46 @@ function detectMlSupplyChain(code, file, out) {
|
|
|
16753
16755
|
}
|
|
16754
16756
|
}
|
|
16755
16757
|
}
|
|
16758
|
+
function detectRustUnsafe(code, file, out) {
|
|
16759
|
+
if (!/\.rs$/i.test(file))
|
|
16760
|
+
return;
|
|
16761
|
+
const ls = lines2(code);
|
|
16762
|
+
const clean = stripCommentsOnly(code);
|
|
16763
|
+
const re = /std::mem::transmute|\btransmute\s*\(|\bassume_init\s*\(|from_raw_parts(?:_mut)?\s*\(|ptr::(copy|copy_nonoverlapping|read|write|swap)\s*\(|from_utf8_unchecked\s*\(|\bunreachable_unchecked\s*\(/g;
|
|
16764
|
+
let m;
|
|
16765
|
+
while ((m = re.exec(clean)) !== null) {
|
|
16766
|
+
const ln = lineNo2(code, m.index);
|
|
16767
|
+
out.push({
|
|
16768
|
+
file,
|
|
16769
|
+
line: ln,
|
|
16770
|
+
type: "rust_unsafe",
|
|
16771
|
+
category: "Rust unsafe / memory-unsafety surface",
|
|
16772
|
+
severity: "medium",
|
|
16773
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16774
|
+
detail: `\`${m[0]}\` bypasses the borrow checker's memory-safety guarantees — type confusion (transmute), uninitialized reads (assume_init), or unchecked pointer/slice construction (from_raw_parts / ptr::*) can lead to out-of-bounds access, use-after-free, or UB (CWE-119/787/416). Verify the input is bounds-checked and no attacker-controlled length/index reaches it.`
|
|
16775
|
+
});
|
|
16776
|
+
}
|
|
16777
|
+
}
|
|
16778
|
+
function detectRustFormatString(code, file, out) {
|
|
16779
|
+
if (!/\.rs$/i.test(file))
|
|
16780
|
+
return;
|
|
16781
|
+
const ls = lines2(code);
|
|
16782
|
+
const clean = stripCommentsOnly(code);
|
|
16783
|
+
const re = /\b(format!|println!|print!|eprintln!|eprint!)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)/g;
|
|
16784
|
+
let m;
|
|
16785
|
+
while ((m = re.exec(clean)) !== null) {
|
|
16786
|
+
const ln = lineNo2(code, m.index);
|
|
16787
|
+
out.push({
|
|
16788
|
+
file,
|
|
16789
|
+
line: ln,
|
|
16790
|
+
type: "rust_format_string",
|
|
16791
|
+
category: "Rust format string injection",
|
|
16792
|
+
severity: "high",
|
|
16793
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16794
|
+
detail: `\`${m[1]}\` passes a variable (\`${m[2]}\`) as the FORMAT string instead of a literal — if attacker-controlled, format specifiers (positional args, width, precision) can read or corrupt memory (CWE-134). Use \`${m[1]}!("{}", value)\` with the input as an argument, never as the format string.`
|
|
16795
|
+
});
|
|
16796
|
+
}
|
|
16797
|
+
}
|
|
16756
16798
|
var SOURCE_TOKENS3, MAGIC_METHODS, SECRET_FIELD_RE, SECRET_PLACEHOLDER_RE, ENTROPY_EXCLUDE_VAR;
|
|
16757
16799
|
var init_complex_bugs = __esm(() => {
|
|
16758
16800
|
init_security_state();
|
|
@@ -16948,17 +16990,31 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
|
|
|
16948
16990
|
file: "train.py",
|
|
16949
16991
|
positive: `import torch\\nmodel = torch.load(downloaded_model)\\n`,
|
|
16950
16992
|
negative: `import torch\\nmodel = torch.load("model.pt", weights_only=True)\\n`
|
|
16993
|
+
},
|
|
16994
|
+
{
|
|
16995
|
+
id: "rust-unsafe",
|
|
16996
|
+
detector: "rust_unsafe",
|
|
16997
|
+
file: "lib.rs",
|
|
16998
|
+
positive: `fn f() { let x: &[u8] = std::mem::transmute(user_data); }\\n`,
|
|
16999
|
+
negative: `fn f() { let x: &[u8] = user_data.as_bytes(); }\\n`
|
|
17000
|
+
},
|
|
17001
|
+
{
|
|
17002
|
+
id: "rust-format-string",
|
|
17003
|
+
detector: "rust_format_string",
|
|
17004
|
+
file: "main.rs",
|
|
17005
|
+
positive: `fn f() { println!(user_input); }\\n`,
|
|
17006
|
+
negative: `fn f() { println!("{}", user_input); }\\n`
|
|
16951
17007
|
}
|
|
16952
17008
|
];
|
|
16953
17009
|
});
|
|
16954
17010
|
|
|
16955
17011
|
// src/variants.ts
|
|
16956
|
-
import { readFileSync as
|
|
16957
|
-
import { join as
|
|
16958
|
-
import { fileURLToPath as
|
|
17012
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
17013
|
+
import { join as join20, dirname as dirname5 } from "node:path";
|
|
17014
|
+
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
16959
17015
|
function loadVariants() {
|
|
16960
17016
|
try {
|
|
16961
|
-
const doc = JSON.parse(
|
|
17017
|
+
const doc = JSON.parse(readFileSync20(join20(__dirname5, "..", "intelligence", "variants.json"), "utf8"));
|
|
16962
17018
|
return doc.variants ?? [];
|
|
16963
17019
|
} catch {
|
|
16964
17020
|
return [];
|
|
@@ -17009,18 +17065,18 @@ function variantCoverageText() {
|
|
|
17009
17065
|
return lines.join(`
|
|
17010
17066
|
`);
|
|
17011
17067
|
}
|
|
17012
|
-
var
|
|
17068
|
+
var __dirname5, CROSS_FILE_ONLY;
|
|
17013
17069
|
var init_variants = __esm(() => {
|
|
17014
17070
|
init_complex_bugs();
|
|
17015
|
-
|
|
17071
|
+
__dirname5 = dirname5(fileURLToPath11(import.meta.url));
|
|
17016
17072
|
CROSS_FILE_ONLY = new Set(["priv_esc"]);
|
|
17017
17073
|
});
|
|
17018
17074
|
|
|
17019
17075
|
// src/sync.ts
|
|
17020
|
-
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as
|
|
17021
|
-
import { join as
|
|
17076
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync26, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
|
|
17077
|
+
import { join as join24 } from "node:path";
|
|
17022
17078
|
import { homedir as homedir10 } from "node:os";
|
|
17023
|
-
import { fileURLToPath as
|
|
17079
|
+
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
17024
17080
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
17025
17081
|
function dirExists(p) {
|
|
17026
17082
|
try {
|
|
@@ -17035,7 +17091,7 @@ function countFiles(dir) {
|
|
|
17035
17091
|
const walk = (d) => {
|
|
17036
17092
|
for (const e of readdirSync7(d, { withFileTypes: true })) {
|
|
17037
17093
|
if (e.isDirectory())
|
|
17038
|
-
walk(
|
|
17094
|
+
walk(join24(d, e.name));
|
|
17039
17095
|
else
|
|
17040
17096
|
n++;
|
|
17041
17097
|
}
|
|
@@ -17049,8 +17105,8 @@ function countFiles(dir) {
|
|
|
17049
17105
|
function syncData() {
|
|
17050
17106
|
if (!dirExists(DATA_ROOT2))
|
|
17051
17107
|
mkdirSync9(DATA_ROOT2, { recursive: true });
|
|
17052
|
-
const payloadsPath =
|
|
17053
|
-
const templatesPath =
|
|
17108
|
+
const payloadsPath = join24(DATA_ROOT2, "payloads");
|
|
17109
|
+
const templatesPath = join24(DATA_ROOT2, "templates");
|
|
17054
17110
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
17055
17111
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
17056
17112
|
if (hasPayloads && hasTemplates) {
|
|
@@ -17060,8 +17116,8 @@ function syncData() {
|
|
|
17060
17116
|
data_root: DATA_ROOT2
|
|
17061
17117
|
};
|
|
17062
17118
|
}
|
|
17063
|
-
const localPayloads =
|
|
17064
|
-
const localTemplates =
|
|
17119
|
+
const localPayloads = join24(ROOT7, "payloads");
|
|
17120
|
+
const localTemplates = join24(ROOT7, "templates");
|
|
17065
17121
|
let payloadsSynced = false;
|
|
17066
17122
|
let templatesSynced = false;
|
|
17067
17123
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -17074,7 +17130,7 @@ function syncData() {
|
|
|
17074
17130
|
}
|
|
17075
17131
|
const gitOk = spawnSync4("git", ["--version"]).status === 0;
|
|
17076
17132
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
17077
|
-
const tmp =
|
|
17133
|
+
const tmp = join24(DATA_ROOT2, ".tmp-clone");
|
|
17078
17134
|
if (dirExists(tmp))
|
|
17079
17135
|
rmSync4(tmp, { recursive: true, force: true });
|
|
17080
17136
|
const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -17082,11 +17138,11 @@ function syncData() {
|
|
|
17082
17138
|
const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
17083
17139
|
if (sparse.status === 0) {
|
|
17084
17140
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
17085
|
-
copyDir(
|
|
17141
|
+
copyDir(join24(tmp, "payloads"), payloadsPath);
|
|
17086
17142
|
payloadsSynced = true;
|
|
17087
17143
|
}
|
|
17088
17144
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
17089
|
-
copyDir(
|
|
17145
|
+
copyDir(join24(tmp, "templates"), templatesPath);
|
|
17090
17146
|
templatesSynced = true;
|
|
17091
17147
|
}
|
|
17092
17148
|
}
|
|
@@ -17108,18 +17164,18 @@ function copyDir(src, dst) {
|
|
|
17108
17164
|
function manualCopy(src, dst) {
|
|
17109
17165
|
mkdirSync9(dst, { recursive: true });
|
|
17110
17166
|
for (const e of readdirSync7(src, { withFileTypes: true })) {
|
|
17111
|
-
const s =
|
|
17112
|
-
const d =
|
|
17167
|
+
const s = join24(src, e.name);
|
|
17168
|
+
const d = join24(dst, e.name);
|
|
17113
17169
|
if (e.isDirectory())
|
|
17114
17170
|
manualCopy(s, d);
|
|
17115
17171
|
else
|
|
17116
|
-
writeFileSync8(d,
|
|
17172
|
+
writeFileSync8(d, readFileSync26(s));
|
|
17117
17173
|
}
|
|
17118
17174
|
}
|
|
17119
17175
|
var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
17120
17176
|
var init_sync = __esm(() => {
|
|
17121
|
-
ROOT7 =
|
|
17122
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
17177
|
+
ROOT7 = join24(fileURLToPath13(new URL(".", import.meta.url)), "..");
|
|
17178
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join24(homedir10(), ".blitzstrike", "data");
|
|
17123
17179
|
});
|
|
17124
17180
|
|
|
17125
17181
|
// src/update.ts
|
|
@@ -17164,15 +17220,15 @@ var init_update = __esm(() => {
|
|
|
17164
17220
|
});
|
|
17165
17221
|
|
|
17166
17222
|
// src/release.ts
|
|
17167
|
-
import { readFileSync as
|
|
17168
|
-
import { join as
|
|
17169
|
-
import { fileURLToPath as
|
|
17223
|
+
import { readFileSync as readFileSync28, existsSync as existsSync13 } from "node:fs";
|
|
17224
|
+
import { join as join25 } from "node:path";
|
|
17225
|
+
import { fileURLToPath as fileURLToPath14 } from "node:url";
|
|
17170
17226
|
import { execSync as execSync2 } from "node:child_process";
|
|
17171
17227
|
function validateRelease() {
|
|
17172
17228
|
const checks = [];
|
|
17173
17229
|
checks.push({ name: "package.json version", ok: true, detail: VERSION });
|
|
17174
17230
|
try {
|
|
17175
|
-
const changelog =
|
|
17231
|
+
const changelog = readFileSync28(join25(ROOT8, "CHANGELOG.md"), "utf8");
|
|
17176
17232
|
const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
|
|
17177
17233
|
const changelogVersion = m?.[1] ?? null;
|
|
17178
17234
|
checks.push({
|
|
@@ -17204,22 +17260,22 @@ function validateRelease() {
|
|
|
17204
17260
|
return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
|
|
17205
17261
|
}
|
|
17206
17262
|
function lockfilePresent() {
|
|
17207
|
-
return existsSync13(
|
|
17263
|
+
return existsSync13(join25(ROOT8, "package-lock.json")) || existsSync13(join25(ROOT8, "bun.lock")) || existsSync13(join25(ROOT8, "bun.lockb"));
|
|
17208
17264
|
}
|
|
17209
17265
|
var ROOT8;
|
|
17210
17266
|
var init_release = __esm(() => {
|
|
17211
17267
|
init_version();
|
|
17212
|
-
ROOT8 =
|
|
17268
|
+
ROOT8 = join25(fileURLToPath14(new URL(".", import.meta.url)), "..");
|
|
17213
17269
|
});
|
|
17214
17270
|
|
|
17215
17271
|
// src/dependency.ts
|
|
17216
|
-
import { readFileSync as
|
|
17217
|
-
import { join as
|
|
17218
|
-
import { fileURLToPath as
|
|
17272
|
+
import { readFileSync as readFileSync29 } from "node:fs";
|
|
17273
|
+
import { join as join26 } from "node:path";
|
|
17274
|
+
import { fileURLToPath as fileURLToPath15 } from "node:url";
|
|
17219
17275
|
function checkDependencies() {
|
|
17220
17276
|
let pkg = {};
|
|
17221
17277
|
try {
|
|
17222
|
-
pkg = JSON.parse(
|
|
17278
|
+
pkg = JSON.parse(readFileSync29(join26(ROOT9, "package.json"), "utf8"));
|
|
17223
17279
|
} catch {
|
|
17224
17280
|
return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
|
|
17225
17281
|
}
|
|
@@ -17242,14 +17298,14 @@ function checkDependencies() {
|
|
|
17242
17298
|
}
|
|
17243
17299
|
var ROOT9;
|
|
17244
17300
|
var init_dependency = __esm(() => {
|
|
17245
|
-
ROOT9 =
|
|
17301
|
+
ROOT9 = join26(fileURLToPath15(new URL(".", import.meta.url)), "..");
|
|
17246
17302
|
});
|
|
17247
17303
|
|
|
17248
17304
|
// src/cli.ts
|
|
17249
|
-
import { readFileSync as
|
|
17250
|
-
import { join as
|
|
17305
|
+
import { readFileSync as readFileSync30, existsSync as existsSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
|
|
17306
|
+
import { join as join27, dirname as dirname6 } from "node:path";
|
|
17251
17307
|
import { homedir as homedir11 } from "node:os";
|
|
17252
|
-
import { fileURLToPath as
|
|
17308
|
+
import { fileURLToPath as fileURLToPath16 } from "node:url";
|
|
17253
17309
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
17254
17310
|
function which(cmd) {
|
|
17255
17311
|
return spawnSync5("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", cmd]).status === 0;
|
|
@@ -17292,7 +17348,7 @@ async function runDoctor() {
|
|
|
17292
17348
|
detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
|
|
17293
17349
|
fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
|
|
17294
17350
|
});
|
|
17295
|
-
const dataOk = existsSync14(
|
|
17351
|
+
const dataOk = existsSync14(join27(ROOT10, "chains.json")) && existsSync14(join27(ROOT10, "tools-catalog.json"));
|
|
17296
17352
|
issues.push({
|
|
17297
17353
|
name: "Data layers (chains + tools-catalog)",
|
|
17298
17354
|
status: dataOk ? "ok" : "fail",
|
|
@@ -17343,28 +17399,28 @@ BlitzStrike — ${verdict}`);
|
|
|
17343
17399
|
function resolveCommand() {
|
|
17344
17400
|
if (which("blitzstrike"))
|
|
17345
17401
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
17346
|
-
const src =
|
|
17402
|
+
const src = join27(ROOT10, "src", "index.ts");
|
|
17347
17403
|
if (which("bun") && existsSync14(src))
|
|
17348
17404
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
17349
17405
|
if (which("npx"))
|
|
17350
17406
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
17351
17407
|
if (which("bunx"))
|
|
17352
17408
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
17353
|
-
const dist =
|
|
17409
|
+
const dist = join27(ROOT10, "dist", "index.js");
|
|
17354
17410
|
if (existsSync14(dist))
|
|
17355
17411
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
17356
17412
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
17357
17413
|
}
|
|
17358
17414
|
function readJson(p) {
|
|
17359
17415
|
try {
|
|
17360
|
-
return JSON.parse(
|
|
17416
|
+
return JSON.parse(readFileSync30(p, "utf8"));
|
|
17361
17417
|
} catch {
|
|
17362
17418
|
return null;
|
|
17363
17419
|
}
|
|
17364
17420
|
}
|
|
17365
17421
|
function jsonMcpServersWrite(p) {
|
|
17366
17422
|
return (command, args) => {
|
|
17367
|
-
const dir =
|
|
17423
|
+
const dir = dirname6(p);
|
|
17368
17424
|
if (!existsSync14(dir))
|
|
17369
17425
|
mkdirSync10(dir, { recursive: true });
|
|
17370
17426
|
const existing = readJson(p) ?? {};
|
|
@@ -17377,7 +17433,7 @@ function jsonMcpServersWrite(p) {
|
|
|
17377
17433
|
}
|
|
17378
17434
|
function opencodeWrite(p) {
|
|
17379
17435
|
return (command, args) => {
|
|
17380
|
-
const dir =
|
|
17436
|
+
const dir = dirname6(p);
|
|
17381
17437
|
if (!existsSync14(dir))
|
|
17382
17438
|
mkdirSync10(dir, { recursive: true });
|
|
17383
17439
|
const existing = readJson(p) ?? {};
|
|
@@ -17390,26 +17446,26 @@ function opencodeWrite(p) {
|
|
|
17390
17446
|
};
|
|
17391
17447
|
}
|
|
17392
17448
|
function copyOpenCodeAgents() {
|
|
17393
|
-
const srcDir =
|
|
17394
|
-
const dstDir =
|
|
17449
|
+
const srcDir = join27(ROOT10, "opencode-agents");
|
|
17450
|
+
const dstDir = join27(homedir11(), ".config", "opencode", "agents");
|
|
17395
17451
|
if (!existsSync14(srcDir))
|
|
17396
17452
|
return;
|
|
17397
17453
|
if (!existsSync14(dstDir))
|
|
17398
17454
|
mkdirSync10(dstDir, { recursive: true });
|
|
17399
17455
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
17400
|
-
const src =
|
|
17401
|
-
const dst =
|
|
17456
|
+
const src = join27(srcDir, f);
|
|
17457
|
+
const dst = join27(dstDir, f);
|
|
17402
17458
|
if (existsSync14(src)) {
|
|
17403
|
-
writeFileSync9(dst,
|
|
17459
|
+
writeFileSync9(dst, readFileSync30(src, "utf8"));
|
|
17404
17460
|
}
|
|
17405
17461
|
}
|
|
17406
17462
|
}
|
|
17407
17463
|
function codexWrite(p) {
|
|
17408
17464
|
return (command, args) => {
|
|
17409
|
-
const dir =
|
|
17465
|
+
const dir = dirname6(p);
|
|
17410
17466
|
if (!existsSync14(dir))
|
|
17411
17467
|
mkdirSync10(dir, { recursive: true });
|
|
17412
|
-
let existing = existsSync14(p) ?
|
|
17468
|
+
let existing = existsSync14(p) ? readFileSync30(p, "utf8") : "";
|
|
17413
17469
|
if (!existing.trimEnd().endsWith(`
|
|
17414
17470
|
`))
|
|
17415
17471
|
existing += `
|
|
@@ -17425,10 +17481,10 @@ args = ${JSON.stringify(args)}
|
|
|
17425
17481
|
}
|
|
17426
17482
|
function hermesWrite(p) {
|
|
17427
17483
|
return (command, args) => {
|
|
17428
|
-
const dir =
|
|
17484
|
+
const dir = dirname6(p);
|
|
17429
17485
|
if (!existsSync14(dir))
|
|
17430
17486
|
mkdirSync10(dir, { recursive: true });
|
|
17431
|
-
let existing = existsSync14(p) ?
|
|
17487
|
+
let existing = existsSync14(p) ? readFileSync30(p, "utf8") : "";
|
|
17432
17488
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
17433
17489
|
if (!existing.trimEnd().endsWith(`
|
|
17434
17490
|
`))
|
|
@@ -17455,74 +17511,74 @@ mcp_servers:
|
|
|
17455
17511
|
function detectAgents() {
|
|
17456
17512
|
const home = homedir11();
|
|
17457
17513
|
const agents = [];
|
|
17458
|
-
const claudeJson =
|
|
17514
|
+
const claudeJson = join27(home, ".claude.json");
|
|
17459
17515
|
agents.push({
|
|
17460
17516
|
name: "Claude Code",
|
|
17461
17517
|
detectPath: claudeJson,
|
|
17462
17518
|
installed: () => existsSync14(claudeJson) || which("claude"),
|
|
17463
17519
|
write: jsonMcpServersWrite(claudeJson)
|
|
17464
17520
|
});
|
|
17465
|
-
const claudeDesktop =
|
|
17521
|
+
const claudeDesktop = join27(home, ".config", "Claude", "claude_desktop_config.json");
|
|
17466
17522
|
agents.push({
|
|
17467
17523
|
name: "Claude Desktop",
|
|
17468
17524
|
detectPath: claudeDesktop,
|
|
17469
17525
|
installed: () => existsSync14(claudeDesktop),
|
|
17470
17526
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
17471
17527
|
});
|
|
17472
|
-
const cursor =
|
|
17528
|
+
const cursor = join27(home, ".cursor", "mcp.json");
|
|
17473
17529
|
agents.push({
|
|
17474
17530
|
name: "Cursor",
|
|
17475
17531
|
detectPath: cursor,
|
|
17476
|
-
installed: () => existsSync14(
|
|
17532
|
+
installed: () => existsSync14(join27(home, ".cursor")) || which("cursor"),
|
|
17477
17533
|
write: jsonMcpServersWrite(cursor)
|
|
17478
17534
|
});
|
|
17479
|
-
const opencode =
|
|
17535
|
+
const opencode = join27(home, ".config", "opencode", "opencode.jsonc");
|
|
17480
17536
|
agents.push({
|
|
17481
17537
|
name: "OpenCode",
|
|
17482
17538
|
detectPath: opencode,
|
|
17483
|
-
installed: () => existsSync14(
|
|
17539
|
+
installed: () => existsSync14(join27(home, ".config", "opencode")) || which("opencode"),
|
|
17484
17540
|
write: opencodeWrite(opencode)
|
|
17485
17541
|
});
|
|
17486
|
-
const codex =
|
|
17542
|
+
const codex = join27(home, ".codex", "config.toml");
|
|
17487
17543
|
agents.push({
|
|
17488
17544
|
name: "Codex",
|
|
17489
17545
|
detectPath: codex,
|
|
17490
|
-
installed: () => existsSync14(
|
|
17546
|
+
installed: () => existsSync14(join27(home, ".codex")) || which("codex"),
|
|
17491
17547
|
write: codexWrite(codex)
|
|
17492
17548
|
});
|
|
17493
|
-
const hermes =
|
|
17549
|
+
const hermes = join27(home, ".hermes", "config.yaml");
|
|
17494
17550
|
agents.push({
|
|
17495
17551
|
name: "Hermes",
|
|
17496
17552
|
detectPath: hermes,
|
|
17497
|
-
installed: () => existsSync14(
|
|
17553
|
+
installed: () => existsSync14(join27(home, ".hermes")) || which("hermes"),
|
|
17498
17554
|
write: hermesWrite(hermes)
|
|
17499
17555
|
});
|
|
17500
|
-
const gemini =
|
|
17556
|
+
const gemini = join27(home, ".gemini", "settings.json");
|
|
17501
17557
|
agents.push({
|
|
17502
17558
|
name: "Gemini CLI",
|
|
17503
17559
|
detectPath: gemini,
|
|
17504
|
-
installed: () => existsSync14(
|
|
17560
|
+
installed: () => existsSync14(join27(home, ".gemini")) || which("gemini"),
|
|
17505
17561
|
write: jsonMcpServersWrite(gemini)
|
|
17506
17562
|
});
|
|
17507
|
-
const windsurf =
|
|
17563
|
+
const windsurf = join27(home, ".codeium", "windsurf", "mcp_config.json");
|
|
17508
17564
|
agents.push({
|
|
17509
17565
|
name: "Windsurf",
|
|
17510
17566
|
detectPath: windsurf,
|
|
17511
|
-
installed: () => existsSync14(
|
|
17567
|
+
installed: () => existsSync14(join27(home, ".codeium")) || which("windsurf"),
|
|
17512
17568
|
write: jsonMcpServersWrite(windsurf)
|
|
17513
17569
|
});
|
|
17514
|
-
const copilot =
|
|
17570
|
+
const copilot = join27(home, ".copilot", "mcp.json");
|
|
17515
17571
|
agents.push({
|
|
17516
17572
|
name: "Copilot",
|
|
17517
17573
|
detectPath: copilot,
|
|
17518
|
-
installed: () => existsSync14(
|
|
17574
|
+
installed: () => existsSync14(join27(home, ".copilot")),
|
|
17519
17575
|
write: jsonMcpServersWrite(copilot)
|
|
17520
17576
|
});
|
|
17521
|
-
const cline =
|
|
17577
|
+
const cline = join27(home, ".cline", "mcp_settings.json");
|
|
17522
17578
|
agents.push({
|
|
17523
17579
|
name: "Cline",
|
|
17524
17580
|
detectPath: cline,
|
|
17525
|
-
installed: () => existsSync14(
|
|
17581
|
+
installed: () => existsSync14(join27(home, ".cline")),
|
|
17526
17582
|
write: jsonMcpServersWrite(cline)
|
|
17527
17583
|
});
|
|
17528
17584
|
return agents;
|
|
@@ -17640,7 +17696,7 @@ var init_cli = __esm(() => {
|
|
|
17640
17696
|
init_release();
|
|
17641
17697
|
init_dependency();
|
|
17642
17698
|
init_mcp_status();
|
|
17643
|
-
ROOT10 =
|
|
17699
|
+
ROOT10 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
|
|
17644
17700
|
});
|
|
17645
17701
|
|
|
17646
17702
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -32278,7 +32334,7 @@ class StdioServerTransport {
|
|
|
32278
32334
|
|
|
32279
32335
|
// src/server.ts
|
|
32280
32336
|
init_scanner();
|
|
32281
|
-
import { readFileSync as
|
|
32337
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
32282
32338
|
|
|
32283
32339
|
// src/recon.ts
|
|
32284
32340
|
var TIMEOUT_MS = 25000;
|
|
@@ -56680,10 +56736,97 @@ var javaAdapter = {
|
|
|
56680
56736
|
return { sources, sinks, sanitizers, calls, assigns };
|
|
56681
56737
|
}
|
|
56682
56738
|
};
|
|
56739
|
+
var RUST_SOURCES = [
|
|
56740
|
+
[/env::args\s*\(|args_os\s*\(/, "cli_argument"],
|
|
56741
|
+
[/stdin\s*\(\s*\)|read_line\s*\(/, "raw_body"],
|
|
56742
|
+
[/\b(?:Query|Form|Json|Path)\b|web::(?:Query|Json|Form|Path)|req\.(?:query|params|param|form|query_string)/, "http_parameter"],
|
|
56743
|
+
[/env::var\s*\(|env::var_os\s*\(/, "env_variable"]
|
|
56744
|
+
];
|
|
56745
|
+
var RUST_SINKS = [
|
|
56746
|
+
[/Command::new|process::Command/, "command_execution", "CWE-78"],
|
|
56747
|
+
[/sqlx::query(?:_as)?\s*\(|sqlx::raw_sql|\.query\s*\(|\.execute\s*\(|rusqlite|diesel::/, "sql_execution", "CWE-89"],
|
|
56748
|
+
[/reqwest::get|reqwest::Client|hyper::|\.get\s*\(|\.post\s*\(|\.put\s*\(|\.request\s*\(/, "http_request", "CWE-918"],
|
|
56749
|
+
[/std::fs::(read_to_string|read|write|remove_file|copy|rename)|File::open\s*\(|fs::(read|write|remove_file|copy)\s*\(/, "path_traversal", "CWE-22"],
|
|
56750
|
+
[/serde_json::from_(str|slice)|bincode::deserialize|serde_yaml::from_str|toml::from_str|\.deserialize\s*\(/, "deserialization", "CWE-502"]
|
|
56751
|
+
];
|
|
56752
|
+
var RUST_SANITIZERS = [
|
|
56753
|
+
[/canonicalize\s*\(/, ["path_traversal"]],
|
|
56754
|
+
[/query(_as)?!\s*\(/, ["sql_execution"]]
|
|
56755
|
+
];
|
|
56756
|
+
var rustAdapter = {
|
|
56757
|
+
language: "rust",
|
|
56758
|
+
extensions: [".rs"],
|
|
56759
|
+
parse(code, _file) {
|
|
56760
|
+
const sources = [];
|
|
56761
|
+
const sinks = [];
|
|
56762
|
+
const sanitizers = [];
|
|
56763
|
+
const calls = [];
|
|
56764
|
+
const assigns = [];
|
|
56765
|
+
const assignRe = /let\s+(?:mut\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+?);/g;
|
|
56766
|
+
let m;
|
|
56767
|
+
while ((m = assignRe.exec(code)) !== null) {
|
|
56768
|
+
const varName = m[1];
|
|
56769
|
+
const rhs = m[2];
|
|
56770
|
+
const line = code.slice(0, m.index).split(`
|
|
56771
|
+
`).length;
|
|
56772
|
+
const src = RUST_SOURCES.find(([re]) => re.test(rhs));
|
|
56773
|
+
if (src)
|
|
56774
|
+
sources.push({ variable: varName, kind: src[1], line, attacker_controlled: src[1] !== "cli_argument" && src[1] !== "env_variable" });
|
|
56775
|
+
const rhsVars = [];
|
|
56776
|
+
const vre = /([a-z_][a-z0-9_]*)/g;
|
|
56777
|
+
let vm;
|
|
56778
|
+
while ((vm = vre.exec(rhs)) !== null) {
|
|
56779
|
+
const v = vm[1];
|
|
56780
|
+
if (!RUST_SOURCES.some(([re]) => re.test(v)))
|
|
56781
|
+
rhsVars.push(v);
|
|
56782
|
+
}
|
|
56783
|
+
assigns.push({ target: varName, sources: rhsVars.slice(0, 8), line });
|
|
56784
|
+
}
|
|
56785
|
+
const extractorRe = /\b(?:Query|Form|Json|Path)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)/g;
|
|
56786
|
+
while ((m = extractorRe.exec(code)) !== null) {
|
|
56787
|
+
const line = code.slice(0, m.index).split(`
|
|
56788
|
+
`).length;
|
|
56789
|
+
sources.push({ variable: m[1], kind: "http_parameter", line, attacker_controlled: true });
|
|
56790
|
+
assigns.push({ target: m[1], sources: [], line });
|
|
56791
|
+
}
|
|
56792
|
+
const lines = code.split(`
|
|
56793
|
+
`);
|
|
56794
|
+
for (let i = 0;i < lines.length; i++) {
|
|
56795
|
+
const line = lines[i];
|
|
56796
|
+
for (const [re, kind, cwe] of RUST_SINKS) {
|
|
56797
|
+
if (!re.test(line))
|
|
56798
|
+
continue;
|
|
56799
|
+
if (kind === "sql_execution" && /query(_as)?!\s*\(/.test(line))
|
|
56800
|
+
continue;
|
|
56801
|
+
const argVars = [];
|
|
56802
|
+
const vre = /([a-z_][a-z0-9_]*)/g;
|
|
56803
|
+
let vm;
|
|
56804
|
+
while ((vm = vre.exec(line)) !== null) {
|
|
56805
|
+
const id = vm[1];
|
|
56806
|
+
if (/^(new|let|mut|arg|args|command|process|std|sqlx|query|query_as|execute|reqwest|client|get|post|put|request|fs|file|open|read_to_string|read|write|remove_file|copy|rename|serde_json|from_str|from_slice|bincode|deserialize|toml|yaml|format|unwrap|expect|env|args|stdin|read_line)$/.test(id))
|
|
56807
|
+
continue;
|
|
56808
|
+
argVars.push(id);
|
|
56809
|
+
}
|
|
56810
|
+
for (const v of argVars.slice(0, 8))
|
|
56811
|
+
sinks.push({ variable: v, kind, line: i + 1, cwe });
|
|
56812
|
+
}
|
|
56813
|
+
for (const [re, kinds] of RUST_SANITIZERS) {
|
|
56814
|
+
if (re.test(line)) {
|
|
56815
|
+
const vre = /([a-z_][a-z0-9_]*)/g;
|
|
56816
|
+
let vm;
|
|
56817
|
+
while ((vm = vre.exec(line)) !== null)
|
|
56818
|
+
sanitizers.push({ variable: vm[1], neutralizes: kinds, line: i + 1 });
|
|
56819
|
+
}
|
|
56820
|
+
}
|
|
56821
|
+
}
|
|
56822
|
+
return { sources, sinks, sanitizers, calls, assigns };
|
|
56823
|
+
}
|
|
56824
|
+
};
|
|
56683
56825
|
registerLanguage(phpAdapter);
|
|
56684
56826
|
registerLanguage(jsAdapter);
|
|
56685
56827
|
registerLanguage(pythonAdapter);
|
|
56686
56828
|
registerLanguage(javaAdapter);
|
|
56829
|
+
registerLanguage(rustAdapter);
|
|
56687
56830
|
|
|
56688
56831
|
// src/incremental.ts
|
|
56689
56832
|
var SOURCE_EXTS = [".php", ".phtml", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
|
|
@@ -58011,7 +58154,14 @@ var ALIASES = {
|
|
|
58011
58154
|
"ai supply chain": "ml_supply_chain",
|
|
58012
58155
|
"model poisoning": "ml_supply_chain",
|
|
58013
58156
|
"ml security": "ml_supply_chain",
|
|
58014
|
-
"ai ml security": "ml_supply_chain"
|
|
58157
|
+
"ai ml security": "ml_supply_chain",
|
|
58158
|
+
"rust unsafe": "rust_unsafe",
|
|
58159
|
+
"unsafe rust": "rust_unsafe",
|
|
58160
|
+
"rust memory safety": "rust_unsafe",
|
|
58161
|
+
transmute: "rust_unsafe",
|
|
58162
|
+
"rust format string": "rust_format_string",
|
|
58163
|
+
"format string": "rust_format_string",
|
|
58164
|
+
"format string injection": "rust_format_string"
|
|
58015
58165
|
};
|
|
58016
58166
|
function normalize(term) {
|
|
58017
58167
|
return term.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -58279,6 +58429,103 @@ async function validateLeakedKey(input) {
|
|
|
58279
58429
|
};
|
|
58280
58430
|
}
|
|
58281
58431
|
|
|
58432
|
+
// src/hackbot-arena.ts
|
|
58433
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
58434
|
+
import { join as join18, dirname as dirname3 } from "node:path";
|
|
58435
|
+
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
58436
|
+
var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
|
|
58437
|
+
function load4() {
|
|
58438
|
+
try {
|
|
58439
|
+
return JSON.parse(readFileSync18(join18(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
|
|
58440
|
+
} catch {
|
|
58441
|
+
return { total: 0, flag_format: "FLAG{nusasec-<32 hex>}", labs: [] };
|
|
58442
|
+
}
|
|
58443
|
+
}
|
|
58444
|
+
var VECTOR_MAP = [
|
|
58445
|
+
{ re: /jwt|algorithm confusion|rs256|hs256/i, vectors: ["jwt"], detectors: ["missing_authz"], chains: ["jwt"], tools: ["jwt_analyze", "live_recon"] },
|
|
58446
|
+
{ re: /ssrf|server-side request/i, vectors: ["ssrf"], detectors: ["ssrf"], chains: ["ssrf"], tools: ["live_recon", "blind_oracle", "active_scan"] },
|
|
58447
|
+
{ re: /sql injection|sqli|postgresql|sql/i, vectors: ["sql_injection"], detectors: ["sql_injection"], chains: ["sql_injection"], tools: ["live_recon", "blind_oracle", "active_scan"] },
|
|
58448
|
+
{ re: /ssti|template injection|liquid|jinja|twig/i, vectors: ["ssti"], detectors: ["ssti"], chains: ["ssti"], tools: ["live_recon", "blind_oracle"] },
|
|
58449
|
+
{ re: /command injection|os command|command execution/i, vectors: ["command_injection"], detectors: ["command_injection"], chains: ["command_injection"], tools: ["live_recon", "blind_oracle"] },
|
|
58450
|
+
{ re: /idor|bola|authorization|broken function|role|membership|ownership|entitlement|directory search|excessive data|data exposure|harvest/i, vectors: ["broken_object_level_authorization"], detectors: ["missing_authz"], chains: ["idor"], tools: ["live_recon", "active_scan", "param_precedence"] },
|
|
58451
|
+
{ re: /graphql/i, vectors: ["graphql_exposure"], detectors: ["graphql_exposure", "sql_injection"], chains: ["graphql"], tools: ["live_recon", "active_scan"] },
|
|
58452
|
+
{ re: /cache/i, vectors: ["web_cache_deception"], detectors: [], chains: ["cache_deception"], tools: ["cache_gap", "live_recon"] },
|
|
58453
|
+
{ re: /credential|key|token|bearer|secret|leak|websocket|rum|api key/i, vectors: ["hardcoded_secret"], detectors: ["hardcoded_secret"], chains: ["secret_leak"], tools: ["live_recon", "drive_devtools", "scan_git_history"] },
|
|
58454
|
+
{ re: /rate.limit|batching|otp/i, vectors: ["business_logic"], detectors: [], chains: ["rate_limit_bypass"], tools: ["race_test", "active_scan"] },
|
|
58455
|
+
{ re: /mass assignment|self.regist|admin account|user creation|scope/i, vectors: ["mass_assignment"], detectors: ["mass_assignment", "missing_authz"], chains: ["mass_assignment"], tools: ["live_recon", "active_scan"] },
|
|
58456
|
+
{ re: /deserialization|rce|workflow|execution/i, vectors: ["code_execution"], detectors: ["deserialization"], chains: ["deserialization"], tools: ["live_recon", "active_scan"] }
|
|
58457
|
+
];
|
|
58458
|
+
function mapLab(lab) {
|
|
58459
|
+
const out = { vectors: [], detectors: [], chains: [], tools: [] };
|
|
58460
|
+
for (const m of VECTOR_MAP) {
|
|
58461
|
+
if (m.re.test(lab.vuln_class)) {
|
|
58462
|
+
out.vectors.push(...m.vectors);
|
|
58463
|
+
out.detectors.push(...m.detectors);
|
|
58464
|
+
out.chains.push(...m.chains);
|
|
58465
|
+
out.tools.push(...m.tools);
|
|
58466
|
+
}
|
|
58467
|
+
}
|
|
58468
|
+
out.vectors = [...new Set(out.vectors)];
|
|
58469
|
+
out.detectors = [...new Set(out.detectors)];
|
|
58470
|
+
out.chains = [...new Set(out.chains)];
|
|
58471
|
+
out.tools = [...new Set(out.tools)];
|
|
58472
|
+
return out;
|
|
58473
|
+
}
|
|
58474
|
+
function findByLab(labId) {
|
|
58475
|
+
const id = labId.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
58476
|
+
return load4().labs.find((l) => l.id.toLowerCase() === id || l.id.toLowerCase() === `labs${id}`) ?? null;
|
|
58477
|
+
}
|
|
58478
|
+
function hackbotArenaList() {
|
|
58479
|
+
const doc = load4();
|
|
58480
|
+
return {
|
|
58481
|
+
arena: "Hackbot-Arena (NusaSec)",
|
|
58482
|
+
total: doc.total,
|
|
58483
|
+
flag_format: doc.flag_format,
|
|
58484
|
+
note: "Port = 8080 + lab number (labs01 -> 8081 … labs30 -> 8110). Each lab has a solver/ reference exploit + judge.success_when solve criteria.",
|
|
58485
|
+
labs: doc.labs.map((l) => ({ id: l.id, name: l.name, difficulty: l.difficulty, port: l.port, vuln_class: l.vuln_class, stack: l.stack }))
|
|
58486
|
+
};
|
|
58487
|
+
}
|
|
58488
|
+
function hackbotArenaBrief(labId) {
|
|
58489
|
+
const lab = findByLab(labId);
|
|
58490
|
+
if (!lab)
|
|
58491
|
+
return { error: `unknown lab '${labId}'`, hint: "use hackbot_arena_list() to see all 30 labs" };
|
|
58492
|
+
const plan = mapLab(lab);
|
|
58493
|
+
return {
|
|
58494
|
+
id: lab.id,
|
|
58495
|
+
name: lab.name,
|
|
58496
|
+
difficulty: lab.difficulty,
|
|
58497
|
+
target: `http://localhost:${lab.port}`,
|
|
58498
|
+
vuln_class: lab.vuln_class,
|
|
58499
|
+
stack: lab.stack,
|
|
58500
|
+
flag_format: load4().flag_format,
|
|
58501
|
+
attack_plan: {
|
|
58502
|
+
vectors: plan.vectors,
|
|
58503
|
+
detectors: plan.detectors,
|
|
58504
|
+
chains: plan.chains,
|
|
58505
|
+
tools: plan.tools
|
|
58506
|
+
},
|
|
58507
|
+
hint: `Start with live_recon(target) to fingerprint, then drive the ${plan.chains.join(" + ") || "detected"} chain. Verify every step live (strike_verify / browser_validate) and recover the flag. Flag format: ${load4().flag_format}.`
|
|
58508
|
+
};
|
|
58509
|
+
}
|
|
58510
|
+
function hackbotArenaCoverage() {
|
|
58511
|
+
const doc = load4();
|
|
58512
|
+
const rows = doc.labs.map((l) => {
|
|
58513
|
+
const plan = mapLab(l);
|
|
58514
|
+
return { id: l.id, name: l.name, vuln_class: l.vuln_class, vectors: plan.vectors, detectors: plan.detectors, chains: plan.chains };
|
|
58515
|
+
});
|
|
58516
|
+
const withVector = rows.filter((r) => r.vectors.length > 0).length;
|
|
58517
|
+
const withDetector = rows.filter((r) => r.detectors.length > 0).length;
|
|
58518
|
+
return {
|
|
58519
|
+
arena: "Hackbot-Arena (NusaSec)",
|
|
58520
|
+
total_labs: doc.total,
|
|
58521
|
+
labs_with_mapped_vector: withVector,
|
|
58522
|
+
labs_with_detector_coverage: withDetector,
|
|
58523
|
+
coverage_ratio: Number((withVector / doc.total).toFixed(2)),
|
|
58524
|
+
note: "A mapped vector/detector means Blitz Strike has the deterministic tooling to drive that vuln class; the agent still has to execute the chain + recover the flag.",
|
|
58525
|
+
rows
|
|
58526
|
+
};
|
|
58527
|
+
}
|
|
58528
|
+
|
|
58282
58529
|
// src/severity.ts
|
|
58283
58530
|
var VRT_MAP = {
|
|
58284
58531
|
sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
|
|
@@ -58302,7 +58549,9 @@ var VRT_MAP = {
|
|
|
58302
58549
|
oauth_misconfig: { vrt: "authentication > oauth_misconfiguration", priority: "P2", severity: "high", rationale: "An OAuth/open-redirect misconfiguration can leak authorization codes → account takeover." },
|
|
58303
58550
|
grpc_reflection: { vrt: "misconfiguration > debug_information_exposure", priority: "P5", severity: "low", rationale: "gRPC reflection exposes the API contract but is not directly exploitable." },
|
|
58304
58551
|
dependency_confusion: { vrt: "insecure_design > dependency_confusion", priority: "P2", severity: "high", rationale: "An internal package resolves from a squattable public registry → attacker-controlled code in the build." },
|
|
58305
|
-
ml_supply_chain: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Untrusted model loading via pickle (torch/joblib/numpy) → arbitrary code execution on load." }
|
|
58552
|
+
ml_supply_chain: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Untrusted model loading via pickle (torch/joblib/numpy) → arbitrary code execution on load." },
|
|
58553
|
+
rust_unsafe: { vrt: "insecure_design > unsafe_memory_operations", priority: "P3", severity: "medium", rationale: "transmute/from_raw_parts/assume_init bypass the borrow checker — a hint that needs bounds verification; memory corruption if attacker-controlled." },
|
|
58554
|
+
rust_format_string: { vrt: "injection > format_string_injection", priority: "P2", severity: "high", rationale: "An attacker-controlled format string can read or corrupt memory via format specifiers." }
|
|
58306
58555
|
};
|
|
58307
58556
|
function severityCalibrate(input) {
|
|
58308
58557
|
const type = input.type?.toLowerCase() ?? "";
|
|
@@ -58341,13 +58590,13 @@ function listSeverityMap() {
|
|
|
58341
58590
|
}
|
|
58342
58591
|
|
|
58343
58592
|
// src/datarefresh.ts
|
|
58344
|
-
import { readFileSync as
|
|
58345
|
-
import { join as
|
|
58346
|
-
import { fileURLToPath as
|
|
58347
|
-
var
|
|
58593
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
58594
|
+
import { join as join19, dirname as dirname4 } from "node:path";
|
|
58595
|
+
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
58596
|
+
var __dirname4 = dirname4(fileURLToPath10(import.meta.url));
|
|
58348
58597
|
function loadJson2(rel) {
|
|
58349
58598
|
try {
|
|
58350
|
-
return JSON.parse(
|
|
58599
|
+
return JSON.parse(readFileSync19(join19(__dirname4, "..", rel), "utf8"));
|
|
58351
58600
|
} catch {
|
|
58352
58601
|
return null;
|
|
58353
58602
|
}
|
|
@@ -58382,18 +58631,18 @@ init_verify_harness();
|
|
|
58382
58631
|
init_variants();
|
|
58383
58632
|
|
|
58384
58633
|
// src/benchmark.ts
|
|
58385
|
-
import { readFileSync as
|
|
58386
|
-
import { join as
|
|
58387
|
-
import { fileURLToPath as
|
|
58634
|
+
import { readFileSync as readFileSync21, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
58635
|
+
import { join as join21 } from "node:path";
|
|
58636
|
+
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
58388
58637
|
import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
|
|
58389
58638
|
init_complex_bugs();
|
|
58390
|
-
var ROOT5 =
|
|
58391
|
-
var CORPUS =
|
|
58392
|
-
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ??
|
|
58639
|
+
var ROOT5 = join21(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
58640
|
+
var CORPUS = join21(ROOT5, "bench", "corpus");
|
|
58641
|
+
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join21(homedir8(), ".blitzstrike", "corpus");
|
|
58393
58642
|
function parseCorpusFile(path) {
|
|
58394
58643
|
let text;
|
|
58395
58644
|
try {
|
|
58396
|
-
text =
|
|
58645
|
+
text = readFileSync21(path, "utf8");
|
|
58397
58646
|
} catch {
|
|
58398
58647
|
return null;
|
|
58399
58648
|
}
|
|
@@ -58449,7 +58698,7 @@ function loadDir(dir) {
|
|
|
58449
58698
|
for (const f of readdirSync5(dir)) {
|
|
58450
58699
|
if (!f.endsWith(".md"))
|
|
58451
58700
|
continue;
|
|
58452
|
-
const e = parseCorpusFile(
|
|
58701
|
+
const e = parseCorpusFile(join21(dir, f));
|
|
58453
58702
|
if (e)
|
|
58454
58703
|
entries.push(e);
|
|
58455
58704
|
}
|
|
@@ -58476,7 +58725,7 @@ function captureFalsePositive(input) {
|
|
|
58476
58725
|
input.code,
|
|
58477
58726
|
"```"
|
|
58478
58727
|
].filter((l) => l !== null);
|
|
58479
|
-
const path =
|
|
58728
|
+
const path = join21(CAPTURED_CORPUS_DIR, `${id}.md`);
|
|
58480
58729
|
writeFileSync6(path, lines.join(`
|
|
58481
58730
|
`) + `
|
|
58482
58731
|
`, "utf8");
|
|
@@ -58498,7 +58747,7 @@ function detect(entry) {
|
|
|
58498
58747
|
}
|
|
58499
58748
|
const lang = entry.language;
|
|
58500
58749
|
if (lang === "php") {
|
|
58501
|
-
const tmp =
|
|
58750
|
+
const tmp = join21(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
58502
58751
|
writeFileSync6(tmp, entry.code);
|
|
58503
58752
|
const r = analyzeDataFlow2(tmp);
|
|
58504
58753
|
rmSync3(tmp, { force: true });
|
|
@@ -58567,12 +58816,12 @@ function runBenchmark() {
|
|
|
58567
58816
|
}
|
|
58568
58817
|
|
|
58569
58818
|
// src/obligations.ts
|
|
58570
|
-
import { readFileSync as
|
|
58571
|
-
import { join as
|
|
58819
|
+
import { readFileSync as readFileSync22, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync7 } from "node:fs";
|
|
58820
|
+
import { join as join22 } from "node:path";
|
|
58572
58821
|
import { createHash as createHash6 } from "node:crypto";
|
|
58573
58822
|
import { homedir as homedir9 } from "node:os";
|
|
58574
|
-
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ??
|
|
58575
|
-
var OBLIGATION_PATH =
|
|
58823
|
+
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join22(homedir9(), ".blitzstrike");
|
|
58824
|
+
var OBLIGATION_PATH = join22(HOME_DIR4, "obligations.jsonl");
|
|
58576
58825
|
function ensureDir3() {
|
|
58577
58826
|
if (!existsSync11(HOME_DIR4))
|
|
58578
58827
|
mkdirSync8(HOME_DIR4, { recursive: true });
|
|
@@ -58585,7 +58834,7 @@ function loadObligations() {
|
|
|
58585
58834
|
return [];
|
|
58586
58835
|
try {
|
|
58587
58836
|
const out = [];
|
|
58588
|
-
for (const line of
|
|
58837
|
+
for (const line of readFileSync22(OBLIGATION_PATH, "utf8").split(`
|
|
58589
58838
|
`)) {
|
|
58590
58839
|
const t = line.trim();
|
|
58591
58840
|
if (!t)
|
|
@@ -58689,11 +58938,11 @@ init_framework_security();
|
|
|
58689
58938
|
// src/variant-scan.ts
|
|
58690
58939
|
init_scanner();
|
|
58691
58940
|
init_complex_bugs();
|
|
58692
|
-
import { readFileSync as
|
|
58941
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
58693
58942
|
|
|
58694
58943
|
// src/cross-file.ts
|
|
58695
58944
|
init_scanner();
|
|
58696
|
-
import { readFileSync as
|
|
58945
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
58697
58946
|
var PUBLIC_HOOK_RE = /(?:wp_ajax_nopriv_\w+|admin_post_nopriv_\w+|init|template_redirect|wp_loaded|parse_request|user_register|um_user_register|register_new_user|authenticate)/;
|
|
58698
58947
|
var PRIV_SINK_RE = /\b(set_role|add_role|remove_role|set_user_role|wp_set_current_user|wp_set_auth_cookie|wp_insert_user|wp_create_user|wp_update_user)\s*\(/;
|
|
58699
58948
|
var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
|
|
@@ -58745,7 +58994,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
58745
58994
|
for (const file of files) {
|
|
58746
58995
|
let code;
|
|
58747
58996
|
try {
|
|
58748
|
-
code = stripComments(
|
|
58997
|
+
code = stripComments(readFileSync23(file, "utf8"));
|
|
58749
58998
|
} catch {
|
|
58750
58999
|
continue;
|
|
58751
59000
|
}
|
|
@@ -58786,7 +59035,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
58786
59035
|
for (const file of files) {
|
|
58787
59036
|
let code;
|
|
58788
59037
|
try {
|
|
58789
|
-
code = stripComments(
|
|
59038
|
+
code = stripComments(readFileSync23(file, "utf8"));
|
|
58790
59039
|
} catch {
|
|
58791
59040
|
continue;
|
|
58792
59041
|
}
|
|
@@ -58809,7 +59058,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
58809
59058
|
for (const file of files) {
|
|
58810
59059
|
let code;
|
|
58811
59060
|
try {
|
|
58812
|
-
code = stripComments(
|
|
59061
|
+
code = stripComments(readFileSync23(file, "utf8"));
|
|
58813
59062
|
} catch {
|
|
58814
59063
|
continue;
|
|
58815
59064
|
}
|
|
@@ -58924,7 +59173,7 @@ function variantScan(root, opts = {}) {
|
|
|
58924
59173
|
for (const file of files) {
|
|
58925
59174
|
let code;
|
|
58926
59175
|
try {
|
|
58927
|
-
code =
|
|
59176
|
+
code = readFileSync24(file, "utf8");
|
|
58928
59177
|
} catch {
|
|
58929
59178
|
continue;
|
|
58930
59179
|
}
|
|
@@ -59584,17 +59833,17 @@ function analyzeJwt(token, opts = {}) {
|
|
|
59584
59833
|
}
|
|
59585
59834
|
|
|
59586
59835
|
// src/enterprise-benchmark.ts
|
|
59587
|
-
import { existsSync as existsSync12, readFileSync as
|
|
59588
|
-
import { join as
|
|
59836
|
+
import { existsSync as existsSync12, readFileSync as readFileSync25, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
59837
|
+
import { join as join23, resolve } from "node:path";
|
|
59589
59838
|
init_complex_bugs();
|
|
59590
59839
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
59591
59840
|
var ENTERPRISE_FIXTURES = [
|
|
59592
|
-
{ id: "php-laravel", dir:
|
|
59593
|
-
{ id: "js-express", dir:
|
|
59594
|
-
{ id: "py-django", dir:
|
|
59595
|
-
{ id: "java-spring", dir:
|
|
59596
|
-
{ id: "wp-batch-route", dir:
|
|
59597
|
-
{ id: "php-complex", dir:
|
|
59841
|
+
{ id: "php-laravel", dir: join23(ROOT6, "php-laravel"), expected_vulns: 4 },
|
|
59842
|
+
{ id: "js-express", dir: join23(ROOT6, "js-express"), expected_vulns: 2 },
|
|
59843
|
+
{ id: "py-django", dir: join23(ROOT6, "py-django"), expected_vulns: 2 },
|
|
59844
|
+
{ id: "java-spring", dir: join23(ROOT6, "java-spring"), expected_vulns: 2 },
|
|
59845
|
+
{ id: "wp-batch-route", dir: join23(ROOT6, "wp-batch-route"), expected_vulns: 3 },
|
|
59846
|
+
{ id: "php-complex", dir: join23(ROOT6, "php-complex"), expected_vulns: 4 }
|
|
59598
59847
|
];
|
|
59599
59848
|
function sourceFiles(root) {
|
|
59600
59849
|
const out = [];
|
|
@@ -59606,7 +59855,7 @@ function sourceFiles(root) {
|
|
|
59606
59855
|
return;
|
|
59607
59856
|
}
|
|
59608
59857
|
for (const e of entries) {
|
|
59609
|
-
const p =
|
|
59858
|
+
const p = join23(dir, e);
|
|
59610
59859
|
let st;
|
|
59611
59860
|
try {
|
|
59612
59861
|
st = statSync3(p);
|
|
@@ -59627,7 +59876,7 @@ function detectFixture(dir) {
|
|
|
59627
59876
|
for (const f of sourceFiles(dir)) {
|
|
59628
59877
|
let code;
|
|
59629
59878
|
try {
|
|
59630
|
-
code =
|
|
59879
|
+
code = readFileSync25(f, "utf8");
|
|
59631
59880
|
} catch {
|
|
59632
59881
|
continue;
|
|
59633
59882
|
}
|
|
@@ -59758,11 +60007,29 @@ var JAVA_SINKS2 = {
|
|
|
59758
60007
|
xpath_injection: true,
|
|
59759
60008
|
ldap_injection: true
|
|
59760
60009
|
};
|
|
60010
|
+
var RUST_SINKS2 = {
|
|
60011
|
+
sql_execution: true,
|
|
60012
|
+
code_execution: false,
|
|
60013
|
+
command_execution: true,
|
|
60014
|
+
file_operations: true,
|
|
60015
|
+
file_inclusion: false,
|
|
60016
|
+
deserialization: true,
|
|
60017
|
+
http_request: true,
|
|
60018
|
+
redirect: true,
|
|
60019
|
+
html_render: false,
|
|
60020
|
+
xml_processing: false,
|
|
60021
|
+
archive_extraction: false,
|
|
60022
|
+
path_traversal: true,
|
|
60023
|
+
template_injection: false,
|
|
60024
|
+
xpath_injection: false,
|
|
60025
|
+
ldap_injection: false
|
|
60026
|
+
};
|
|
59761
60027
|
var SINK_BY_LANG = {
|
|
59762
60028
|
php: PHP_SINKS2,
|
|
59763
60029
|
javascript: JS_SINKS2,
|
|
59764
60030
|
python: PY_SINKS2,
|
|
59765
|
-
java: JAVA_SINKS2
|
|
60031
|
+
java: JAVA_SINKS2,
|
|
60032
|
+
rust: RUST_SINKS2
|
|
59766
60033
|
};
|
|
59767
60034
|
function coverageMatrix() {
|
|
59768
60035
|
const langs = listLanguages();
|
|
@@ -59822,7 +60089,7 @@ function createServer() {
|
|
|
59822
60089
|
` + `- A hard block (Cloudflare/WAF/IP-block/captcha) is NOT the end: mark the finding blocked + note the required vantage (residential / authorized / correct-SNI / --resolve direct-to-origin), then CONTINUE to the next lead (origin bypass, non-prod cluster, mobile APK, subdomain).
|
|
59823
60090
|
` + `- Keep trying every lead AND every chain step until each is verified/executed or blocked, THEN generate_report. Never deliver a bare surface map, and never stop at 'found X' when X enables more.
|
|
59824
60091
|
|
|
59825
|
-
` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + "DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Calibrate severity with severity_calibrate(type, cvss) to get a P1–P5 tier + VRT-style category. Start each engagement with engagement_scaffold(target, scope) for the folder + scope.md structure, and check data_refresh() to confirm the data layers are consistent. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.\n" + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified. For a repo, also scan_git_history(root) — secrets DELETED from HEAD still live in .git history (the removed-later-is-not-fixed trap), and it feeds every recovered pre-deletion blob through the same secret detector. Once you recover a credential, prove it is still ACTIVE with validate_leaked_key(apiBase, endpoints, key|blobUrl) — a READ-ONLY differential test (401-without-key vs 200-with-key = auth bypass PROVEN). Two supply-chain detectors: dependency_confusion (a manifest/build file resolving a package from a public registry — pypi.org/npmjs.org --extra-index-url, or a scoped @company/pkg with no private registry) and ml_supply_chain (torch.load without weights_only=True / joblib.load / np.load(allow_pickle=True) / keras load_model on an untrusted model → pickle RCE).\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
|
|
60092
|
+
` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + "DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Calibrate severity with severity_calibrate(type, cvss) to get a P1–P5 tier + VRT-style category. Start each engagement with engagement_scaffold(target, scope) for the folder + scope.md structure, and check data_refresh() to confirm the data layers are consistent. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.\n" + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified. For a repo, also scan_git_history(root) — secrets DELETED from HEAD still live in .git history (the removed-later-is-not-fixed trap), and it feeds every recovered pre-deletion blob through the same secret detector. Once you recover a credential, prove it is still ACTIVE with validate_leaked_key(apiBase, endpoints, key|blobUrl) — a READ-ONLY differential test (401-without-key vs 200-with-key = auth bypass PROVEN). Two supply-chain detectors: dependency_confusion (a manifest/build file resolving a package from a public registry — pypi.org/npmjs.org --extra-index-url, or a scoped @company/pkg with no private registry) and ml_supply_chain (torch.load without weights_only=True / joblib.load / np.load(allow_pickle=True) / keras load_model on an untrusted model → pickle RCE). Rust is supported end-to-end: the taint engine (blitz_scan / taint_file) maps Rust sinks (std::process::Command, sqlx/diesel, reqwest/hyper, std::fs, serde/bincode) to the classic classes, plus two Rust-specific detectors — rust_unsafe (transmute / assume_init / from_raw_parts / ptr::* — memory-unsafety surface, CWE-119) and rust_format_string (println!/format! with a variable as the format string — CWE-134). The sqlx query! compile-time macro is treated as safe (suppressed).\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
|
|
59826
60093
|
` + `- When a lead needs DOM/JS precision — DOM-XSS sink execution, AJAX endpoint + payload interception, JS runtime errors, redirect chains, SSO/token flow, or anything a static HTML glance cannot prove — call drive_devtools(target). It spawns chrome-devtools-mcp and drives a REAL headless browser (navigate -> list_network_requests -> list_console_messages -> evaluate_script), giving reproducible evidence instead of a guess.
|
|
59827
60094
|
` + `- Before calling it, check_mcp() to confirm the browser MCP is available; if not, blitzstrike install-tools or npm i -g chrome-devtools-mcp. Never substitute eyeballing minified JS or a static sink guess when a live browser can PROVE it. This is the precision + consistency layer.
|
|
59828
60095
|
` + "EXTERNAL MCP SERVERS (verified, connect them — NOT one-shot CLIs): chrome-devtools-mcp (29 tools: list_network_requests/get_network_request for raw HTTP interception, list_console_messages for JS errors + DOM-XSS, evaluate_script to run JS, take_screenshot) -> stdio chrome-devtools-mcp --headless --chromeArg=--no-sandbox --chromeArg=--disable-gpu (as root/container this is MANDATORY or the browser crashes; add --proxyServer http://127.0.0.1:8080 to route through Burp, --acceptInsecureCerts for bad TLS). burp-suite-mcp (proxy history + active scan + repeater) -> NOT npm: build the JAR (git clone https://github.com/PortSwigger/mcp-server.git && cd mcp-server && ./gradlew embedProxyJar), load it in Burp Extensions, then connect the client to http://127.0.0.1:9876 (SSE) or the stdio proxy java -jar mcp-proxy-all.jar --sse-url http://127.0.0.1:9876."
|
|
@@ -60292,6 +60559,29 @@ ${statusLine}` : statusLine },
|
|
|
60292
60559
|
}, async (input) => {
|
|
60293
60560
|
return { content: [{ type: "text", text: JSON.stringify(await validateLeakedKey(input)) }] };
|
|
60294
60561
|
});
|
|
60562
|
+
server.registerTool("hackbot_arena_list", {
|
|
60563
|
+
title: "List Hackbot Arena benchmark labs",
|
|
60564
|
+
description: "BENCHMARK: list the 30 Hackbot Arena (NusaSec) dockerized web-security labs — a local benchmark of realistic vulnerability chains with deterministic flags. Returns id/name/difficulty/port/vuln_class per lab (evaluator view; no flags or solutions).",
|
|
60565
|
+
inputSchema: {}
|
|
60566
|
+
}, async () => {
|
|
60567
|
+
return { content: [{ type: "text", text: JSON.stringify(hackbotArenaList()) }] };
|
|
60568
|
+
});
|
|
60569
|
+
server.registerTool("hackbot_arena_brief", {
|
|
60570
|
+
title: "Attack-plan brief for one Hackbot Arena lab",
|
|
60571
|
+
description: "BENCHMARK: given a lab id (e.g. labs03), return the target URL, vuln class, stack, flag format, and the DERIVED Blitz Strike attack plan (vectors/detectors/chains/tools) to solve it. This is the agent brief — it never contains the flag, judge criteria, or reference solution.",
|
|
60572
|
+
inputSchema: {
|
|
60573
|
+
labId: string2().describe("Lab id (e.g. labs03, labs20)")
|
|
60574
|
+
}
|
|
60575
|
+
}, async ({ labId }) => {
|
|
60576
|
+
return { content: [{ type: "text", text: JSON.stringify(hackbotArenaBrief(labId)) }] };
|
|
60577
|
+
});
|
|
60578
|
+
server.registerTool("hackbot_arena_coverage", {
|
|
60579
|
+
title: "Blitz Strike coverage vs Hackbot Arena",
|
|
60580
|
+
description: "BENCHMARK: map all 30 Hackbot Arena labs against Blitz Strike's detector/vector coverage — which vuln classes Blitz Strike has deterministic tooling for, per lab, plus the derived chains.",
|
|
60581
|
+
inputSchema: {}
|
|
60582
|
+
}, async () => {
|
|
60583
|
+
return { content: [{ type: "text", text: JSON.stringify(hackbotArenaCoverage()) }] };
|
|
60584
|
+
});
|
|
60295
60585
|
server.registerTool("fofa_search", {
|
|
60296
60586
|
title: "FOFA asset search",
|
|
60297
60587
|
description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
|
|
@@ -60677,7 +60967,7 @@ ${statusLine}` : statusLine },
|
|
|
60677
60967
|
let src = code;
|
|
60678
60968
|
if (!src && path) {
|
|
60679
60969
|
try {
|
|
60680
|
-
src =
|
|
60970
|
+
src = readFileSync27(path, "utf8");
|
|
60681
60971
|
} catch {
|
|
60682
60972
|
return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
|
|
60683
60973
|
}
|
|
@@ -61295,7 +61585,7 @@ ${statusLine}` : statusLine },
|
|
|
61295
61585
|
description: "Route confusion (separate class from source→sink taint): detect attacker-controlled dispatch (call_user_func/forward/dispatch), dynamic method calls ($obj->$m()), dynamic include (LFI), batch/proxy forwarding without per-route auth re-check, and route matching without path normalization.",
|
|
61296
61586
|
inputSchema: { path: string2().describe("Source file path (PHP)") }
|
|
61297
61587
|
}, async ({ path }) => {
|
|
61298
|
-
const code =
|
|
61588
|
+
const code = readFileSync27(path, "utf8");
|
|
61299
61589
|
const findings = detectRouteConfusion(code, path);
|
|
61300
61590
|
return {
|
|
61301
61591
|
content: [{
|
|
@@ -61309,7 +61599,7 @@ ${statusLine}` : statusLine },
|
|
|
61309
61599
|
description: "Complex-bug detection beyond taint: deserialization→POP gadget chain (unserialize + magic method), type juggling (loose ==/!= vs hash/secret), mass assignment (extract/parse_str without a safe flag), prototype pollution (unsafe merge of request data), CRLF/header injection (user input into a header), path confusion (user input into a file path without canonicalization), SSRF (user-controlled URL with no host allowlist), XXE (user XML parsed without disabling entities), SSTI (user input into a template render with no sandbox), wrong-context sanitization (sanitize_text_field() into a SQL query), and missing authorization/nonce on WordPress entry points (wp_ajax_* / admin_post_* / register_rest_route without current_user_can, nonce verification, or permission_callback).",
|
|
61310
61600
|
inputSchema: { path: string2().describe("Source file path") }
|
|
61311
61601
|
}, async ({ path }) => {
|
|
61312
|
-
const code =
|
|
61602
|
+
const code = readFileSync27(path, "utf8");
|
|
61313
61603
|
const findings = detectComplexBugs(code, path);
|
|
61314
61604
|
return {
|
|
61315
61605
|
content: [{
|
|
@@ -61395,7 +61685,7 @@ ${statusLine}` : statusLine },
|
|
|
61395
61685
|
let lang = language;
|
|
61396
61686
|
if (path && !code) {
|
|
61397
61687
|
try {
|
|
61398
|
-
src =
|
|
61688
|
+
src = readFileSync27(path, "utf8");
|
|
61399
61689
|
} catch {
|
|
61400
61690
|
return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
|
|
61401
61691
|
}
|
|
@@ -61644,15 +61934,15 @@ async function serve() {
|
|
|
61644
61934
|
|
|
61645
61935
|
// src/index.ts
|
|
61646
61936
|
init_cli();
|
|
61647
|
-
import { readFileSync as
|
|
61648
|
-
import { join as
|
|
61649
|
-
import { fileURLToPath as
|
|
61650
|
-
var ROOT11 =
|
|
61937
|
+
import { readFileSync as readFileSync31, existsSync as existsSync15 } from "node:fs";
|
|
61938
|
+
import { join as join28 } from "node:path";
|
|
61939
|
+
import { fileURLToPath as fileURLToPath17 } from "node:url";
|
|
61940
|
+
var ROOT11 = join28(fileURLToPath17(new URL(".", import.meta.url)), "..");
|
|
61651
61941
|
function readVersion() {
|
|
61652
61942
|
try {
|
|
61653
|
-
const p =
|
|
61943
|
+
const p = join28(ROOT11, "package.json");
|
|
61654
61944
|
if (existsSync15(p))
|
|
61655
|
-
return JSON.parse(
|
|
61945
|
+
return JSON.parse(readFileSync31(p, "utf8")).version ?? "1.0.0";
|
|
61656
61946
|
} catch {}
|
|
61657
61947
|
return "1.0.0";
|
|
61658
61948
|
}
|