blitzstrike 2.4.7 → 2.4.9
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 +23 -0
- package/dist/index.js +222 -135
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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.9] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added — evidence-first enforcement (findings are never born empty)
|
|
11
|
+
|
|
12
|
+
- `makeFinding` + `finding_create` now accept `evidence` at creation — a finding
|
|
13
|
+
can carry its observed artifact (headers/URL/response) the moment it is
|
|
14
|
+
created, instead of relying on a separate attach call that gets skipped.
|
|
15
|
+
- `finding_create` warns when a finding is created with no evidence.
|
|
16
|
+
- `reportMarkdown` now flags evidence-less findings with an explicit
|
|
17
|
+
"Evidence-first violation" block (ids listed) — detection without proof is
|
|
18
|
+
surfaced instead of silently shipped.
|
|
19
|
+
|
|
20
|
+
## [2.4.8] — 2026-09-14
|
|
21
|
+
|
|
22
|
+
### Added — MCP integration health checks
|
|
23
|
+
|
|
24
|
+
- New `checkMcpServers` (`src/mcp-status.ts`): verifies external MCP
|
|
25
|
+
integrations are actually present/connected — chrome-devtools-mcp (stdio:
|
|
26
|
+
binary or npx auto-install) and burp-suite-mcp (SSE: alive only when Burp is
|
|
27
|
+
running with the extension loaded).
|
|
28
|
+
- `blitzstrike doctor` now reports both MCP servers with availability + fix hints.
|
|
29
|
+
- New MCP tool `check_mcp` to probe MCP integration status on demand.
|
|
30
|
+
|
|
8
31
|
## [2.4.7] — 2026-09-14
|
|
9
32
|
|
|
10
33
|
### Added — deterministic arbitrary-file-read verification (no bare hypotheses)
|
package/dist/index.js
CHANGED
|
@@ -7667,6 +7667,95 @@ var init_catalog = __esm(() => {
|
|
|
7667
7667
|
ROOT2 = join6(fileURLToPath3(new URL(".", import.meta.url)), "..");
|
|
7668
7668
|
});
|
|
7669
7669
|
|
|
7670
|
+
// src/burp-mcp.ts
|
|
7671
|
+
async function fetchTimeout(url, init = {}, timeoutMs = 8000) {
|
|
7672
|
+
const ctrl = new AbortController;
|
|
7673
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
7674
|
+
try {
|
|
7675
|
+
return await fetch(url, { ...init, signal: ctrl.signal });
|
|
7676
|
+
} finally {
|
|
7677
|
+
clearTimeout(t);
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
function sseMessage(text) {
|
|
7681
|
+
const m = text.match(/event:\s*message[\s\S]*?data:\s*([^\n]+)/);
|
|
7682
|
+
return m ? m[1].trim() : null;
|
|
7683
|
+
}
|
|
7684
|
+
async function driveBurpMCP(baseUrl = DEFAULT_BURP_URL, timeoutMs = 1e4) {
|
|
7685
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
7686
|
+
let sseRes;
|
|
7687
|
+
try {
|
|
7688
|
+
sseRes = await fetchTimeout(`${base}/sse`, { headers: { Accept: "text/event-stream" } }, timeoutMs);
|
|
7689
|
+
} catch (e) {
|
|
7690
|
+
return { ok: false, alive: false, base_url: base, error: `Burp MCP not reachable at ${base}/sse (${String(e)}) — start Burp + load the MCP extension` };
|
|
7691
|
+
}
|
|
7692
|
+
if (!sseRes.ok) {
|
|
7693
|
+
return {
|
|
7694
|
+
ok: false,
|
|
7695
|
+
alive: false,
|
|
7696
|
+
base_url: base,
|
|
7697
|
+
error: `Burp MCP SSE endpoint returned HTTP ${sseRes.status} — is the extension enabled in Burp → MCP?`
|
|
7698
|
+
};
|
|
7699
|
+
}
|
|
7700
|
+
let endpoint = null;
|
|
7701
|
+
let serverInfo = "";
|
|
7702
|
+
let toolsRaw = "";
|
|
7703
|
+
try {
|
|
7704
|
+
const reader = sseRes.body.getReader();
|
|
7705
|
+
const decoder = new TextDecoder;
|
|
7706
|
+
let buf = "";
|
|
7707
|
+
const deadline = Date.now() + timeoutMs;
|
|
7708
|
+
while (Date.now() < deadline) {
|
|
7709
|
+
const { value, done } = await reader.read();
|
|
7710
|
+
if (done)
|
|
7711
|
+
break;
|
|
7712
|
+
buf += decoder.decode(value, { stream: true });
|
|
7713
|
+
if (!endpoint) {
|
|
7714
|
+
const em = buf.match(/event:\s*endpoint[\s\S]*?data:\s*([^\n]+)/);
|
|
7715
|
+
if (em)
|
|
7716
|
+
endpoint = em[1].trim();
|
|
7717
|
+
}
|
|
7718
|
+
if (endpoint)
|
|
7719
|
+
break;
|
|
7720
|
+
}
|
|
7721
|
+
if (endpoint) {
|
|
7722
|
+
const postUrl = new URL(endpoint, base).toString();
|
|
7723
|
+
const initRes = await fetchTimeout(postUrl, {
|
|
7724
|
+
method: "POST",
|
|
7725
|
+
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
7726
|
+
body: JSON.stringify({
|
|
7727
|
+
jsonrpc: "2.0",
|
|
7728
|
+
id: 1,
|
|
7729
|
+
method: "initialize",
|
|
7730
|
+
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "blitzstrike", version: "2" } }
|
|
7731
|
+
})
|
|
7732
|
+
}, timeoutMs);
|
|
7733
|
+
const initText = await initRes.text();
|
|
7734
|
+
const initMsg = sseMessage(initText) ?? (initText.includes("result") ? initText : "");
|
|
7735
|
+
serverInfo = initMsg.slice(0, 600);
|
|
7736
|
+
const toolsRes = await fetchTimeout(postUrl, {
|
|
7737
|
+
method: "POST",
|
|
7738
|
+
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
7739
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} })
|
|
7740
|
+
}, timeoutMs);
|
|
7741
|
+
const toolsText = await toolsRes.text();
|
|
7742
|
+
toolsRaw = (sseMessage(toolsText) ?? toolsText).slice(0, 3000);
|
|
7743
|
+
}
|
|
7744
|
+
} catch (e) {
|
|
7745
|
+
return { ok: true, alive: true, base_url: base, endpoint, error: `SSE stream opened but handshake failed: ${String(e)}` };
|
|
7746
|
+
}
|
|
7747
|
+
if (!endpoint) {
|
|
7748
|
+
return {
|
|
7749
|
+
ok: true,
|
|
7750
|
+
alive: true,
|
|
7751
|
+
base_url: base,
|
|
7752
|
+
error: "SSE stream open but no endpoint event received — Burp MCP extension may not be fully enabled"
|
|
7753
|
+
};
|
|
7754
|
+
}
|
|
7755
|
+
return { ok: true, alive: true, base_url: base, endpoint, server: serverInfo, tools: toolsRaw };
|
|
7756
|
+
}
|
|
7757
|
+
var DEFAULT_BURP_URL = "http://127.0.0.1:9876";
|
|
7758
|
+
|
|
7670
7759
|
// node_modules/php-parser/src/lexer/attribute.js
|
|
7671
7760
|
var require_attribute = __commonJS(function(exports, module) {
|
|
7672
7761
|
module.exports = {
|
|
@@ -15295,12 +15384,39 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
15295
15384
|
module.exports.default = Engine;
|
|
15296
15385
|
});
|
|
15297
15386
|
|
|
15387
|
+
// src/mcp-status.ts
|
|
15388
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
15389
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
15390
|
+
async function checkMcpServers() {
|
|
15391
|
+
const out = [];
|
|
15392
|
+
const bin = ["/usr/bin/chrome-devtools-mcp", "/usr/local/bin/chrome-devtools-mcp"].find((p) => existsSync8(p));
|
|
15393
|
+
const npxOk = spawnSync3("npx", ["--version"], { timeout: 8000 }).error === undefined;
|
|
15394
|
+
const devtoolsOk = Boolean(bin) || npxOk;
|
|
15395
|
+
out.push({
|
|
15396
|
+
name: "chrome-devtools-mcp",
|
|
15397
|
+
available: devtoolsOk,
|
|
15398
|
+
kind: "stdio",
|
|
15399
|
+
detail: bin ? `installed at ${bin}` : npxOk ? "not installed — will auto-install via npx on first use" : "not installed (no npx either)",
|
|
15400
|
+
fix: devtoolsOk ? undefined : "install Node/npm, then `npm i -g chrome-devtools-mcp`"
|
|
15401
|
+
});
|
|
15402
|
+
const burp = await driveBurpMCP("http://127.0.0.1:9876", 4000);
|
|
15403
|
+
out.push({
|
|
15404
|
+
name: "burp-suite-mcp",
|
|
15405
|
+
available: burp.alive,
|
|
15406
|
+
kind: "sse",
|
|
15407
|
+
detail: burp.alive ? `alive at ${burp.base_url}${burp.endpoint ? ` (endpoint ${burp.endpoint})` : ""}` : burp.error ?? "not reachable",
|
|
15408
|
+
fix: burp.alive ? undefined : "build PortSwigger/mcp-server (`./gradlew embedProxyJar`) + load the JAR in Burp → Extensions, then enable the MCP extension"
|
|
15409
|
+
});
|
|
15410
|
+
return out;
|
|
15411
|
+
}
|
|
15412
|
+
var init_mcp_status = () => {};
|
|
15413
|
+
|
|
15298
15414
|
// src/sync.ts
|
|
15299
15415
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, readFileSync as readFileSync18, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
|
|
15300
15416
|
import { join as join16 } from "node:path";
|
|
15301
15417
|
import { homedir as homedir5 } from "node:os";
|
|
15302
15418
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
15303
|
-
import { spawnSync as
|
|
15419
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
15304
15420
|
function dirExists(p) {
|
|
15305
15421
|
try {
|
|
15306
15422
|
return statSync4(p).isDirectory();
|
|
@@ -15351,14 +15467,14 @@ function syncData() {
|
|
|
15351
15467
|
copyDir(localTemplates, templatesPath);
|
|
15352
15468
|
templatesSynced = true;
|
|
15353
15469
|
}
|
|
15354
|
-
const gitOk =
|
|
15470
|
+
const gitOk = spawnSync4("git", ["--version"]).status === 0;
|
|
15355
15471
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
15356
15472
|
const tmp = join16(DATA_ROOT2, ".tmp-clone");
|
|
15357
15473
|
if (dirExists(tmp))
|
|
15358
15474
|
rmSync4(tmp, { recursive: true, force: true });
|
|
15359
|
-
const r =
|
|
15475
|
+
const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
15360
15476
|
if (r.status === 0) {
|
|
15361
|
-
const sparse =
|
|
15477
|
+
const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
15362
15478
|
if (sparse.status === 0) {
|
|
15363
15479
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
15364
15480
|
copyDir(join16(tmp, "payloads"), payloadsPath);
|
|
@@ -15379,7 +15495,7 @@ function syncData() {
|
|
|
15379
15495
|
};
|
|
15380
15496
|
}
|
|
15381
15497
|
function copyDir(src, dst) {
|
|
15382
|
-
const r =
|
|
15498
|
+
const r = spawnSync4("cp", ["-r", src, dst], { timeout: 120000 });
|
|
15383
15499
|
if (r.status !== 0) {
|
|
15384
15500
|
manualCopy(src, dst);
|
|
15385
15501
|
}
|
|
@@ -15443,7 +15559,7 @@ var init_update = __esm(() => {
|
|
|
15443
15559
|
});
|
|
15444
15560
|
|
|
15445
15561
|
// src/release.ts
|
|
15446
|
-
import { readFileSync as readFileSync20, existsSync as
|
|
15562
|
+
import { readFileSync as readFileSync20, existsSync as existsSync10 } from "node:fs";
|
|
15447
15563
|
import { join as join17 } from "node:path";
|
|
15448
15564
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
15449
15565
|
import { execSync as execSync4 } from "node:child_process";
|
|
@@ -15483,7 +15599,7 @@ function validateRelease() {
|
|
|
15483
15599
|
return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
|
|
15484
15600
|
}
|
|
15485
15601
|
function lockfilePresent() {
|
|
15486
|
-
return
|
|
15602
|
+
return existsSync10(join17(ROOT8, "package-lock.json")) || existsSync10(join17(ROOT8, "bun.lock")) || existsSync10(join17(ROOT8, "bun.lockb"));
|
|
15487
15603
|
}
|
|
15488
15604
|
var ROOT8;
|
|
15489
15605
|
var init_release = __esm(() => {
|
|
@@ -15525,20 +15641,20 @@ var init_dependency = __esm(() => {
|
|
|
15525
15641
|
});
|
|
15526
15642
|
|
|
15527
15643
|
// src/cli.ts
|
|
15528
|
-
import { readFileSync as readFileSync22, existsSync as
|
|
15644
|
+
import { readFileSync as readFileSync22, existsSync as existsSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "node:fs";
|
|
15529
15645
|
import { join as join19, dirname as dirname2 } from "node:path";
|
|
15530
15646
|
import { homedir as homedir6 } from "node:os";
|
|
15531
15647
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
15532
|
-
import { spawnSync as
|
|
15648
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
15533
15649
|
function which(cmd) {
|
|
15534
|
-
return
|
|
15650
|
+
return spawnSync5(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
15535
15651
|
}
|
|
15536
15652
|
function installedCount() {
|
|
15537
15653
|
const tools = loadTools();
|
|
15538
15654
|
const installed = tools.filter((t) => which(t.name)).map((t) => t.name);
|
|
15539
15655
|
return { total: tools.length, installed, missing: tools.map((t) => t.name).filter((n) => !installed.includes(n)) };
|
|
15540
15656
|
}
|
|
15541
|
-
function runDoctor() {
|
|
15657
|
+
async function runDoctor() {
|
|
15542
15658
|
console.log(`BlitzStrike doctor — environment health check
|
|
15543
15659
|
`);
|
|
15544
15660
|
const issues = [];
|
|
@@ -15571,7 +15687,7 @@ function runDoctor() {
|
|
|
15571
15687
|
detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
|
|
15572
15688
|
fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
|
|
15573
15689
|
});
|
|
15574
|
-
const dataOk =
|
|
15690
|
+
const dataOk = existsSync11(join19(ROOT10, "chains.json")) && existsSync11(join19(ROOT10, "tools-catalog.json"));
|
|
15575
15691
|
issues.push({
|
|
15576
15692
|
name: "Data layers (chains + tools-catalog)",
|
|
15577
15693
|
status: dataOk ? "ok" : "fail",
|
|
@@ -15592,6 +15708,15 @@ function runDoctor() {
|
|
|
15592
15708
|
detail: deps.safe ? `${deps.total_dependencies} deps, no install scripts` : `${deps.install_scripts.length} install script(s) + ${deps.git_dependencies.length} git dep(s)`,
|
|
15593
15709
|
fix: deps.safe ? undefined : "remove install-time scripts and git dependencies"
|
|
15594
15710
|
});
|
|
15711
|
+
const mcps = await checkMcpServers();
|
|
15712
|
+
for (const m of mcps) {
|
|
15713
|
+
issues.push({
|
|
15714
|
+
name: `MCP: ${m.name}`,
|
|
15715
|
+
status: m.available ? "ok" : "warn",
|
|
15716
|
+
detail: m.detail,
|
|
15717
|
+
fix: m.fix
|
|
15718
|
+
});
|
|
15719
|
+
}
|
|
15595
15720
|
const symbol = { ok: "OK ", warn: "WARN", fail: "FAIL" };
|
|
15596
15721
|
for (const i of issues) {
|
|
15597
15722
|
console.log(`${symbol[i.status]} ${i.name} ${i.detail}`);
|
|
@@ -15614,14 +15739,14 @@ function resolveCommand() {
|
|
|
15614
15739
|
if (which("blitzstrike"))
|
|
15615
15740
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
15616
15741
|
const src = join19(ROOT10, "src", "index.ts");
|
|
15617
|
-
if (which("bun") &&
|
|
15742
|
+
if (which("bun") && existsSync11(src))
|
|
15618
15743
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
15619
15744
|
if (which("npx"))
|
|
15620
15745
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
15621
15746
|
if (which("bunx"))
|
|
15622
15747
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
15623
15748
|
const dist = join19(ROOT10, "dist", "index.js");
|
|
15624
|
-
if (
|
|
15749
|
+
if (existsSync11(dist))
|
|
15625
15750
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
15626
15751
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
15627
15752
|
}
|
|
@@ -15635,7 +15760,7 @@ function readJson(p) {
|
|
|
15635
15760
|
function jsonMcpServersWrite(p) {
|
|
15636
15761
|
return (command, args) => {
|
|
15637
15762
|
const dir = dirname2(p);
|
|
15638
|
-
if (!
|
|
15763
|
+
if (!existsSync11(dir))
|
|
15639
15764
|
mkdirSync5(dir, { recursive: true });
|
|
15640
15765
|
const existing = readJson(p) ?? {};
|
|
15641
15766
|
existing.mcpServers = {
|
|
@@ -15648,7 +15773,7 @@ function jsonMcpServersWrite(p) {
|
|
|
15648
15773
|
function opencodeWrite(p) {
|
|
15649
15774
|
return (command, args) => {
|
|
15650
15775
|
const dir = dirname2(p);
|
|
15651
|
-
if (!
|
|
15776
|
+
if (!existsSync11(dir))
|
|
15652
15777
|
mkdirSync5(dir, { recursive: true });
|
|
15653
15778
|
const existing = readJson(p) ?? {};
|
|
15654
15779
|
existing.mcp = {
|
|
@@ -15662,14 +15787,14 @@ function opencodeWrite(p) {
|
|
|
15662
15787
|
function copyOpenCodeAgents() {
|
|
15663
15788
|
const srcDir = join19(ROOT10, "opencode-agents");
|
|
15664
15789
|
const dstDir = join19(homedir6(), ".config", "opencode", "agents");
|
|
15665
|
-
if (!
|
|
15790
|
+
if (!existsSync11(srcDir))
|
|
15666
15791
|
return;
|
|
15667
|
-
if (!
|
|
15792
|
+
if (!existsSync11(dstDir))
|
|
15668
15793
|
mkdirSync5(dstDir, { recursive: true });
|
|
15669
15794
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
15670
15795
|
const src = join19(srcDir, f);
|
|
15671
15796
|
const dst = join19(dstDir, f);
|
|
15672
|
-
if (
|
|
15797
|
+
if (existsSync11(src)) {
|
|
15673
15798
|
writeFileSync7(dst, readFileSync22(src, "utf8"));
|
|
15674
15799
|
}
|
|
15675
15800
|
}
|
|
@@ -15677,9 +15802,9 @@ function copyOpenCodeAgents() {
|
|
|
15677
15802
|
function codexWrite(p) {
|
|
15678
15803
|
return (command, args) => {
|
|
15679
15804
|
const dir = dirname2(p);
|
|
15680
|
-
if (!
|
|
15805
|
+
if (!existsSync11(dir))
|
|
15681
15806
|
mkdirSync5(dir, { recursive: true });
|
|
15682
|
-
let existing =
|
|
15807
|
+
let existing = existsSync11(p) ? readFileSync22(p, "utf8") : "";
|
|
15683
15808
|
if (!existing.trimEnd().endsWith(`
|
|
15684
15809
|
`))
|
|
15685
15810
|
existing += `
|
|
@@ -15696,9 +15821,9 @@ args = ${JSON.stringify(args)}
|
|
|
15696
15821
|
function hermesWrite(p) {
|
|
15697
15822
|
return (command, args) => {
|
|
15698
15823
|
const dir = dirname2(p);
|
|
15699
|
-
if (!
|
|
15824
|
+
if (!existsSync11(dir))
|
|
15700
15825
|
mkdirSync5(dir, { recursive: true });
|
|
15701
|
-
let existing =
|
|
15826
|
+
let existing = existsSync11(p) ? readFileSync22(p, "utf8") : "";
|
|
15702
15827
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
15703
15828
|
if (!existing.trimEnd().endsWith(`
|
|
15704
15829
|
`))
|
|
@@ -15729,70 +15854,70 @@ function detectAgents() {
|
|
|
15729
15854
|
agents.push({
|
|
15730
15855
|
name: "Claude Code",
|
|
15731
15856
|
detectPath: claudeJson,
|
|
15732
|
-
installed: () =>
|
|
15857
|
+
installed: () => existsSync11(claudeJson) || which("claude"),
|
|
15733
15858
|
write: jsonMcpServersWrite(claudeJson)
|
|
15734
15859
|
});
|
|
15735
15860
|
const claudeDesktop = join19(home, ".config", "Claude", "claude_desktop_config.json");
|
|
15736
15861
|
agents.push({
|
|
15737
15862
|
name: "Claude Desktop",
|
|
15738
15863
|
detectPath: claudeDesktop,
|
|
15739
|
-
installed: () =>
|
|
15864
|
+
installed: () => existsSync11(claudeDesktop),
|
|
15740
15865
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
15741
15866
|
});
|
|
15742
15867
|
const cursor = join19(home, ".cursor", "mcp.json");
|
|
15743
15868
|
agents.push({
|
|
15744
15869
|
name: "Cursor",
|
|
15745
15870
|
detectPath: cursor,
|
|
15746
|
-
installed: () =>
|
|
15871
|
+
installed: () => existsSync11(join19(home, ".cursor")) || which("cursor"),
|
|
15747
15872
|
write: jsonMcpServersWrite(cursor)
|
|
15748
15873
|
});
|
|
15749
15874
|
const opencode = join19(home, ".config", "opencode", "opencode.jsonc");
|
|
15750
15875
|
agents.push({
|
|
15751
15876
|
name: "OpenCode",
|
|
15752
15877
|
detectPath: opencode,
|
|
15753
|
-
installed: () =>
|
|
15878
|
+
installed: () => existsSync11(join19(home, ".config", "opencode")) || which("opencode"),
|
|
15754
15879
|
write: opencodeWrite(opencode)
|
|
15755
15880
|
});
|
|
15756
15881
|
const codex = join19(home, ".codex", "config.toml");
|
|
15757
15882
|
agents.push({
|
|
15758
15883
|
name: "Codex",
|
|
15759
15884
|
detectPath: codex,
|
|
15760
|
-
installed: () =>
|
|
15885
|
+
installed: () => existsSync11(join19(home, ".codex")) || which("codex"),
|
|
15761
15886
|
write: codexWrite(codex)
|
|
15762
15887
|
});
|
|
15763
15888
|
const hermes = join19(home, ".hermes", "config.yaml");
|
|
15764
15889
|
agents.push({
|
|
15765
15890
|
name: "Hermes",
|
|
15766
15891
|
detectPath: hermes,
|
|
15767
|
-
installed: () =>
|
|
15892
|
+
installed: () => existsSync11(join19(home, ".hermes")) || which("hermes"),
|
|
15768
15893
|
write: hermesWrite(hermes)
|
|
15769
15894
|
});
|
|
15770
15895
|
const gemini = join19(home, ".gemini", "settings.json");
|
|
15771
15896
|
agents.push({
|
|
15772
15897
|
name: "Gemini CLI",
|
|
15773
15898
|
detectPath: gemini,
|
|
15774
|
-
installed: () =>
|
|
15899
|
+
installed: () => existsSync11(join19(home, ".gemini")) || which("gemini"),
|
|
15775
15900
|
write: jsonMcpServersWrite(gemini)
|
|
15776
15901
|
});
|
|
15777
15902
|
const windsurf = join19(home, ".codeium", "windsurf", "mcp_config.json");
|
|
15778
15903
|
agents.push({
|
|
15779
15904
|
name: "Windsurf",
|
|
15780
15905
|
detectPath: windsurf,
|
|
15781
|
-
installed: () =>
|
|
15906
|
+
installed: () => existsSync11(join19(home, ".codeium")) || which("windsurf"),
|
|
15782
15907
|
write: jsonMcpServersWrite(windsurf)
|
|
15783
15908
|
});
|
|
15784
15909
|
const copilot = join19(home, ".copilot", "mcp.json");
|
|
15785
15910
|
agents.push({
|
|
15786
15911
|
name: "Copilot",
|
|
15787
15912
|
detectPath: copilot,
|
|
15788
|
-
installed: () =>
|
|
15913
|
+
installed: () => existsSync11(join19(home, ".copilot")),
|
|
15789
15914
|
write: jsonMcpServersWrite(copilot)
|
|
15790
15915
|
});
|
|
15791
15916
|
const cline = join19(home, ".cline", "mcp_settings.json");
|
|
15792
15917
|
agents.push({
|
|
15793
15918
|
name: "Cline",
|
|
15794
15919
|
detectPath: cline,
|
|
15795
|
-
installed: () =>
|
|
15920
|
+
installed: () => existsSync11(join19(home, ".cline")),
|
|
15796
15921
|
write: jsonMcpServersWrite(cline)
|
|
15797
15922
|
});
|
|
15798
15923
|
return agents;
|
|
@@ -15874,6 +15999,7 @@ var init_cli = __esm(() => {
|
|
|
15874
15999
|
init_catalog();
|
|
15875
16000
|
init_release();
|
|
15876
16001
|
init_dependency();
|
|
16002
|
+
init_mcp_status();
|
|
15877
16003
|
ROOT10 = join19(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
15878
16004
|
});
|
|
15879
16005
|
|
|
@@ -30938,7 +31064,7 @@ function makeFinding(input) {
|
|
|
30938
31064
|
const status = input.status ?? "detected";
|
|
30939
31065
|
const factors = confidenceFactorsFor(input);
|
|
30940
31066
|
const confidence = computeConfidence(factors);
|
|
30941
|
-
|
|
31067
|
+
let finding = {
|
|
30942
31068
|
id: nextFindingId(),
|
|
30943
31069
|
status,
|
|
30944
31070
|
title: input.title,
|
|
@@ -30961,6 +31087,16 @@ function makeFinding(input) {
|
|
|
30961
31087
|
remediation: {},
|
|
30962
31088
|
timestamps: { created: now, updated: now }
|
|
30963
31089
|
};
|
|
31090
|
+
if (input.evidence?.length) {
|
|
31091
|
+
for (const e of input.evidence) {
|
|
31092
|
+
const ev = makeEvidence({
|
|
31093
|
+
type: e.type,
|
|
31094
|
+
description: e.description,
|
|
31095
|
+
artifacts: e.content ? [{ name: "artifact", kind: "evidence", content: e.content }] : []
|
|
31096
|
+
});
|
|
31097
|
+
finding = attachEvidence(finding, ev);
|
|
31098
|
+
}
|
|
31099
|
+
}
|
|
30964
31100
|
recordAudit("finding_created", { tool: "eagle-eye", target: input.target.host ?? input.target.endpoint, result: status, correlationId: finding.id });
|
|
30965
31101
|
return finding;
|
|
30966
31102
|
}
|
|
@@ -31781,95 +31917,6 @@ async function driveChromeDevtools(target, timeoutMs = 25000) {
|
|
|
31781
31917
|
}
|
|
31782
31918
|
}
|
|
31783
31919
|
|
|
31784
|
-
// src/burp-mcp.ts
|
|
31785
|
-
var DEFAULT_BURP_URL = "http://127.0.0.1:9876";
|
|
31786
|
-
async function fetchTimeout(url, init = {}, timeoutMs = 8000) {
|
|
31787
|
-
const ctrl = new AbortController;
|
|
31788
|
-
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
31789
|
-
try {
|
|
31790
|
-
return await fetch(url, { ...init, signal: ctrl.signal });
|
|
31791
|
-
} finally {
|
|
31792
|
-
clearTimeout(t);
|
|
31793
|
-
}
|
|
31794
|
-
}
|
|
31795
|
-
function sseMessage(text) {
|
|
31796
|
-
const m = text.match(/event:\s*message[\s\S]*?data:\s*([^\n]+)/);
|
|
31797
|
-
return m ? m[1].trim() : null;
|
|
31798
|
-
}
|
|
31799
|
-
async function driveBurpMCP(baseUrl = DEFAULT_BURP_URL, timeoutMs = 1e4) {
|
|
31800
|
-
const base = baseUrl.replace(/\/+$/, "");
|
|
31801
|
-
let sseRes;
|
|
31802
|
-
try {
|
|
31803
|
-
sseRes = await fetchTimeout(`${base}/sse`, { headers: { Accept: "text/event-stream" } }, timeoutMs);
|
|
31804
|
-
} catch (e) {
|
|
31805
|
-
return { ok: false, alive: false, base_url: base, error: `Burp MCP not reachable at ${base}/sse (${String(e)}) — start Burp + load the MCP extension` };
|
|
31806
|
-
}
|
|
31807
|
-
if (!sseRes.ok) {
|
|
31808
|
-
return {
|
|
31809
|
-
ok: false,
|
|
31810
|
-
alive: false,
|
|
31811
|
-
base_url: base,
|
|
31812
|
-
error: `Burp MCP SSE endpoint returned HTTP ${sseRes.status} — is the extension enabled in Burp → MCP?`
|
|
31813
|
-
};
|
|
31814
|
-
}
|
|
31815
|
-
let endpoint = null;
|
|
31816
|
-
let serverInfo = "";
|
|
31817
|
-
let toolsRaw = "";
|
|
31818
|
-
try {
|
|
31819
|
-
const reader = sseRes.body.getReader();
|
|
31820
|
-
const decoder = new TextDecoder;
|
|
31821
|
-
let buf = "";
|
|
31822
|
-
const deadline = Date.now() + timeoutMs;
|
|
31823
|
-
while (Date.now() < deadline) {
|
|
31824
|
-
const { value, done } = await reader.read();
|
|
31825
|
-
if (done)
|
|
31826
|
-
break;
|
|
31827
|
-
buf += decoder.decode(value, { stream: true });
|
|
31828
|
-
if (!endpoint) {
|
|
31829
|
-
const em = buf.match(/event:\s*endpoint[\s\S]*?data:\s*([^\n]+)/);
|
|
31830
|
-
if (em)
|
|
31831
|
-
endpoint = em[1].trim();
|
|
31832
|
-
}
|
|
31833
|
-
if (endpoint)
|
|
31834
|
-
break;
|
|
31835
|
-
}
|
|
31836
|
-
if (endpoint) {
|
|
31837
|
-
const postUrl = new URL(endpoint, base).toString();
|
|
31838
|
-
const initRes = await fetchTimeout(postUrl, {
|
|
31839
|
-
method: "POST",
|
|
31840
|
-
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
31841
|
-
body: JSON.stringify({
|
|
31842
|
-
jsonrpc: "2.0",
|
|
31843
|
-
id: 1,
|
|
31844
|
-
method: "initialize",
|
|
31845
|
-
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "blitzstrike", version: "2" } }
|
|
31846
|
-
})
|
|
31847
|
-
}, timeoutMs);
|
|
31848
|
-
const initText = await initRes.text();
|
|
31849
|
-
const initMsg = sseMessage(initText) ?? (initText.includes("result") ? initText : "");
|
|
31850
|
-
serverInfo = initMsg.slice(0, 600);
|
|
31851
|
-
const toolsRes = await fetchTimeout(postUrl, {
|
|
31852
|
-
method: "POST",
|
|
31853
|
-
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
31854
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} })
|
|
31855
|
-
}, timeoutMs);
|
|
31856
|
-
const toolsText = await toolsRes.text();
|
|
31857
|
-
toolsRaw = (sseMessage(toolsText) ?? toolsText).slice(0, 3000);
|
|
31858
|
-
}
|
|
31859
|
-
} catch (e) {
|
|
31860
|
-
return { ok: true, alive: true, base_url: base, endpoint, error: `SSE stream opened but handshake failed: ${String(e)}` };
|
|
31861
|
-
}
|
|
31862
|
-
if (!endpoint) {
|
|
31863
|
-
return {
|
|
31864
|
-
ok: true,
|
|
31865
|
-
alive: true,
|
|
31866
|
-
base_url: base,
|
|
31867
|
-
error: "SSE stream open but no endpoint event received — Burp MCP extension may not be fully enabled"
|
|
31868
|
-
};
|
|
31869
|
-
}
|
|
31870
|
-
return { ok: true, alive: true, base_url: base, endpoint, server: serverInfo, tools: toolsRaw };
|
|
31871
|
-
}
|
|
31872
|
-
|
|
31873
31920
|
// src/chain-executor.ts
|
|
31874
31921
|
var BLITZ_FINDING_TOOLS = {
|
|
31875
31922
|
strike_verify: verifyFinding,
|
|
@@ -32972,11 +33019,15 @@ function summarize(findings) {
|
|
|
32972
33019
|
const byStatus = {};
|
|
32973
33020
|
const bySeverity = {};
|
|
32974
33021
|
let evidenceArtifacts = 0;
|
|
33022
|
+
let evidenceLess = 0;
|
|
32975
33023
|
for (const f of findings) {
|
|
32976
33024
|
byStatus[f.status ?? "detected"] = (byStatus[f.status ?? "detected"] ?? 0) + 1;
|
|
32977
33025
|
const sev = f.classification?.severity ?? "informational";
|
|
32978
33026
|
bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
|
|
32979
|
-
|
|
33027
|
+
const arts = f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
|
|
33028
|
+
evidenceArtifacts += arts;
|
|
33029
|
+
if ((f.evidence ?? []).length === 0)
|
|
33030
|
+
evidenceLess += 1;
|
|
32980
33031
|
}
|
|
32981
33032
|
const groups = dedupFindings(findings);
|
|
32982
33033
|
return {
|
|
@@ -32986,7 +33037,8 @@ function summarize(findings) {
|
|
|
32986
33037
|
by_severity: bySeverity,
|
|
32987
33038
|
confirmed: byStatus["confirmed"] ?? 0,
|
|
32988
33039
|
false_positives: byStatus["false_positive"] ?? 0,
|
|
32989
|
-
evidence_artifacts: evidenceArtifacts
|
|
33040
|
+
evidence_artifacts: evidenceArtifacts,
|
|
33041
|
+
evidence_less_findings: evidenceLess
|
|
32990
33042
|
};
|
|
32991
33043
|
}
|
|
32992
33044
|
function reportMarkdown(findings, opts = {}) {
|
|
@@ -33010,6 +33062,11 @@ function reportMarkdown(findings, opts = {}) {
|
|
|
33010
33062
|
}
|
|
33011
33063
|
lines.push(`Detailed findings, reproduction steps, impact, and remediation follow in section 5.`);
|
|
33012
33064
|
lines.push("");
|
|
33065
|
+
if (summary.evidence_less_findings > 0) {
|
|
33066
|
+
const evLess = findings.filter((f) => (f.evidence ?? []).length === 0).map((f) => f.id).join(", ");
|
|
33067
|
+
lines.push(`> ⚠️ **Evidence-first violation:** **${summary.evidence_less_findings}** finding(s) carry NO evidence artifacts (${evLess}). ` + `Detection is not proof — every finding must carry the observed artifact (headers, URL, response body). Attach evidence via finding_attach_evidence before finalizing.`);
|
|
33068
|
+
lines.push("");
|
|
33069
|
+
}
|
|
33013
33070
|
lines.push("## 2. Scope");
|
|
33014
33071
|
lines.push("");
|
|
33015
33072
|
lines.push(`- **Target(s):** ${scope}`);
|
|
@@ -55414,6 +55471,9 @@ function browserDiagnostics() {
|
|
|
55414
55471
|
return { console_errors: [...consoleErrors], page_errors: [...pageErrors] };
|
|
55415
55472
|
}
|
|
55416
55473
|
|
|
55474
|
+
// src/server.ts
|
|
55475
|
+
init_mcp_status();
|
|
55476
|
+
|
|
55417
55477
|
// src/route-confusion.ts
|
|
55418
55478
|
var SOURCE_TOKENS = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post)|getParameter\(|req\.(query|body|params)/;
|
|
55419
55479
|
function lines(code) {
|
|
@@ -55988,7 +56048,7 @@ function runBenchmark() {
|
|
|
55988
56048
|
}
|
|
55989
56049
|
|
|
55990
56050
|
// src/enterprise-benchmark.ts
|
|
55991
|
-
import { existsSync as
|
|
56051
|
+
import { existsSync as existsSync9, readFileSync as readFileSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
55992
56052
|
import { join as join15, resolve } from "node:path";
|
|
55993
56053
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
55994
56054
|
var ENTERPRISE_FIXTURES = [
|
|
@@ -56048,7 +56108,7 @@ function detectFixture(dir) {
|
|
|
56048
56108
|
function runEnterpriseBenchmark() {
|
|
56049
56109
|
const fixtures = [];
|
|
56050
56110
|
for (const fx of ENTERPRISE_FIXTURES) {
|
|
56051
|
-
if (!
|
|
56111
|
+
if (!existsSync9(fx.dir)) {
|
|
56052
56112
|
fixtures.push({ id: fx.id, available: false, detected: 0, expected: fx.expected_vulns, recall: 0 });
|
|
56053
56113
|
continue;
|
|
56054
56114
|
}
|
|
@@ -56680,6 +56740,14 @@ ${statusLine}` : statusLine },
|
|
|
56680
56740
|
}, async ({ target }) => {
|
|
56681
56741
|
return { content: [{ type: "text", text: JSON.stringify(await driveChromeDevtools(target)) }] };
|
|
56682
56742
|
});
|
|
56743
|
+
server.registerTool("check_mcp", {
|
|
56744
|
+
title: "Check external MCP integrations (chrome-devtools-mcp + burp-suite-mcp)",
|
|
56745
|
+
description: "Verify every external MCP integration is actually present/connected: chrome-devtools-mcp (stdio — installed or npx auto-install) and burp-suite-mcp (SSE — only alive when Burp is running with the extension loaded). Returns availability + fix hints, so you know before you need them.",
|
|
56746
|
+
inputSchema: {}
|
|
56747
|
+
}, async () => {
|
|
56748
|
+
const status = await checkMcpServers();
|
|
56749
|
+
return { content: [{ type: "text", text: JSON.stringify({ mcp_servers: status }) }] };
|
|
56750
|
+
});
|
|
56683
56751
|
server.registerTool("install_all_tools", {
|
|
56684
56752
|
title: "Install every catalog tool at once",
|
|
56685
56753
|
description: "CATALOG: install every non-MCP-server catalog tool in parallel (bounded concurrency), skipping MCP servers (they are connected, not installed). Returns a summary (installed / already_installed / failed) + per-tool result. Optionally filter by category (recon, web, api, cloud, system, ...). Use this to mass-provision the toolbelt instead of ensure_tool one-by-one.",
|
|
@@ -57122,7 +57190,7 @@ ${statusLine}` : statusLine },
|
|
|
57122
57190
|
});
|
|
57123
57191
|
server.registerTool("finding_create", {
|
|
57124
57192
|
title: "Create a canonical finding",
|
|
57125
|
-
description: "FINDINGS: create a canonical, evidence-first finding. Severity describes impact; confidence (deterministic) describes certainty. Default status=detected (hypothesis-pending).",
|
|
57193
|
+
description: "FINDINGS: create a canonical, evidence-first finding. Severity describes impact; confidence (deterministic) describes certainty. Default status=detected (hypothesis-pending). Pass `evidence` (JSON array of {type, description, content}) to attach observed artifacts AT CREATION — a finding must never be born empty when you already hold the artifact (headers, URL, response body).",
|
|
57126
57194
|
inputSchema: {
|
|
57127
57195
|
title: string2().describe("Finding title"),
|
|
57128
57196
|
target_type: string2().optional().describe("Target type (web/api/source/mobile/network/other)"),
|
|
@@ -57134,9 +57202,18 @@ ${statusLine}` : statusLine },
|
|
|
57134
57202
|
source_name: string2().describe("Source name"),
|
|
57135
57203
|
sink_type: string2().describe("Sink type (e.g. sql_execution, command_execution, file_operations)"),
|
|
57136
57204
|
sink_symbol: string2().optional().describe("Sink symbol (e.g. '->query(')"),
|
|
57137
|
-
chain_id: string2().optional().describe("Escalation chain id")
|
|
57205
|
+
chain_id: string2().optional().describe("Escalation chain id"),
|
|
57206
|
+
evidence: string2().optional().describe("JSON array of evidence entries: [{type, description, content?}] — attached at creation (redacted + SHA-256 tagged)")
|
|
57207
|
+
}
|
|
57208
|
+
}, async ({ title, target_type, host, endpoint, severity, cwe, source_type, source_name, sink_type, sink_symbol, chain_id, evidence }) => {
|
|
57209
|
+
let evs = [];
|
|
57210
|
+
if (evidence) {
|
|
57211
|
+
try {
|
|
57212
|
+
evs = JSON.parse(evidence);
|
|
57213
|
+
} catch {
|
|
57214
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid evidence JSON" }) }] };
|
|
57215
|
+
}
|
|
57138
57216
|
}
|
|
57139
|
-
}, async ({ title, target_type, host, endpoint, severity, cwe, source_type, source_name, sink_type, sink_symbol, chain_id }) => {
|
|
57140
57217
|
const f = makeFinding({
|
|
57141
57218
|
title,
|
|
57142
57219
|
target: { type: target_type ?? "web", host, endpoint },
|
|
@@ -57145,9 +57222,19 @@ ${statusLine}` : statusLine },
|
|
|
57145
57222
|
source: { type: source_type, name: source_name },
|
|
57146
57223
|
sink: { type: sink_type, symbol: sink_symbol },
|
|
57147
57224
|
chainId: chain_id ?? null,
|
|
57148
|
-
status: "detected"
|
|
57225
|
+
status: "detected",
|
|
57226
|
+
evidence: evs
|
|
57149
57227
|
});
|
|
57150
|
-
|
|
57228
|
+
const empty = (f.evidence ?? []).length === 0;
|
|
57229
|
+
return {
|
|
57230
|
+
content: [{
|
|
57231
|
+
type: "text",
|
|
57232
|
+
text: JSON.stringify({
|
|
57233
|
+
...f,
|
|
57234
|
+
_warning: empty ? "finding created with NO evidence — if you hold the observed artifact (headers/URL/body), attach it now via `evidence` or finding_attach_evidence" : undefined
|
|
57235
|
+
})
|
|
57236
|
+
}]
|
|
57237
|
+
};
|
|
57151
57238
|
});
|
|
57152
57239
|
server.registerTool("finding_transition", {
|
|
57153
57240
|
title: "Advance a finding lifecycle",
|
|
@@ -57623,14 +57710,14 @@ async function serve() {
|
|
|
57623
57710
|
|
|
57624
57711
|
// src/index.ts
|
|
57625
57712
|
init_cli();
|
|
57626
|
-
import { readFileSync as readFileSync23, existsSync as
|
|
57713
|
+
import { readFileSync as readFileSync23, existsSync as existsSync12 } from "node:fs";
|
|
57627
57714
|
import { join as join20 } from "node:path";
|
|
57628
57715
|
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
57629
57716
|
var ROOT11 = join20(fileURLToPath13(new URL(".", import.meta.url)), "..");
|
|
57630
57717
|
function readVersion() {
|
|
57631
57718
|
try {
|
|
57632
57719
|
const p = join20(ROOT11, "package.json");
|
|
57633
|
-
if (
|
|
57720
|
+
if (existsSync12(p))
|
|
57634
57721
|
return JSON.parse(readFileSync23(p, "utf8")).version ?? "1.0.0";
|
|
57635
57722
|
} catch {}
|
|
57636
57723
|
return "1.0.0";
|
|
@@ -57662,7 +57749,7 @@ async function main() {
|
|
|
57662
57749
|
return;
|
|
57663
57750
|
}
|
|
57664
57751
|
if (args[0] === "doctor") {
|
|
57665
|
-
runDoctor();
|
|
57752
|
+
await runDoctor();
|
|
57666
57753
|
return;
|
|
57667
57754
|
}
|
|
57668
57755
|
if (args[0] === "install") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.9",
|
|
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": {
|