blitzstrike 2.4.9 → 2.4.11

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.11] — 2026-09-14
9
+
10
+ ### Fixed — bulk-install no longer pollutes the caller's cwd
11
+
12
+ - `install-tools` / `install_all_tools` now run every install command in a fixed
13
+ tools directory (`~/.blitzstrike/tools`, override `BLITZSTRIKE_TOOLS_DIR`), so
14
+ `git clone`-style tools (massdns, phpggc, radare2, testssl.sh, …) no longer
15
+ land in whatever directory you happened to run from.
16
+ - Added those cloned tool dirs to `.gitignore`.
17
+ - `blitzstrike install` now prints a hint pointing to `blitzstrike install-tools`
18
+ for the full 140-tool catalog.
19
+
20
+ ## [2.4.10] — 2026-09-14
21
+
22
+ ### Added — auto-install external MCP servers to every agent
23
+
24
+ - chrome-devtools-mcp is now flagged `installable` in the catalog: `install-tools`
25
+ / `install_all_tools` provision it (npm i -g) instead of skipping all MCP
26
+ servers. burp-suite-mcp stays skipped (GUI/daemon — build + run manually).
27
+ - `blitzstrike install` (register to every agent) now also auto-installs
28
+ chrome-devtools-mcp so all agents can drive a real browser immediately.
29
+
30
+ ### Fixed — doctor git stderr leak
31
+
32
+ - `validateRelease` suppresses git/npm stderr, so `blitzstrike doctor` no longer
33
+ prints a stray `fatal: not a git repository` / `No tags can describe` line when
34
+ run outside a git checkout.
35
+
8
36
  ## [2.4.9] — 2026-09-14
9
37
 
10
38
  ### Added — evidence-first enforcement (findings are never born empty)
package/dist/index.js CHANGED
@@ -7398,9 +7398,10 @@ function h1Username() {
7398
7398
  }
7399
7399
 
7400
7400
  // src/catalog.ts
7401
- import { readFileSync as readFileSync6, readdirSync as readdirSync2 } from "node:fs";
7401
+ import { readFileSync as readFileSync6, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "node:fs";
7402
7402
  import { join as join6 } from "node:path";
7403
7403
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7404
+ import { homedir as homedir3 } from "node:os";
7404
7405
  import { spawnSync, spawn } from "node:child_process";
7405
7406
  function loadTools() {
7406
7407
  if (_toolsCache)
@@ -7517,7 +7518,8 @@ function ensureTool(name) {
7517
7518
  if (!cmd) {
7518
7519
  return { name: t.name, installed: false, action: "none", note: `no install command for ${platform}` };
7519
7520
  }
7520
- const r = spawnSync(cmd, { shell: true, timeout: 120000 });
7521
+ mkdirSync3(TOOLS_DIR, { recursive: true });
7522
+ const r = spawnSync(cmd, { shell: true, timeout: 120000, cwd: TOOLS_DIR });
7521
7523
  const ok = r.status === 0;
7522
7524
  return {
7523
7525
  name: t.name,
@@ -7616,7 +7618,8 @@ function ensureToolAsync(name, timeoutMs = 120000) {
7616
7618
  resolve({ name: t.name, installed: false, action: "none", note: "no install command for this platform" });
7617
7619
  return;
7618
7620
  }
7619
- const inst = spawn(cmd, { shell: true, timeout: timeoutMs });
7621
+ mkdirSync3(TOOLS_DIR, { recursive: true });
7622
+ const inst = spawn(cmd, { shell: true, timeout: timeoutMs, cwd: TOOLS_DIR });
7620
7623
  let out = "";
7621
7624
  inst.stdout?.on("data", (d) => {
7622
7625
  out += d.toString();
@@ -7633,9 +7636,9 @@ function ensureToolAsync(name, timeoutMs = 120000) {
7633
7636
  }
7634
7637
  async function installAllTools(opts = {}) {
7635
7638
  const tools = loadTools();
7636
- const mcp = tools.filter((t) => t.category === "mcp-server").map((t) => t.name);
7639
+ const skippedMcp = tools.filter((t) => t.category === "mcp-server" && t.installable !== true).map((t) => t.name);
7637
7640
  const cat = opts.category?.toLowerCase();
7638
- const targets = tools.filter((t) => t.category !== "mcp-server" && (!cat || t.category === cat));
7641
+ const targets = tools.filter((t) => (t.category !== "mcp-server" || t.installable === true) && (!cat || t.category === cat));
7639
7642
  const concurrency = Math.max(1, opts.concurrency ?? 4);
7640
7643
  const results = [];
7641
7644
  let idx = 0;
@@ -7657,14 +7660,15 @@ async function installAllTools(opts = {}) {
7657
7660
  installed,
7658
7661
  already_installed: already,
7659
7662
  failed,
7660
- skipped_mcp_servers: mcp,
7663
+ skipped_mcp_servers: skippedMcp,
7661
7664
  results
7662
7665
  };
7663
7666
  }
7664
- var ROOT2, _toolsCache = null, _skillsCache = null;
7667
+ var ROOT2, TOOLS_DIR, _toolsCache = null, _skillsCache = null;
7665
7668
  var init_catalog = __esm(() => {
7666
7669
  init_manuals();
7667
7670
  ROOT2 = join6(fileURLToPath3(new URL(".", import.meta.url)), "..");
7671
+ TOOLS_DIR = process.env.BLITZSTRIKE_TOOLS_DIR ?? join6(homedir3(), ".blitzstrike", "tools");
7668
7672
  });
7669
7673
 
7670
7674
  // src/burp-mcp.ts
@@ -15412,9 +15416,9 @@ async function checkMcpServers() {
15412
15416
  var init_mcp_status = () => {};
15413
15417
 
15414
15418
  // src/sync.ts
15415
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, readFileSync as readFileSync18, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
15419
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, readFileSync as readFileSync18, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
15416
15420
  import { join as join16 } from "node:path";
15417
- import { homedir as homedir5 } from "node:os";
15421
+ import { homedir as homedir6 } from "node:os";
15418
15422
  import { fileURLToPath as fileURLToPath9 } from "node:url";
15419
15423
  import { spawnSync as spawnSync4 } from "node:child_process";
15420
15424
  function dirExists(p) {
@@ -15443,7 +15447,7 @@ function countFiles(dir) {
15443
15447
  }
15444
15448
  function syncData() {
15445
15449
  if (!dirExists(DATA_ROOT2))
15446
- mkdirSync4(DATA_ROOT2, { recursive: true });
15450
+ mkdirSync5(DATA_ROOT2, { recursive: true });
15447
15451
  const payloadsPath = join16(DATA_ROOT2, "payloads");
15448
15452
  const templatesPath = join16(DATA_ROOT2, "templates");
15449
15453
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
@@ -15501,7 +15505,7 @@ function copyDir(src, dst) {
15501
15505
  }
15502
15506
  }
15503
15507
  function manualCopy(src, dst) {
15504
- mkdirSync4(dst, { recursive: true });
15508
+ mkdirSync5(dst, { recursive: true });
15505
15509
  for (const e of readdirSync7(src, { withFileTypes: true })) {
15506
15510
  const s = join16(src, e.name);
15507
15511
  const d = join16(dst, e.name);
@@ -15514,7 +15518,7 @@ function manualCopy(src, dst) {
15514
15518
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
15515
15519
  var init_sync = __esm(() => {
15516
15520
  ROOT7 = join16(fileURLToPath9(new URL(".", import.meta.url)), "..");
15517
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join16(homedir5(), ".blitzstrike", "data");
15521
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join16(homedir6(), ".blitzstrike", "data");
15518
15522
  });
15519
15523
 
15520
15524
  // src/update.ts
@@ -15579,13 +15583,13 @@ function validateRelease() {
15579
15583
  checks.push({ name: "CHANGELOG version", ok: false, detail: "CHANGELOG.md not found" });
15580
15584
  }
15581
15585
  try {
15582
- const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8" }).trim();
15586
+ const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
15583
15587
  checks.push({ name: "Git tag", ok: tag === `v${VERSION}`, detail: tag, soft: true });
15584
15588
  } catch {
15585
- checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git)", soft: true });
15589
+ checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git/tags)", soft: true });
15586
15590
  }
15587
15591
  try {
15588
- const published = execSync4("npm view blitzstrike version", { encoding: "utf8", timeout: 8000 }).trim();
15592
+ const published = execSync4("npm view blitzstrike version", { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "ignore"] }).trim();
15589
15593
  checks.push({ name: "npm registry version", ok: published === VERSION, detail: published, soft: true });
15590
15594
  } catch {
15591
15595
  checks.push({ name: "npm registry version", ok: true, detail: "unavailable (offline)", soft: true });
@@ -15641,9 +15645,9 @@ var init_dependency = __esm(() => {
15641
15645
  });
15642
15646
 
15643
15647
  // src/cli.ts
15644
- import { readFileSync as readFileSync22, existsSync as existsSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "node:fs";
15648
+ import { readFileSync as readFileSync22, existsSync as existsSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "node:fs";
15645
15649
  import { join as join19, dirname as dirname2 } from "node:path";
15646
- import { homedir as homedir6 } from "node:os";
15650
+ import { homedir as homedir7 } from "node:os";
15647
15651
  import { fileURLToPath as fileURLToPath12 } from "node:url";
15648
15652
  import { spawnSync as spawnSync5 } from "node:child_process";
15649
15653
  function which(cmd) {
@@ -15761,7 +15765,7 @@ function jsonMcpServersWrite(p) {
15761
15765
  return (command, args) => {
15762
15766
  const dir = dirname2(p);
15763
15767
  if (!existsSync11(dir))
15764
- mkdirSync5(dir, { recursive: true });
15768
+ mkdirSync6(dir, { recursive: true });
15765
15769
  const existing = readJson(p) ?? {};
15766
15770
  existing.mcpServers = {
15767
15771
  ...existing.mcpServers ?? {},
@@ -15774,7 +15778,7 @@ function opencodeWrite(p) {
15774
15778
  return (command, args) => {
15775
15779
  const dir = dirname2(p);
15776
15780
  if (!existsSync11(dir))
15777
- mkdirSync5(dir, { recursive: true });
15781
+ mkdirSync6(dir, { recursive: true });
15778
15782
  const existing = readJson(p) ?? {};
15779
15783
  existing.mcp = {
15780
15784
  ...existing.mcp ?? {},
@@ -15786,11 +15790,11 @@ function opencodeWrite(p) {
15786
15790
  }
15787
15791
  function copyOpenCodeAgents() {
15788
15792
  const srcDir = join19(ROOT10, "opencode-agents");
15789
- const dstDir = join19(homedir6(), ".config", "opencode", "agents");
15793
+ const dstDir = join19(homedir7(), ".config", "opencode", "agents");
15790
15794
  if (!existsSync11(srcDir))
15791
15795
  return;
15792
15796
  if (!existsSync11(dstDir))
15793
- mkdirSync5(dstDir, { recursive: true });
15797
+ mkdirSync6(dstDir, { recursive: true });
15794
15798
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
15795
15799
  const src = join19(srcDir, f);
15796
15800
  const dst = join19(dstDir, f);
@@ -15803,7 +15807,7 @@ function codexWrite(p) {
15803
15807
  return (command, args) => {
15804
15808
  const dir = dirname2(p);
15805
15809
  if (!existsSync11(dir))
15806
- mkdirSync5(dir, { recursive: true });
15810
+ mkdirSync6(dir, { recursive: true });
15807
15811
  let existing = existsSync11(p) ? readFileSync22(p, "utf8") : "";
15808
15812
  if (!existing.trimEnd().endsWith(`
15809
15813
  `))
@@ -15822,7 +15826,7 @@ function hermesWrite(p) {
15822
15826
  return (command, args) => {
15823
15827
  const dir = dirname2(p);
15824
15828
  if (!existsSync11(dir))
15825
- mkdirSync5(dir, { recursive: true });
15829
+ mkdirSync6(dir, { recursive: true });
15826
15830
  let existing = existsSync11(p) ? readFileSync22(p, "utf8") : "";
15827
15831
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
15828
15832
  if (!existing.trimEnd().endsWith(`
@@ -15848,7 +15852,7 @@ mcp_servers:
15848
15852
  };
15849
15853
  }
15850
15854
  function detectAgents() {
15851
- const home = homedir6();
15855
+ const home = homedir7();
15852
15856
  const agents = [];
15853
15857
  const claudeJson = join19(home, ".claude.json");
15854
15858
  agents.push({
@@ -15958,6 +15962,13 @@ Agent-specific formats: see docs/installation.md`);
15958
15962
  console.log(`
15959
15963
  Registered with ${ok}/${installed.length} agent(s).`);
15960
15964
  console.log("Restart your agent, then call `run_engagement` or any `blitzstrike` tool.");
15965
+ console.log(`
15966
+ External MCP servers:`);
15967
+ const devtools = ensureTool("chrome-devtools-mcp");
15968
+ console.log(` ${devtools.installed ? "OK " : "FAIL"} chrome-devtools-mcp ${devtools.installed ? "installed (browser MCP ready for all agents)" : devtools.command ?? "install failed"}`);
15969
+ console.log(` SKIP burp-suite-mcp GUI/daemon — build manually (PortSwigger/mcp-server) + load in Burp (connect, not install)`);
15970
+ console.log(`
15971
+ To install ALL 140 catalog tools (139 CLI + chrome-devtools-mcp), run: blitzstrike install-tools`);
15961
15972
  }
15962
15973
  async function runInstallTools(args) {
15963
15974
  const getArg = (flag) => {
@@ -15975,9 +15986,9 @@ async function runInstallTools(args) {
15975
15986
  await Promise.resolve().then(() => init_catalog());
15976
15987
  if (dryRun) {
15977
15988
  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):`);
15989
+ const mcp = tools.filter((t) => t.category === "mcp-server" && t.installable !== true);
15990
+ const targets = tools.filter((t) => (t.category !== "mcp-server" || t.installable === true) && (!category || t.category === category.toLowerCase()));
15991
+ console.log(`Dry run — would attempt ${targets.length} tool(s); ${mcp.length} MCP server(s) skipped (GUI/daemon — connect, not install):`);
15981
15992
  for (const t of targets)
15982
15993
  console.log(` ${t.category.padEnd(11)} ${t.name}`);
15983
15994
  return;
@@ -31773,7 +31784,7 @@ async function browserValidate(check, target, payload) {
31773
31784
  import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
31774
31785
  import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
31775
31786
  import { join as join7 } from "node:path";
31776
- import { homedir as homedir3 } from "node:os";
31787
+ import { homedir as homedir4 } from "node:os";
31777
31788
  function mcpClient(command, args) {
31778
31789
  const proc = spawn2(command, args, { stdio: ["pipe", "pipe", "pipe"] });
31779
31790
  let id = 0;
@@ -31849,7 +31860,7 @@ function findChromium() {
31849
31860
  return p;
31850
31861
  }
31851
31862
  }
31852
- const pw = join7(homedir3(), ".cache", "ms-playwright");
31863
+ const pw = join7(homedir4(), ".cache", "ms-playwright");
31853
31864
  if (existsSync4(pw)) {
31854
31865
  for (const d of readdirSync3(pw)) {
31855
31866
  if (!d.startsWith("chromium"))
@@ -32179,7 +32190,7 @@ import { readFileSync as readFileSync7, existsSync as existsSync5 } from "node:f
32179
32190
  import { join as join8 } from "node:path";
32180
32191
  import { fileURLToPath as fileURLToPath4 } from "node:url";
32181
32192
  import { readdirSync as readdirSync4 } from "node:fs";
32182
- import { homedir as homedir4 } from "node:os";
32193
+ import { homedir as homedir5 } from "node:os";
32183
32194
  var ROOT3 = join8(fileURLToPath4(new URL(".", import.meta.url)), "..");
32184
32195
  var INTEL = join8(ROOT3, "intelligence");
32185
32196
  function loadJson(name) {
@@ -32410,7 +32421,7 @@ function attackPlan(target, tech = [], params = []) {
32410
32421
  }
32411
32422
  var PACKAGE_PAYLOADS = join8(ROOT3, "payloads");
32412
32423
  var PACKAGE_TEMPLATES = join8(ROOT3, "templates");
32413
- var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join8(homedir4(), ".blitzstrike", "data");
32424
+ var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join8(homedir5(), ".blitzstrike", "data");
32414
32425
  function payloadsDir() {
32415
32426
  if (existsSync5(PACKAGE_PAYLOADS))
32416
32427
  return PACKAGE_PAYLOADS;
@@ -34730,7 +34741,7 @@ function compactState(phase, findings) {
34730
34741
  }
34731
34742
 
34732
34743
  // src/tasks.ts
34733
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "node:fs";
34744
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "node:fs";
34734
34745
  import { join as join10 } from "node:path";
34735
34746
  import { fileURLToPath as fileURLToPath6 } from "node:url";
34736
34747
  function tasksDir() {
@@ -34747,7 +34758,7 @@ function load(id) {
34747
34758
  }
34748
34759
  }
34749
34760
  function save(t) {
34750
- mkdirSync3(tasksDir(), { recursive: true });
34761
+ mkdirSync4(tasksDir(), { recursive: true });
34751
34762
  writeFileSync2(taskPath(t.task_id), JSON.stringify(t, null, 2));
34752
34763
  }
34753
34764
  function taskStart(id, objective, plan) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.9",
3
+ "version": "2.4.11",
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": {