blitzstrike 2.4.21 → 2.4.23
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 +25 -0
- package/bench/corpus/cb-crlf-safe.md +9 -0
- package/bench/corpus/cb-crlf-vuln.md +9 -0
- package/bench/corpus/cb-deser-safe.md +9 -0
- package/bench/corpus/cb-deser-vuln.md +11 -0
- package/bench/corpus/cb-massassign-safe.md +9 -0
- package/bench/corpus/cb-massassign-vuln.md +9 -0
- package/bench/corpus/cb-pathconf-safe.md +10 -0
- package/bench/corpus/cb-pathconf-vuln.md +10 -0
- package/bench/corpus/cb-proto-safe.md +8 -0
- package/bench/corpus/cb-proto-vuln.md +8 -0
- package/bench/corpus/cb-ssrf-safe.md +9 -0
- package/bench/corpus/cb-ssrf-vuln.md +10 -0
- package/bench/corpus/cb-ssti-safe.md +8 -0
- package/bench/corpus/cb-ssti-vuln.md +8 -0
- package/bench/corpus/cb-typejuggle-safe.md +9 -0
- package/bench/corpus/cb-typejuggle-vuln.md +9 -0
- package/bench/corpus/cb-xxe-safe.md +11 -0
- package/bench/corpus/cb-xxe-vuln.md +10 -0
- package/bench/corpus/rc-batch-safe.md +9 -0
- package/bench/corpus/rc-batch-vuln.md +9 -0
- package/bench/corpus/rc-dispatch-safe.md +10 -0
- package/bench/corpus/rc-dispatch-vuln.md +9 -0
- package/bench/corpus/rc-include-safe.md +10 -0
- package/bench/corpus/rc-include-vuln.md +9 -0
- package/bench/corpus/rc-method-safe.md +10 -0
- package/bench/corpus/rc-method-vuln.md +10 -0
- package/bench/corpus/rc-normalize-safe.md +10 -0
- package/bench/corpus/rc-normalize-vuln.md +10 -0
- package/dist/index.js +192 -116
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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
|
+
## [2.4.23] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Precision — false-positive reduction + per-detector benchmark
|
|
11
|
+
|
|
12
|
+
- Fixed 6 false-positive patterns in the heuristic detectors (complex-bugs +
|
|
13
|
+
route-confusion): canonicalized include (`basename`/`realpath`), whitelist
|
|
14
|
+
lookup (`$allowed[$p]`), batch-forwarding WITH an auth re-check, anchored
|
|
15
|
+
validation regex (`preg_match('/^[a-z]+$/')`), constrained SSRF (fixed
|
|
16
|
+
`scheme://` host literal), and URL literals in the path-confusion detector.
|
|
17
|
+
- Extended the benchmark to measure per-detector precision/recall/F1 (taint +
|
|
18
|
+
complex_bugs + route_confusion) with 28 new labelled fixtures.
|
|
19
|
+
- Corpus now at 137 cases: **precision 1.0 / recall 1.0 / F1 1.0, 0 FP, 0 FN**
|
|
20
|
+
across all three detectors.
|
|
21
|
+
|
|
22
|
+
## [2.4.22] — 2026-09-14
|
|
23
|
+
|
|
24
|
+
### Added — on-disk report persistence (reports/ folder)
|
|
25
|
+
|
|
26
|
+
- `generate_report` now WRITES the HackerOne-grade markdown/JSON report to
|
|
27
|
+
`~/.blitzstrike/reports/<scope-slug>-<timestamp>.md` (override via
|
|
28
|
+
`BLITZSTRIKE_REPORT_DIR`) and returns the saved path — an engagement now
|
|
29
|
+
produces an on-disk deliverable, not just an in-memory string.
|
|
30
|
+
- `run_engagement` / `run_autonomous` also persist their inline report to the
|
|
31
|
+
same folder (report.report.saved).
|
|
32
|
+
|
|
8
33
|
## [2.4.21] — 2026-09-14
|
|
9
34
|
|
|
10
35
|
### Security — eliminate the last shell-interpolation sites
|
package/dist/index.js
CHANGED
|
@@ -15493,9 +15493,9 @@ async function checkMcpServers() {
|
|
|
15493
15493
|
var init_mcp_status = () => {};
|
|
15494
15494
|
|
|
15495
15495
|
// src/sync.ts
|
|
15496
|
-
import { mkdirSync as
|
|
15497
|
-
import { join as
|
|
15498
|
-
import { homedir as
|
|
15496
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, readFileSync as readFileSync18, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
|
|
15497
|
+
import { join as join17 } from "node:path";
|
|
15498
|
+
import { homedir as homedir7 } from "node:os";
|
|
15499
15499
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
15500
15500
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
15501
15501
|
function dirExists(p) {
|
|
@@ -15511,7 +15511,7 @@ function countFiles(dir) {
|
|
|
15511
15511
|
const walk = (d) => {
|
|
15512
15512
|
for (const e of readdirSync7(d, { withFileTypes: true })) {
|
|
15513
15513
|
if (e.isDirectory())
|
|
15514
|
-
walk(
|
|
15514
|
+
walk(join17(d, e.name));
|
|
15515
15515
|
else
|
|
15516
15516
|
n++;
|
|
15517
15517
|
}
|
|
@@ -15524,9 +15524,9 @@ function countFiles(dir) {
|
|
|
15524
15524
|
}
|
|
15525
15525
|
function syncData() {
|
|
15526
15526
|
if (!dirExists(DATA_ROOT2))
|
|
15527
|
-
|
|
15528
|
-
const payloadsPath =
|
|
15529
|
-
const templatesPath =
|
|
15527
|
+
mkdirSync6(DATA_ROOT2, { recursive: true });
|
|
15528
|
+
const payloadsPath = join17(DATA_ROOT2, "payloads");
|
|
15529
|
+
const templatesPath = join17(DATA_ROOT2, "templates");
|
|
15530
15530
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
15531
15531
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
15532
15532
|
if (hasPayloads && hasTemplates) {
|
|
@@ -15536,8 +15536,8 @@ function syncData() {
|
|
|
15536
15536
|
data_root: DATA_ROOT2
|
|
15537
15537
|
};
|
|
15538
15538
|
}
|
|
15539
|
-
const localPayloads =
|
|
15540
|
-
const localTemplates =
|
|
15539
|
+
const localPayloads = join17(ROOT7, "payloads");
|
|
15540
|
+
const localTemplates = join17(ROOT7, "templates");
|
|
15541
15541
|
let payloadsSynced = false;
|
|
15542
15542
|
let templatesSynced = false;
|
|
15543
15543
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -15550,7 +15550,7 @@ function syncData() {
|
|
|
15550
15550
|
}
|
|
15551
15551
|
const gitOk = spawnSync4("git", ["--version"]).status === 0;
|
|
15552
15552
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
15553
|
-
const tmp =
|
|
15553
|
+
const tmp = join17(DATA_ROOT2, ".tmp-clone");
|
|
15554
15554
|
if (dirExists(tmp))
|
|
15555
15555
|
rmSync4(tmp, { recursive: true, force: true });
|
|
15556
15556
|
const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -15558,11 +15558,11 @@ function syncData() {
|
|
|
15558
15558
|
const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
15559
15559
|
if (sparse.status === 0) {
|
|
15560
15560
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
15561
|
-
copyDir(
|
|
15561
|
+
copyDir(join17(tmp, "payloads"), payloadsPath);
|
|
15562
15562
|
payloadsSynced = true;
|
|
15563
15563
|
}
|
|
15564
15564
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
15565
|
-
copyDir(
|
|
15565
|
+
copyDir(join17(tmp, "templates"), templatesPath);
|
|
15566
15566
|
templatesSynced = true;
|
|
15567
15567
|
}
|
|
15568
15568
|
}
|
|
@@ -15582,20 +15582,20 @@ function copyDir(src, dst) {
|
|
|
15582
15582
|
}
|
|
15583
15583
|
}
|
|
15584
15584
|
function manualCopy(src, dst) {
|
|
15585
|
-
|
|
15585
|
+
mkdirSync6(dst, { recursive: true });
|
|
15586
15586
|
for (const e of readdirSync7(src, { withFileTypes: true })) {
|
|
15587
|
-
const s =
|
|
15588
|
-
const d =
|
|
15587
|
+
const s = join17(src, e.name);
|
|
15588
|
+
const d = join17(dst, e.name);
|
|
15589
15589
|
if (e.isDirectory())
|
|
15590
15590
|
manualCopy(s, d);
|
|
15591
15591
|
else
|
|
15592
|
-
|
|
15592
|
+
writeFileSync7(d, readFileSync18(s));
|
|
15593
15593
|
}
|
|
15594
15594
|
}
|
|
15595
15595
|
var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
15596
15596
|
var init_sync = __esm(() => {
|
|
15597
|
-
ROOT7 =
|
|
15598
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
15597
|
+
ROOT7 = join17(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
15598
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join17(homedir7(), ".blitzstrike", "data");
|
|
15599
15599
|
});
|
|
15600
15600
|
|
|
15601
15601
|
// src/update.ts
|
|
@@ -15641,14 +15641,14 @@ var init_update = __esm(() => {
|
|
|
15641
15641
|
|
|
15642
15642
|
// src/release.ts
|
|
15643
15643
|
import { readFileSync as readFileSync20, existsSync as existsSync10 } from "node:fs";
|
|
15644
|
-
import { join as
|
|
15644
|
+
import { join as join18 } from "node:path";
|
|
15645
15645
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
15646
15646
|
import { execSync as execSync2 } from "node:child_process";
|
|
15647
15647
|
function validateRelease() {
|
|
15648
15648
|
const checks = [];
|
|
15649
15649
|
checks.push({ name: "package.json version", ok: true, detail: VERSION });
|
|
15650
15650
|
try {
|
|
15651
|
-
const changelog = readFileSync20(
|
|
15651
|
+
const changelog = readFileSync20(join18(ROOT8, "CHANGELOG.md"), "utf8");
|
|
15652
15652
|
const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
|
|
15653
15653
|
const changelogVersion = m?.[1] ?? null;
|
|
15654
15654
|
checks.push({
|
|
@@ -15680,22 +15680,22 @@ function validateRelease() {
|
|
|
15680
15680
|
return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
|
|
15681
15681
|
}
|
|
15682
15682
|
function lockfilePresent() {
|
|
15683
|
-
return existsSync10(
|
|
15683
|
+
return existsSync10(join18(ROOT8, "package-lock.json")) || existsSync10(join18(ROOT8, "bun.lock")) || existsSync10(join18(ROOT8, "bun.lockb"));
|
|
15684
15684
|
}
|
|
15685
15685
|
var ROOT8;
|
|
15686
15686
|
var init_release = __esm(() => {
|
|
15687
15687
|
init_version();
|
|
15688
|
-
ROOT8 =
|
|
15688
|
+
ROOT8 = join18(fileURLToPath10(new URL(".", import.meta.url)), "..");
|
|
15689
15689
|
});
|
|
15690
15690
|
|
|
15691
15691
|
// src/dependency.ts
|
|
15692
15692
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
15693
|
-
import { join as
|
|
15693
|
+
import { join as join19 } from "node:path";
|
|
15694
15694
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
15695
15695
|
function checkDependencies() {
|
|
15696
15696
|
let pkg = {};
|
|
15697
15697
|
try {
|
|
15698
|
-
pkg = JSON.parse(readFileSync21(
|
|
15698
|
+
pkg = JSON.parse(readFileSync21(join19(ROOT9, "package.json"), "utf8"));
|
|
15699
15699
|
} catch {
|
|
15700
15700
|
return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
|
|
15701
15701
|
}
|
|
@@ -15718,13 +15718,13 @@ function checkDependencies() {
|
|
|
15718
15718
|
}
|
|
15719
15719
|
var ROOT9;
|
|
15720
15720
|
var init_dependency = __esm(() => {
|
|
15721
|
-
ROOT9 =
|
|
15721
|
+
ROOT9 = join19(fileURLToPath11(new URL(".", import.meta.url)), "..");
|
|
15722
15722
|
});
|
|
15723
15723
|
|
|
15724
15724
|
// src/cli.ts
|
|
15725
|
-
import { readFileSync as readFileSync22, existsSync as existsSync11, writeFileSync as
|
|
15726
|
-
import { join as
|
|
15727
|
-
import { homedir as
|
|
15725
|
+
import { readFileSync as readFileSync22, existsSync as existsSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "node:fs";
|
|
15726
|
+
import { join as join20, dirname as dirname2 } from "node:path";
|
|
15727
|
+
import { homedir as homedir8 } from "node:os";
|
|
15728
15728
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
15729
15729
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
15730
15730
|
function which(cmd) {
|
|
@@ -15768,7 +15768,7 @@ async function runDoctor() {
|
|
|
15768
15768
|
detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
|
|
15769
15769
|
fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
|
|
15770
15770
|
});
|
|
15771
|
-
const dataOk = existsSync11(
|
|
15771
|
+
const dataOk = existsSync11(join20(ROOT10, "chains.json")) && existsSync11(join20(ROOT10, "tools-catalog.json"));
|
|
15772
15772
|
issues.push({
|
|
15773
15773
|
name: "Data layers (chains + tools-catalog)",
|
|
15774
15774
|
status: dataOk ? "ok" : "fail",
|
|
@@ -15819,14 +15819,14 @@ BlitzStrike — ${verdict}`);
|
|
|
15819
15819
|
function resolveCommand() {
|
|
15820
15820
|
if (which("blitzstrike"))
|
|
15821
15821
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
15822
|
-
const src =
|
|
15822
|
+
const src = join20(ROOT10, "src", "index.ts");
|
|
15823
15823
|
if (which("bun") && existsSync11(src))
|
|
15824
15824
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
15825
15825
|
if (which("npx"))
|
|
15826
15826
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
15827
15827
|
if (which("bunx"))
|
|
15828
15828
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
15829
|
-
const dist =
|
|
15829
|
+
const dist = join20(ROOT10, "dist", "index.js");
|
|
15830
15830
|
if (existsSync11(dist))
|
|
15831
15831
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
15832
15832
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
@@ -15842,41 +15842,41 @@ function jsonMcpServersWrite(p) {
|
|
|
15842
15842
|
return (command, args) => {
|
|
15843
15843
|
const dir = dirname2(p);
|
|
15844
15844
|
if (!existsSync11(dir))
|
|
15845
|
-
|
|
15845
|
+
mkdirSync7(dir, { recursive: true });
|
|
15846
15846
|
const existing = readJson(p) ?? {};
|
|
15847
15847
|
existing.mcpServers = {
|
|
15848
15848
|
...existing.mcpServers ?? {},
|
|
15849
15849
|
blitzstrike: { command, args }
|
|
15850
15850
|
};
|
|
15851
|
-
|
|
15851
|
+
writeFileSync8(p, JSON.stringify(existing, null, 2));
|
|
15852
15852
|
};
|
|
15853
15853
|
}
|
|
15854
15854
|
function opencodeWrite(p) {
|
|
15855
15855
|
return (command, args) => {
|
|
15856
15856
|
const dir = dirname2(p);
|
|
15857
15857
|
if (!existsSync11(dir))
|
|
15858
|
-
|
|
15858
|
+
mkdirSync7(dir, { recursive: true });
|
|
15859
15859
|
const existing = readJson(p) ?? {};
|
|
15860
15860
|
existing.mcp = {
|
|
15861
15861
|
...existing.mcp ?? {},
|
|
15862
15862
|
blitzstrike: { type: "local", command: [command, ...args], enabled: true }
|
|
15863
15863
|
};
|
|
15864
|
-
|
|
15864
|
+
writeFileSync8(p, JSON.stringify(existing, null, 2));
|
|
15865
15865
|
copyOpenCodeAgents();
|
|
15866
15866
|
};
|
|
15867
15867
|
}
|
|
15868
15868
|
function copyOpenCodeAgents() {
|
|
15869
|
-
const srcDir =
|
|
15870
|
-
const dstDir =
|
|
15869
|
+
const srcDir = join20(ROOT10, "opencode-agents");
|
|
15870
|
+
const dstDir = join20(homedir8(), ".config", "opencode", "agents");
|
|
15871
15871
|
if (!existsSync11(srcDir))
|
|
15872
15872
|
return;
|
|
15873
15873
|
if (!existsSync11(dstDir))
|
|
15874
|
-
|
|
15874
|
+
mkdirSync7(dstDir, { recursive: true });
|
|
15875
15875
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
15876
|
-
const src =
|
|
15877
|
-
const dst =
|
|
15876
|
+
const src = join20(srcDir, f);
|
|
15877
|
+
const dst = join20(dstDir, f);
|
|
15878
15878
|
if (existsSync11(src)) {
|
|
15879
|
-
|
|
15879
|
+
writeFileSync8(dst, readFileSync22(src, "utf8"));
|
|
15880
15880
|
}
|
|
15881
15881
|
}
|
|
15882
15882
|
}
|
|
@@ -15884,7 +15884,7 @@ function codexWrite(p) {
|
|
|
15884
15884
|
return (command, args) => {
|
|
15885
15885
|
const dir = dirname2(p);
|
|
15886
15886
|
if (!existsSync11(dir))
|
|
15887
|
-
|
|
15887
|
+
mkdirSync7(dir, { recursive: true });
|
|
15888
15888
|
let existing = existsSync11(p) ? readFileSync22(p, "utf8") : "";
|
|
15889
15889
|
if (!existing.trimEnd().endsWith(`
|
|
15890
15890
|
`))
|
|
@@ -15896,14 +15896,14 @@ function codexWrite(p) {
|
|
|
15896
15896
|
command = "${command}"
|
|
15897
15897
|
args = ${JSON.stringify(args)}
|
|
15898
15898
|
`;
|
|
15899
|
-
|
|
15899
|
+
writeFileSync8(p, existing);
|
|
15900
15900
|
};
|
|
15901
15901
|
}
|
|
15902
15902
|
function hermesWrite(p) {
|
|
15903
15903
|
return (command, args) => {
|
|
15904
15904
|
const dir = dirname2(p);
|
|
15905
15905
|
if (!existsSync11(dir))
|
|
15906
|
-
|
|
15906
|
+
mkdirSync7(dir, { recursive: true });
|
|
15907
15907
|
let existing = existsSync11(p) ? readFileSync22(p, "utf8") : "";
|
|
15908
15908
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
15909
15909
|
if (!existing.trimEnd().endsWith(`
|
|
@@ -15925,80 +15925,80 @@ mcp_servers:
|
|
|
15925
15925
|
args: ${JSON.stringify(args)}
|
|
15926
15926
|
`;
|
|
15927
15927
|
}
|
|
15928
|
-
|
|
15928
|
+
writeFileSync8(p, existing);
|
|
15929
15929
|
};
|
|
15930
15930
|
}
|
|
15931
15931
|
function detectAgents() {
|
|
15932
|
-
const home =
|
|
15932
|
+
const home = homedir8();
|
|
15933
15933
|
const agents = [];
|
|
15934
|
-
const claudeJson =
|
|
15934
|
+
const claudeJson = join20(home, ".claude.json");
|
|
15935
15935
|
agents.push({
|
|
15936
15936
|
name: "Claude Code",
|
|
15937
15937
|
detectPath: claudeJson,
|
|
15938
15938
|
installed: () => existsSync11(claudeJson) || which("claude"),
|
|
15939
15939
|
write: jsonMcpServersWrite(claudeJson)
|
|
15940
15940
|
});
|
|
15941
|
-
const claudeDesktop =
|
|
15941
|
+
const claudeDesktop = join20(home, ".config", "Claude", "claude_desktop_config.json");
|
|
15942
15942
|
agents.push({
|
|
15943
15943
|
name: "Claude Desktop",
|
|
15944
15944
|
detectPath: claudeDesktop,
|
|
15945
15945
|
installed: () => existsSync11(claudeDesktop),
|
|
15946
15946
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
15947
15947
|
});
|
|
15948
|
-
const cursor =
|
|
15948
|
+
const cursor = join20(home, ".cursor", "mcp.json");
|
|
15949
15949
|
agents.push({
|
|
15950
15950
|
name: "Cursor",
|
|
15951
15951
|
detectPath: cursor,
|
|
15952
|
-
installed: () => existsSync11(
|
|
15952
|
+
installed: () => existsSync11(join20(home, ".cursor")) || which("cursor"),
|
|
15953
15953
|
write: jsonMcpServersWrite(cursor)
|
|
15954
15954
|
});
|
|
15955
|
-
const opencode =
|
|
15955
|
+
const opencode = join20(home, ".config", "opencode", "opencode.jsonc");
|
|
15956
15956
|
agents.push({
|
|
15957
15957
|
name: "OpenCode",
|
|
15958
15958
|
detectPath: opencode,
|
|
15959
|
-
installed: () => existsSync11(
|
|
15959
|
+
installed: () => existsSync11(join20(home, ".config", "opencode")) || which("opencode"),
|
|
15960
15960
|
write: opencodeWrite(opencode)
|
|
15961
15961
|
});
|
|
15962
|
-
const codex =
|
|
15962
|
+
const codex = join20(home, ".codex", "config.toml");
|
|
15963
15963
|
agents.push({
|
|
15964
15964
|
name: "Codex",
|
|
15965
15965
|
detectPath: codex,
|
|
15966
|
-
installed: () => existsSync11(
|
|
15966
|
+
installed: () => existsSync11(join20(home, ".codex")) || which("codex"),
|
|
15967
15967
|
write: codexWrite(codex)
|
|
15968
15968
|
});
|
|
15969
|
-
const hermes =
|
|
15969
|
+
const hermes = join20(home, ".hermes", "config.yaml");
|
|
15970
15970
|
agents.push({
|
|
15971
15971
|
name: "Hermes",
|
|
15972
15972
|
detectPath: hermes,
|
|
15973
|
-
installed: () => existsSync11(
|
|
15973
|
+
installed: () => existsSync11(join20(home, ".hermes")) || which("hermes"),
|
|
15974
15974
|
write: hermesWrite(hermes)
|
|
15975
15975
|
});
|
|
15976
|
-
const gemini =
|
|
15976
|
+
const gemini = join20(home, ".gemini", "settings.json");
|
|
15977
15977
|
agents.push({
|
|
15978
15978
|
name: "Gemini CLI",
|
|
15979
15979
|
detectPath: gemini,
|
|
15980
|
-
installed: () => existsSync11(
|
|
15980
|
+
installed: () => existsSync11(join20(home, ".gemini")) || which("gemini"),
|
|
15981
15981
|
write: jsonMcpServersWrite(gemini)
|
|
15982
15982
|
});
|
|
15983
|
-
const windsurf =
|
|
15983
|
+
const windsurf = join20(home, ".codeium", "windsurf", "mcp_config.json");
|
|
15984
15984
|
agents.push({
|
|
15985
15985
|
name: "Windsurf",
|
|
15986
15986
|
detectPath: windsurf,
|
|
15987
|
-
installed: () => existsSync11(
|
|
15987
|
+
installed: () => existsSync11(join20(home, ".codeium")) || which("windsurf"),
|
|
15988
15988
|
write: jsonMcpServersWrite(windsurf)
|
|
15989
15989
|
});
|
|
15990
|
-
const copilot =
|
|
15990
|
+
const copilot = join20(home, ".copilot", "mcp.json");
|
|
15991
15991
|
agents.push({
|
|
15992
15992
|
name: "Copilot",
|
|
15993
15993
|
detectPath: copilot,
|
|
15994
|
-
installed: () => existsSync11(
|
|
15994
|
+
installed: () => existsSync11(join20(home, ".copilot")),
|
|
15995
15995
|
write: jsonMcpServersWrite(copilot)
|
|
15996
15996
|
});
|
|
15997
|
-
const cline =
|
|
15997
|
+
const cline = join20(home, ".cline", "mcp_settings.json");
|
|
15998
15998
|
agents.push({
|
|
15999
15999
|
name: "Cline",
|
|
16000
16000
|
detectPath: cline,
|
|
16001
|
-
installed: () => existsSync11(
|
|
16001
|
+
installed: () => existsSync11(join20(home, ".cline")),
|
|
16002
16002
|
write: jsonMcpServersWrite(cline)
|
|
16003
16003
|
});
|
|
16004
16004
|
return agents;
|
|
@@ -16116,7 +16116,7 @@ var init_cli = __esm(() => {
|
|
|
16116
16116
|
init_release();
|
|
16117
16117
|
init_dependency();
|
|
16118
16118
|
init_mcp_status();
|
|
16119
|
-
ROOT10 =
|
|
16119
|
+
ROOT10 = join20(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
16120
16120
|
});
|
|
16121
16121
|
|
|
16122
16122
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -30838,7 +30838,7 @@ async function nvdLookup(cveId) {
|
|
|
30838
30838
|
// src/orchestrator.ts
|
|
30839
30839
|
init_scanner();
|
|
30840
30840
|
import { readFileSync as readFileSync8, existsSync as existsSync6, statSync as statSync2 } from "node:fs";
|
|
30841
|
-
import { join as
|
|
30841
|
+
import { join as join10 } from "node:path";
|
|
30842
30842
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
30843
30843
|
|
|
30844
30844
|
// src/memory.ts
|
|
@@ -33115,6 +33115,9 @@ function cvssAssess(input) {
|
|
|
33115
33115
|
}
|
|
33116
33116
|
|
|
33117
33117
|
// src/report.ts
|
|
33118
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
33119
|
+
import { join as join9 } from "node:path";
|
|
33120
|
+
import { homedir as homedir6 } from "node:os";
|
|
33118
33121
|
var SEVERITY_METRICS = {
|
|
33119
33122
|
critical: { AV: "N", AC: "L", PR: "N", UI: "N", S: "U", C: "H", I: "H", A: "H" },
|
|
33120
33123
|
high: { AV: "N", AC: "L", PR: "L", UI: "N", S: "U", C: "H", I: "H", A: "N" },
|
|
@@ -33317,6 +33320,21 @@ function reportJson(findings, opts = {}) {
|
|
|
33317
33320
|
};
|
|
33318
33321
|
return JSON.stringify(payload, null, 2);
|
|
33319
33322
|
}
|
|
33323
|
+
var REPORT_DIR = process.env.BLITZSTRIKE_REPORT_DIR ?? join9(homedir6(), ".blitzstrike", "reports");
|
|
33324
|
+
function slugify2(s) {
|
|
33325
|
+
const slug = s.toLowerCase().replace(/^https?:\/\//, "").replace(/[^a-z0-9.-]+/g, "-").replace(/^-+|-+$/g, "").replace(/\./g, "_").slice(0, 60);
|
|
33326
|
+
return slug || "engagement";
|
|
33327
|
+
}
|
|
33328
|
+
function saveReport(content, opts = {}) {
|
|
33329
|
+
mkdirSync4(REPORT_DIR, { recursive: true });
|
|
33330
|
+
const slug = slugify2(opts.scope ?? opts.title ?? "engagement");
|
|
33331
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
33332
|
+
const ext = opts.format === "json" ? "json" : "md";
|
|
33333
|
+
const filename = `${slug}-${ts}.${ext}`;
|
|
33334
|
+
const path = join9(REPORT_DIR, filename);
|
|
33335
|
+
writeFileSync2(path, content, "utf8");
|
|
33336
|
+
return { path, dir: REPORT_DIR, filename };
|
|
33337
|
+
}
|
|
33320
33338
|
|
|
33321
33339
|
// src/sarif.ts
|
|
33322
33340
|
init_version();
|
|
@@ -33910,8 +33928,8 @@ function loadChains() {
|
|
|
33910
33928
|
if (_chainsCache)
|
|
33911
33929
|
return _chainsCache;
|
|
33912
33930
|
try {
|
|
33913
|
-
const dir =
|
|
33914
|
-
const path =
|
|
33931
|
+
const dir = join10(fileURLToPath5(new URL(".", import.meta.url)), "..");
|
|
33932
|
+
const path = join10(dir, "chains.json");
|
|
33915
33933
|
const raw = JSON.parse(readFileSync8(path, "utf8"));
|
|
33916
33934
|
_chainsCache = raw.chains ?? [];
|
|
33917
33935
|
return _chainsCache;
|
|
@@ -34136,7 +34154,8 @@ function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000,
|
|
|
34136
34154
|
summary: JSON.parse(reportJsonText).summary ?? null,
|
|
34137
34155
|
markdown: reportMarkdownText,
|
|
34138
34156
|
json: reportJsonText,
|
|
34139
|
-
sarif: sarifDoc
|
|
34157
|
+
sarif: sarifDoc,
|
|
34158
|
+
saved: saveReport(reportMarkdownText, { title: reportTitle, scope: target, version: VERSION, format: "markdown" }).path
|
|
34140
34159
|
};
|
|
34141
34160
|
report.status = "DETECTED";
|
|
34142
34161
|
report.note = "Detection complete — verification is the NEXT mandatory step (do not stop here). " + "Every finding is a HYPOTHESIS until verified: strike_verify (HTTP reflection, marker + negative control), " + "verify_file_read (CWE-22 / path traversal), or drive_devtools (DOM/JS precision). " + "Verify each finding NOW, then generate_report. Never deliver a report with unverified findings.";
|
|
@@ -34689,8 +34708,8 @@ var CHAIN_ID_TO_ESCALATION = {
|
|
|
34689
34708
|
};
|
|
34690
34709
|
function loadChainLinks() {
|
|
34691
34710
|
try {
|
|
34692
|
-
const dir =
|
|
34693
|
-
const data = JSON.parse(readFileSync8(
|
|
34711
|
+
const dir = join10(fileURLToPath5(new URL(".", import.meta.url)), "..");
|
|
34712
|
+
const data = JSON.parse(readFileSync8(join10(dir, "intelligence", "chain_links.json"), "utf8"));
|
|
34694
34713
|
return data.links ?? [];
|
|
34695
34714
|
} catch {
|
|
34696
34715
|
return [];
|
|
@@ -34846,14 +34865,14 @@ function compactState(phase, findings) {
|
|
|
34846
34865
|
}
|
|
34847
34866
|
|
|
34848
34867
|
// src/tasks.ts
|
|
34849
|
-
import { mkdirSync as
|
|
34850
|
-
import { join as
|
|
34868
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
|
|
34869
|
+
import { join as join11 } from "node:path";
|
|
34851
34870
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
34852
34871
|
function tasksDir() {
|
|
34853
|
-
return
|
|
34872
|
+
return join11(fileURLToPath6(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
|
|
34854
34873
|
}
|
|
34855
34874
|
function taskPath(id) {
|
|
34856
|
-
return
|
|
34875
|
+
return join11(tasksDir(), `${id}.json`);
|
|
34857
34876
|
}
|
|
34858
34877
|
function load(id) {
|
|
34859
34878
|
try {
|
|
@@ -34863,8 +34882,8 @@ function load(id) {
|
|
|
34863
34882
|
}
|
|
34864
34883
|
}
|
|
34865
34884
|
function save(t) {
|
|
34866
|
-
|
|
34867
|
-
|
|
34885
|
+
mkdirSync5(tasksDir(), { recursive: true });
|
|
34886
|
+
writeFileSync3(taskPath(t.task_id), JSON.stringify(t, null, 2));
|
|
34868
34887
|
}
|
|
34869
34888
|
function taskStart(id, objective, plan) {
|
|
34870
34889
|
const now = Date.now();
|
|
@@ -35161,8 +35180,8 @@ function engagementStatus() {
|
|
|
35161
35180
|
|
|
35162
35181
|
// src/incremental.ts
|
|
35163
35182
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
35164
|
-
import { writeFileSync as
|
|
35165
|
-
import { join as
|
|
35183
|
+
import { writeFileSync as writeFileSync4, mkdtempSync, rmSync } from "node:fs";
|
|
35184
|
+
import { join as join12 } from "node:path";
|
|
35166
35185
|
import { tmpdir } from "node:os";
|
|
35167
35186
|
|
|
35168
35187
|
// src/eagle2.ts
|
|
@@ -54677,9 +54696,9 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
54677
54696
|
continue;
|
|
54678
54697
|
const lang = detectLanguage(file)?.language;
|
|
54679
54698
|
if (lang === "php") {
|
|
54680
|
-
const dir = mkdtempSync(
|
|
54681
|
-
const tmp =
|
|
54682
|
-
|
|
54699
|
+
const dir = mkdtempSync(join12(tmpdir(), "blitz-incr-"));
|
|
54700
|
+
const tmp = join12(dir, "changed.php");
|
|
54701
|
+
writeFileSync4(tmp, code);
|
|
54683
54702
|
try {
|
|
54684
54703
|
const r = analyzeDataFlow2(tmp);
|
|
54685
54704
|
for (const f of r.findings)
|
|
@@ -55091,16 +55110,16 @@ function taintTree(root, maxFiles = 2000) {
|
|
|
55091
55110
|
}
|
|
55092
55111
|
|
|
55093
55112
|
// src/differential.ts
|
|
55094
|
-
import { writeFileSync as
|
|
55095
|
-
import { join as
|
|
55113
|
+
import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14 } from "node:fs";
|
|
55114
|
+
import { join as join13 } from "node:path";
|
|
55096
55115
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
55097
55116
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
55098
55117
|
init_scanner();
|
|
55099
55118
|
function runEngine(code, language, file) {
|
|
55100
55119
|
if (language === "php") {
|
|
55101
|
-
const dir = mkdtempSync2(
|
|
55102
|
-
const tmp =
|
|
55103
|
-
|
|
55120
|
+
const dir = mkdtempSync2(join13(tmpdir2(), "blitz-diff-"));
|
|
55121
|
+
const tmp = join13(dir, "old.php");
|
|
55122
|
+
writeFileSync5(tmp, code);
|
|
55104
55123
|
try {
|
|
55105
55124
|
const r = analyzeDataFlow2(tmp);
|
|
55106
55125
|
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
|
|
@@ -55285,15 +55304,15 @@ function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
55285
55304
|
|
|
55286
55305
|
// src/frameworks.ts
|
|
55287
55306
|
import { readFileSync as readFileSync15, existsSync as existsSync7 } from "node:fs";
|
|
55288
|
-
import { join as
|
|
55307
|
+
import { join as join14 } from "node:path";
|
|
55289
55308
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
55290
|
-
var ROOT4 =
|
|
55291
|
-
var INTEL2 =
|
|
55309
|
+
var ROOT4 = join14(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
55310
|
+
var INTEL2 = join14(ROOT4, "intelligence");
|
|
55292
55311
|
var cache = null;
|
|
55293
55312
|
function load2() {
|
|
55294
55313
|
if (cache)
|
|
55295
55314
|
return cache;
|
|
55296
|
-
const p =
|
|
55315
|
+
const p = join14(INTEL2, "frameworks.json");
|
|
55297
55316
|
if (!existsSync7(p))
|
|
55298
55317
|
return null;
|
|
55299
55318
|
try {
|
|
@@ -55611,6 +55630,8 @@ var DYNAMIC_METHOD_RE = /(->|::)\s*\$[A-Za-z_]/g;
|
|
|
55611
55630
|
var BATCH_LOOP_RE = /\bforeach\s*\([^)]*(requests|batch|sub_requests|operations)\b/gi;
|
|
55612
55631
|
var ROUTE_MATCH_RE = /\b(preg_match|preg_match_all|strpos|stripos|str_contains)\s*\(/i;
|
|
55613
55632
|
var NORMALIZERS = /\b(strtolower|strtoupper|rtrim|urldecode|rawurldecode|parse_url|normalize|canonical)\b/i;
|
|
55633
|
+
var WHITELIST_LOOKUP = /\$[A-Za-z_]\w*\s*\[\s*\$[A-Za-z_]\w*\s*\]/;
|
|
55634
|
+
var CANONICALIZE = /\b(basename|realpath|normalize|canonical|resolve|toRealPath)\s*\(/i;
|
|
55614
55635
|
function detectDynamicDispatch(code, file, out) {
|
|
55615
55636
|
const ls = lines(code);
|
|
55616
55637
|
DYNAMIC_DISPATCH_RE.lastIndex = 0;
|
|
@@ -55622,6 +55643,8 @@ function detectDynamicDispatch(code, file, out) {
|
|
|
55622
55643
|
`);
|
|
55623
55644
|
if (!userControlled(l) && !userControlled(scope))
|
|
55624
55645
|
continue;
|
|
55646
|
+
if (WHITELIST_LOOKUP.test(l) || WHITELIST_LOOKUP.test(scope))
|
|
55647
|
+
continue;
|
|
55625
55648
|
out.push({
|
|
55626
55649
|
file,
|
|
55627
55650
|
line: ln,
|
|
@@ -55644,6 +55667,8 @@ function detectDynamicMethod(code, file, out) {
|
|
|
55644
55667
|
`);
|
|
55645
55668
|
if (!userControlled(l) && !userControlled(scope))
|
|
55646
55669
|
continue;
|
|
55670
|
+
if (WHITELIST_LOOKUP.test(l) || WHITELIST_LOOKUP.test(scope))
|
|
55671
|
+
continue;
|
|
55647
55672
|
out.push({
|
|
55648
55673
|
file,
|
|
55649
55674
|
line: ln,
|
|
@@ -55666,6 +55691,11 @@ function detectDynamicInclude(code, file, out) {
|
|
|
55666
55691
|
`);
|
|
55667
55692
|
if (!userControlled(l) && !userControlled(scope))
|
|
55668
55693
|
continue;
|
|
55694
|
+
if (CANONICALIZE.test(scope + `
|
|
55695
|
+
` + l))
|
|
55696
|
+
continue;
|
|
55697
|
+
if (WHITELIST_LOOKUP.test(l) || WHITELIST_LOOKUP.test(scope))
|
|
55698
|
+
continue;
|
|
55669
55699
|
out.push({
|
|
55670
55700
|
file,
|
|
55671
55701
|
line: ln,
|
|
@@ -55688,14 +55718,16 @@ function detectBatchForwarding(code, file, out) {
|
|
|
55688
55718
|
if (!forwards)
|
|
55689
55719
|
continue;
|
|
55690
55720
|
const rechecks = /\b(current_user_can|permission_callback|check_ajax_referer|authorize|authorise|is_user_logged_in)\b/i.test(body);
|
|
55721
|
+
if (rechecks)
|
|
55722
|
+
continue;
|
|
55691
55723
|
out.push({
|
|
55692
55724
|
file,
|
|
55693
55725
|
line: ln,
|
|
55694
55726
|
type: "batch_forwarding",
|
|
55695
55727
|
category: "Route confusion (batch/proxy forwarding)",
|
|
55696
|
-
severity:
|
|
55728
|
+
severity: "high",
|
|
55697
55729
|
evidence: lines(code)[ln - 1]?.trim() ?? "",
|
|
55698
|
-
detail:
|
|
55730
|
+
detail: "Batch/proxy loop forwards to inner handlers with no per-route authorization re-check — an attacker may reach a protected handler."
|
|
55699
55731
|
});
|
|
55700
55732
|
}
|
|
55701
55733
|
}
|
|
@@ -55711,6 +55743,8 @@ function detectRouteNormalization(code, file, out) {
|
|
|
55711
55743
|
continue;
|
|
55712
55744
|
if (NORMALIZERS.test(l) || NORMALIZERS.test(scope))
|
|
55713
55745
|
continue;
|
|
55746
|
+
if (/preg_match\s*\(\s*["'][^"']*\^[^"']*\$[^"']*["']/.test(l))
|
|
55747
|
+
continue;
|
|
55714
55748
|
out.push({
|
|
55715
55749
|
file,
|
|
55716
55750
|
line: i + 1,
|
|
@@ -55908,6 +55942,12 @@ function detectPathConfusion(code, file, out) {
|
|
|
55908
55942
|
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
55909
55943
|
continue;
|
|
55910
55944
|
if (/\b(basename|realpath|canonicalPath|getCanonicalPath|normalize|resolve|toRealPath)\s*\(/i.test(scope + `
|
|
55945
|
+
` + l))
|
|
55946
|
+
continue;
|
|
55947
|
+
if (/\$[A-Za-z_]\w*\s*\[\s*\$[A-Za-z_]\w*\s*\]/.test(scope + `
|
|
55948
|
+
` + l))
|
|
55949
|
+
continue;
|
|
55950
|
+
if (/["'][^"']*:\/\/[^"']*["']/.test(scope + `
|
|
55911
55951
|
` + l))
|
|
55912
55952
|
continue;
|
|
55913
55953
|
out.push({
|
|
@@ -55933,6 +55973,9 @@ function detectSsrF(code, file, out) {
|
|
|
55933
55973
|
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
55934
55974
|
continue;
|
|
55935
55975
|
if (/(allowlist|whitelist|in_array|preg_match|parse_url|startsWith\(["']https?|endsWith\(["']|basename|realpath|normalize)/i.test(scope + `
|
|
55976
|
+
` + l))
|
|
55977
|
+
continue;
|
|
55978
|
+
if (/["'][^"']*:\/\/[^"']*["']/.test(scope + `
|
|
55936
55979
|
` + l))
|
|
55937
55980
|
continue;
|
|
55938
55981
|
out.push({
|
|
@@ -56050,12 +56093,12 @@ function nextSteps(ctx) {
|
|
|
56050
56093
|
}
|
|
56051
56094
|
|
|
56052
56095
|
// src/benchmark.ts
|
|
56053
|
-
import { readFileSync as readFileSync16, readdirSync as readdirSync5, writeFileSync as
|
|
56054
|
-
import { join as
|
|
56096
|
+
import { readFileSync as readFileSync16, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3 } from "node:fs";
|
|
56097
|
+
import { join as join15 } from "node:path";
|
|
56055
56098
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
56056
56099
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
56057
|
-
var ROOT5 =
|
|
56058
|
-
var CORPUS =
|
|
56100
|
+
var ROOT5 = join15(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
56101
|
+
var CORPUS = join15(ROOT5, "bench", "corpus");
|
|
56059
56102
|
function parseCorpusFile(path) {
|
|
56060
56103
|
let text;
|
|
56061
56104
|
try {
|
|
@@ -56069,11 +56112,12 @@ function parseCorpusFile(path) {
|
|
|
56069
56112
|
let language = "php";
|
|
56070
56113
|
let vulnerable = true;
|
|
56071
56114
|
let sinkType;
|
|
56115
|
+
let detector;
|
|
56072
56116
|
let code = "";
|
|
56073
56117
|
let inCode = false;
|
|
56074
56118
|
let codeLang = "";
|
|
56075
56119
|
for (const line of lines) {
|
|
56076
|
-
const m = line.match(/^\s*-\s*(id|language|vulnerable|sink_type)\s*:\s*(.+)$/i);
|
|
56120
|
+
const m = line.match(/^\s*-\s*(id|language|vulnerable|sink_type|detector)\s*:\s*(.+)$/i);
|
|
56077
56121
|
if (!inCode && m) {
|
|
56078
56122
|
const key = m[1].toLowerCase();
|
|
56079
56123
|
const val = m[2].trim();
|
|
@@ -56085,6 +56129,8 @@ function parseCorpusFile(path) {
|
|
|
56085
56129
|
vulnerable = val === "true";
|
|
56086
56130
|
else if (key === "sink_type")
|
|
56087
56131
|
sinkType = val;
|
|
56132
|
+
else if (key === "detector")
|
|
56133
|
+
detector = val;
|
|
56088
56134
|
continue;
|
|
56089
56135
|
}
|
|
56090
56136
|
const start = line.match(/^```(\w*)/);
|
|
@@ -56101,7 +56147,7 @@ function parseCorpusFile(path) {
|
|
|
56101
56147
|
}
|
|
56102
56148
|
if (!id)
|
|
56103
56149
|
id = path.split("/").pop().replace(/\.md$/, "");
|
|
56104
|
-
return { id, language: language || codeLang || "php", vulnerable, sink_type: sinkType, code: code.trim() };
|
|
56150
|
+
return { id, language: language || codeLang || "php", vulnerable, sink_type: sinkType, detector, code: code.trim() };
|
|
56105
56151
|
}
|
|
56106
56152
|
function loadCorpus() {
|
|
56107
56153
|
let entries = [];
|
|
@@ -56109,7 +56155,7 @@ function loadCorpus() {
|
|
|
56109
56155
|
for (const f of readdirSync5(CORPUS)) {
|
|
56110
56156
|
if (!f.endsWith(".md"))
|
|
56111
56157
|
continue;
|
|
56112
|
-
const e = parseCorpusFile(
|
|
56158
|
+
const e = parseCorpusFile(join15(CORPUS, f));
|
|
56113
56159
|
if (e)
|
|
56114
56160
|
entries.push(e);
|
|
56115
56161
|
}
|
|
@@ -56119,10 +56165,22 @@ function loadCorpus() {
|
|
|
56119
56165
|
return entries;
|
|
56120
56166
|
}
|
|
56121
56167
|
function detect(entry) {
|
|
56168
|
+
if (entry.detector === "route_confusion") {
|
|
56169
|
+
const f = detectRouteConfusion(entry.code, "bench." + entry.language);
|
|
56170
|
+
if (f.length > 0)
|
|
56171
|
+
return { detected: true, sinkKind: f[0].type };
|
|
56172
|
+
return { detected: false };
|
|
56173
|
+
}
|
|
56174
|
+
if (entry.detector === "complex_bugs") {
|
|
56175
|
+
const f = detectComplexBugs(entry.code, "bench." + entry.language);
|
|
56176
|
+
if (f.length > 0)
|
|
56177
|
+
return { detected: true, sinkKind: f[0].type };
|
|
56178
|
+
return { detected: false };
|
|
56179
|
+
}
|
|
56122
56180
|
const lang = entry.language;
|
|
56123
56181
|
if (lang === "php") {
|
|
56124
|
-
const tmp =
|
|
56125
|
-
|
|
56182
|
+
const tmp = join15(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
56183
|
+
writeFileSync6(tmp, entry.code);
|
|
56126
56184
|
const r = analyzeDataFlow2(tmp);
|
|
56127
56185
|
rmSync3(tmp, { force: true });
|
|
56128
56186
|
if (r.findings.length > 0)
|
|
@@ -56150,6 +56208,19 @@ function runBenchmark() {
|
|
|
56150
56208
|
const detectionRate = tp + fn > 0 ? tp / (tp + fn) : 0;
|
|
56151
56209
|
const falsePositiveRate = fp + tn > 0 ? fp / (fp + tn) : 0;
|
|
56152
56210
|
const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
|
|
56211
|
+
const byDetector = {};
|
|
56212
|
+
for (const r of results) {
|
|
56213
|
+
const d = r.entry.detector ?? "taint";
|
|
56214
|
+
byDetector[d] = byDetector[d] ?? { tp: 0, tn: 0, fp: 0, fn: 0, total: 0 };
|
|
56215
|
+
byDetector[d][r.outcome] += 1;
|
|
56216
|
+
byDetector[d].total += 1;
|
|
56217
|
+
}
|
|
56218
|
+
const detectorMetrics = Object.fromEntries(Object.entries(byDetector).map(([d, m]) => {
|
|
56219
|
+
const p = m.tp + m.fp > 0 ? m.tp / (m.tp + m.fp) : 0;
|
|
56220
|
+
const rec = m.tp + m.fn > 0 ? m.tp / (m.tp + m.fn) : 0;
|
|
56221
|
+
const f1 = p + rec > 0 ? 2 * p * rec / (p + rec) : 0;
|
|
56222
|
+
return [d, { ...m, precision: Number(p.toFixed(3)), recall: Number(rec.toFixed(3)), f1: Number(f1.toFixed(3)) }];
|
|
56223
|
+
}));
|
|
56153
56224
|
return {
|
|
56154
56225
|
total_cases: total,
|
|
56155
56226
|
tp,
|
|
@@ -56159,21 +56230,22 @@ function runBenchmark() {
|
|
|
56159
56230
|
detection_rate: Number(detectionRate.toFixed(3)),
|
|
56160
56231
|
false_positive_rate: Number(falsePositiveRate.toFixed(3)),
|
|
56161
56232
|
precision: Number(precision.toFixed(3)),
|
|
56162
|
-
|
|
56233
|
+
by_detector: detectorMetrics,
|
|
56234
|
+
results: results.map((r) => ({ id: r.entry.id, language: r.entry.language, detector: r.entry.detector ?? "taint", outcome: r.outcome, sink: r.sink_kind }))
|
|
56163
56235
|
};
|
|
56164
56236
|
}
|
|
56165
56237
|
|
|
56166
56238
|
// src/enterprise-benchmark.ts
|
|
56167
56239
|
import { existsSync as existsSync9, readFileSync as readFileSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
56168
|
-
import { join as
|
|
56240
|
+
import { join as join16, resolve } from "node:path";
|
|
56169
56241
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
56170
56242
|
var ENTERPRISE_FIXTURES = [
|
|
56171
|
-
{ id: "php-laravel", dir:
|
|
56172
|
-
{ id: "js-express", dir:
|
|
56173
|
-
{ id: "py-django", dir:
|
|
56174
|
-
{ id: "java-spring", dir:
|
|
56175
|
-
{ id: "wp-batch-route", dir:
|
|
56176
|
-
{ id: "php-complex", dir:
|
|
56243
|
+
{ id: "php-laravel", dir: join16(ROOT6, "php-laravel"), expected_vulns: 4 },
|
|
56244
|
+
{ id: "js-express", dir: join16(ROOT6, "js-express"), expected_vulns: 2 },
|
|
56245
|
+
{ id: "py-django", dir: join16(ROOT6, "py-django"), expected_vulns: 2 },
|
|
56246
|
+
{ id: "java-spring", dir: join16(ROOT6, "java-spring"), expected_vulns: 2 },
|
|
56247
|
+
{ id: "wp-batch-route", dir: join16(ROOT6, "wp-batch-route"), expected_vulns: 1 },
|
|
56248
|
+
{ id: "php-complex", dir: join16(ROOT6, "php-complex"), expected_vulns: 4 }
|
|
56177
56249
|
];
|
|
56178
56250
|
function sourceFiles(root) {
|
|
56179
56251
|
const out = [];
|
|
@@ -56185,7 +56257,7 @@ function sourceFiles(root) {
|
|
|
56185
56257
|
return;
|
|
56186
56258
|
}
|
|
56187
56259
|
for (const e of entries) {
|
|
56188
|
-
const p =
|
|
56260
|
+
const p = join16(dir, e);
|
|
56189
56261
|
let st;
|
|
56190
56262
|
try {
|
|
56191
56263
|
st = statSync3(p);
|
|
@@ -57788,9 +57860,13 @@ ${statusLine}` : statusLine },
|
|
|
57788
57860
|
} catch {
|
|
57789
57861
|
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
|
|
57790
57862
|
}
|
|
57863
|
+
const fmt = format ?? "markdown";
|
|
57791
57864
|
const opts = { title, scope, version: VERSION };
|
|
57792
|
-
const out =
|
|
57793
|
-
|
|
57865
|
+
const out = fmt === "json" ? reportJson(list, opts) : reportMarkdown(list, opts);
|
|
57866
|
+
const saved = saveReport(out, { title, scope, version: VERSION, format: fmt });
|
|
57867
|
+
return { content: [{ type: "text", text: `Report saved to: ${saved.path}
|
|
57868
|
+
|
|
57869
|
+
${out}` }] };
|
|
57794
57870
|
});
|
|
57795
57871
|
server.registerTool("run_benchmark", {
|
|
57796
57872
|
title: "Run the benchmark suite",
|
|
@@ -57832,12 +57908,12 @@ async function serve() {
|
|
|
57832
57908
|
// src/index.ts
|
|
57833
57909
|
init_cli();
|
|
57834
57910
|
import { readFileSync as readFileSync23, existsSync as existsSync12 } from "node:fs";
|
|
57835
|
-
import { join as
|
|
57911
|
+
import { join as join21 } from "node:path";
|
|
57836
57912
|
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
57837
|
-
var ROOT11 =
|
|
57913
|
+
var ROOT11 = join21(fileURLToPath13(new URL(".", import.meta.url)), "..");
|
|
57838
57914
|
function readVersion() {
|
|
57839
57915
|
try {
|
|
57840
|
-
const p =
|
|
57916
|
+
const p = join21(ROOT11, "package.json");
|
|
57841
57917
|
if (existsSync12(p))
|
|
57842
57918
|
return JSON.parse(readFileSync23(p, "utf8")).version ?? "1.0.0";
|
|
57843
57919
|
} catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.23",
|
|
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": {
|