blitzstrike 2.4.8 → 2.4.10

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 CHANGED
@@ -5,6 +5,34 @@ 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.10] — 2026-09-14
9
+
10
+ ### Added — auto-install external MCP servers to every agent
11
+
12
+ - chrome-devtools-mcp is now flagged `installable` in the catalog: `install-tools`
13
+ / `install_all_tools` provision it (npm i -g) instead of skipping all MCP
14
+ servers. burp-suite-mcp stays skipped (GUI/daemon — build + run manually).
15
+ - `blitzstrike install` (register to every agent) now also auto-installs
16
+ chrome-devtools-mcp so all agents can drive a real browser immediately.
17
+
18
+ ### Fixed — doctor git stderr leak
19
+
20
+ - `validateRelease` suppresses git/npm stderr, so `blitzstrike doctor` no longer
21
+ prints a stray `fatal: not a git repository` / `No tags can describe` line when
22
+ run outside a git checkout.
23
+
24
+ ## [2.4.9] — 2026-09-14
25
+
26
+ ### Added — evidence-first enforcement (findings are never born empty)
27
+
28
+ - `makeFinding` + `finding_create` now accept `evidence` at creation — a finding
29
+ can carry its observed artifact (headers/URL/response) the moment it is
30
+ created, instead of relying on a separate attach call that gets skipped.
31
+ - `finding_create` warns when a finding is created with no evidence.
32
+ - `reportMarkdown` now flags evidence-less findings with an explicit
33
+ "Evidence-first violation" block (ids listed) — detection without proof is
34
+ surfaced instead of silently shipped.
35
+
8
36
  ## [2.4.8] — 2026-09-14
9
37
 
10
38
  ### Added — MCP integration health checks
package/dist/index.js CHANGED
@@ -7633,9 +7633,9 @@ function ensureToolAsync(name, timeoutMs = 120000) {
7633
7633
  }
7634
7634
  async function installAllTools(opts = {}) {
7635
7635
  const tools = loadTools();
7636
- const mcp = tools.filter((t) => t.category === "mcp-server").map((t) => t.name);
7636
+ const skippedMcp = tools.filter((t) => t.category === "mcp-server" && t.installable !== true).map((t) => t.name);
7637
7637
  const cat = opts.category?.toLowerCase();
7638
- const targets = tools.filter((t) => t.category !== "mcp-server" && (!cat || t.category === cat));
7638
+ const targets = tools.filter((t) => (t.category !== "mcp-server" || t.installable === true) && (!cat || t.category === cat));
7639
7639
  const concurrency = Math.max(1, opts.concurrency ?? 4);
7640
7640
  const results = [];
7641
7641
  let idx = 0;
@@ -7657,7 +7657,7 @@ async function installAllTools(opts = {}) {
7657
7657
  installed,
7658
7658
  already_installed: already,
7659
7659
  failed,
7660
- skipped_mcp_servers: mcp,
7660
+ skipped_mcp_servers: skippedMcp,
7661
7661
  results
7662
7662
  };
7663
7663
  }
@@ -15579,13 +15579,13 @@ function validateRelease() {
15579
15579
  checks.push({ name: "CHANGELOG version", ok: false, detail: "CHANGELOG.md not found" });
15580
15580
  }
15581
15581
  try {
15582
- const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8" }).trim();
15582
+ const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
15583
15583
  checks.push({ name: "Git tag", ok: tag === `v${VERSION}`, detail: tag, soft: true });
15584
15584
  } catch {
15585
- checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git)", soft: true });
15585
+ checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git/tags)", soft: true });
15586
15586
  }
15587
15587
  try {
15588
- const published = execSync4("npm view blitzstrike version", { encoding: "utf8", timeout: 8000 }).trim();
15588
+ const published = execSync4("npm view blitzstrike version", { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "ignore"] }).trim();
15589
15589
  checks.push({ name: "npm registry version", ok: published === VERSION, detail: published, soft: true });
15590
15590
  } catch {
15591
15591
  checks.push({ name: "npm registry version", ok: true, detail: "unavailable (offline)", soft: true });
@@ -15958,6 +15958,11 @@ Agent-specific formats: see docs/installation.md`);
15958
15958
  console.log(`
15959
15959
  Registered with ${ok}/${installed.length} agent(s).`);
15960
15960
  console.log("Restart your agent, then call `run_engagement` or any `blitzstrike` tool.");
15961
+ console.log(`
15962
+ External MCP servers:`);
15963
+ const devtools = ensureTool("chrome-devtools-mcp");
15964
+ console.log(` ${devtools.installed ? "OK " : "FAIL"} chrome-devtools-mcp ${devtools.installed ? "installed (browser MCP ready for all agents)" : devtools.command ?? "install failed"}`);
15965
+ console.log(` SKIP burp-suite-mcp GUI/daemon — build manually (PortSwigger/mcp-server) + load in Burp (connect, not install)`);
15961
15966
  }
15962
15967
  async function runInstallTools(args) {
15963
15968
  const getArg = (flag) => {
@@ -15975,9 +15980,9 @@ async function runInstallTools(args) {
15975
15980
  await Promise.resolve().then(() => init_catalog());
15976
15981
  if (dryRun) {
15977
15982
  const tools = loadTools();
15978
- const mcp = tools.filter((t) => t.category === "mcp-server");
15979
- const targets = tools.filter((t) => t.category !== "mcp-server" && (!category || t.category === category.toLowerCase()));
15980
- console.log(`Dry run — would attempt ${targets.length} tool(s); ${mcp.length} MCP server(s) skipped (connect, not install):`);
15983
+ const mcp = tools.filter((t) => t.category === "mcp-server" && t.installable !== true);
15984
+ const targets = tools.filter((t) => (t.category !== "mcp-server" || t.installable === true) && (!category || t.category === category.toLowerCase()));
15985
+ console.log(`Dry run — would attempt ${targets.length} tool(s); ${mcp.length} MCP server(s) skipped (GUI/daemon — connect, not install):`);
15981
15986
  for (const t of targets)
15982
15987
  console.log(` ${t.category.padEnd(11)} ${t.name}`);
15983
15988
  return;
@@ -31064,7 +31069,7 @@ function makeFinding(input) {
31064
31069
  const status = input.status ?? "detected";
31065
31070
  const factors = confidenceFactorsFor(input);
31066
31071
  const confidence = computeConfidence(factors);
31067
- const finding = {
31072
+ let finding = {
31068
31073
  id: nextFindingId(),
31069
31074
  status,
31070
31075
  title: input.title,
@@ -31087,6 +31092,16 @@ function makeFinding(input) {
31087
31092
  remediation: {},
31088
31093
  timestamps: { created: now, updated: now }
31089
31094
  };
31095
+ if (input.evidence?.length) {
31096
+ for (const e of input.evidence) {
31097
+ const ev = makeEvidence({
31098
+ type: e.type,
31099
+ description: e.description,
31100
+ artifacts: e.content ? [{ name: "artifact", kind: "evidence", content: e.content }] : []
31101
+ });
31102
+ finding = attachEvidence(finding, ev);
31103
+ }
31104
+ }
31090
31105
  recordAudit("finding_created", { tool: "eagle-eye", target: input.target.host ?? input.target.endpoint, result: status, correlationId: finding.id });
31091
31106
  return finding;
31092
31107
  }
@@ -33009,11 +33024,15 @@ function summarize(findings) {
33009
33024
  const byStatus = {};
33010
33025
  const bySeverity = {};
33011
33026
  let evidenceArtifacts = 0;
33027
+ let evidenceLess = 0;
33012
33028
  for (const f of findings) {
33013
33029
  byStatus[f.status ?? "detected"] = (byStatus[f.status ?? "detected"] ?? 0) + 1;
33014
33030
  const sev = f.classification?.severity ?? "informational";
33015
33031
  bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
33016
- evidenceArtifacts += f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
33032
+ const arts = f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
33033
+ evidenceArtifacts += arts;
33034
+ if ((f.evidence ?? []).length === 0)
33035
+ evidenceLess += 1;
33017
33036
  }
33018
33037
  const groups = dedupFindings(findings);
33019
33038
  return {
@@ -33023,7 +33042,8 @@ function summarize(findings) {
33023
33042
  by_severity: bySeverity,
33024
33043
  confirmed: byStatus["confirmed"] ?? 0,
33025
33044
  false_positives: byStatus["false_positive"] ?? 0,
33026
- evidence_artifacts: evidenceArtifacts
33045
+ evidence_artifacts: evidenceArtifacts,
33046
+ evidence_less_findings: evidenceLess
33027
33047
  };
33028
33048
  }
33029
33049
  function reportMarkdown(findings, opts = {}) {
@@ -33047,6 +33067,11 @@ function reportMarkdown(findings, opts = {}) {
33047
33067
  }
33048
33068
  lines.push(`Detailed findings, reproduction steps, impact, and remediation follow in section 5.`);
33049
33069
  lines.push("");
33070
+ if (summary.evidence_less_findings > 0) {
33071
+ const evLess = findings.filter((f) => (f.evidence ?? []).length === 0).map((f) => f.id).join(", ");
33072
+ 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.`);
33073
+ lines.push("");
33074
+ }
33050
33075
  lines.push("## 2. Scope");
33051
33076
  lines.push("");
33052
33077
  lines.push(`- **Target(s):** ${scope}`);
@@ -57170,7 +57195,7 @@ ${statusLine}` : statusLine },
57170
57195
  });
57171
57196
  server.registerTool("finding_create", {
57172
57197
  title: "Create a canonical finding",
57173
- description: "FINDINGS: create a canonical, evidence-first finding. Severity describes impact; confidence (deterministic) describes certainty. Default status=detected (hypothesis-pending).",
57198
+ 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).",
57174
57199
  inputSchema: {
57175
57200
  title: string2().describe("Finding title"),
57176
57201
  target_type: string2().optional().describe("Target type (web/api/source/mobile/network/other)"),
@@ -57182,9 +57207,18 @@ ${statusLine}` : statusLine },
57182
57207
  source_name: string2().describe("Source name"),
57183
57208
  sink_type: string2().describe("Sink type (e.g. sql_execution, command_execution, file_operations)"),
57184
57209
  sink_symbol: string2().optional().describe("Sink symbol (e.g. '->query(')"),
57185
- chain_id: string2().optional().describe("Escalation chain id")
57210
+ chain_id: string2().optional().describe("Escalation chain id"),
57211
+ evidence: string2().optional().describe("JSON array of evidence entries: [{type, description, content?}] — attached at creation (redacted + SHA-256 tagged)")
57212
+ }
57213
+ }, async ({ title, target_type, host, endpoint, severity, cwe, source_type, source_name, sink_type, sink_symbol, chain_id, evidence }) => {
57214
+ let evs = [];
57215
+ if (evidence) {
57216
+ try {
57217
+ evs = JSON.parse(evidence);
57218
+ } catch {
57219
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid evidence JSON" }) }] };
57220
+ }
57186
57221
  }
57187
- }, async ({ title, target_type, host, endpoint, severity, cwe, source_type, source_name, sink_type, sink_symbol, chain_id }) => {
57188
57222
  const f = makeFinding({
57189
57223
  title,
57190
57224
  target: { type: target_type ?? "web", host, endpoint },
@@ -57193,9 +57227,19 @@ ${statusLine}` : statusLine },
57193
57227
  source: { type: source_type, name: source_name },
57194
57228
  sink: { type: sink_type, symbol: sink_symbol },
57195
57229
  chainId: chain_id ?? null,
57196
- status: "detected"
57230
+ status: "detected",
57231
+ evidence: evs
57197
57232
  });
57198
- return { content: [{ type: "text", text: JSON.stringify(f) }] };
57233
+ const empty = (f.evidence ?? []).length === 0;
57234
+ return {
57235
+ content: [{
57236
+ type: "text",
57237
+ text: JSON.stringify({
57238
+ ...f,
57239
+ _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
57240
+ })
57241
+ }]
57242
+ };
57199
57243
  });
57200
57244
  server.registerTool("finding_transition", {
57201
57245
  title: "Advance a finding lifecycle",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.8",
3
+ "version": "2.4.10",
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": {