blitzstrike 2.4.4 → 2.4.6

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,27 @@ 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.6] — 2026-09-14
9
+
10
+ ### Added — chrome-devtools-mcp auto-install (npx zero-install fallback)
11
+
12
+ - `drive_devtools` / `browser_devtools` now auto-fetch chrome-devtools-mcp via
13
+ `npx -y chrome-devtools-mcp@latest` when no global binary is present — no
14
+ manual `npm i -g` required. (burp-suite-mcp still can't be auto-installed:
15
+ it's a Java/GUI app that must be built + run manually.)
16
+
17
+ ## [2.4.5] — 2026-09-14
18
+
19
+ ### Added — responsible-disclosure header (X-HackerOne-Research)
20
+
21
+ - New `H1_USERNAME` env var: when set, every outbound security-testing request
22
+ (STRIKE verification, live recon, active scan, advanced checks, leak-source
23
+ fetch) carries `X-HackerOne-Research: <username>` so targets/triagers can
24
+ identify the researcher.
25
+ - `src/http.ts` central helper (`researchHeaders` / `withResearchHeaders` /
26
+ `h1Username`); injected across strike, live-recon, active, orchestrator, server.
27
+ - `blitzstrike doctor` now reports the HackerOne research-header status.
28
+
8
29
  ## [2.4.4] — 2026-09-14
9
30
 
10
31
  ### Added — automated publishing via npm trusted publishing (OIDC)
package/README.md CHANGED
@@ -347,6 +347,9 @@ Edit `chains.json` to add knowledge — never hardcode in source.
347
347
  |---|---|---|
348
348
  | `FOFA_EMAIL` | For `fofa_search` | FOFA account email |
349
349
  | `FOFA_KEY` | For `fofa_search` | FOFA API key |
350
+ | `H1_USERNAME` | Optional | Your HackerOne username. When set, every outbound security-testing request carries `X-HackerOne-Research: <username>` so targets/triagers can identify you (responsible-disclosure convention). |
351
+ | `BLITZSTRIKE_HOME` | Optional | Override the home directory (default `~/.blitzstrike`). |
352
+ | `BLITZSTRIKE_DATA` | Optional | Override the data-cache directory (default `~/.blitzstrike/data`). |
350
353
 
351
354
  All other tools need no credentials.
352
355
 
package/dist/index.js CHANGED
@@ -7388,6 +7388,15 @@ var init_version = __esm(() => {
7388
7388
  })();
7389
7389
  });
7390
7390
 
7391
+ // src/http.ts
7392
+ function researchHeaders() {
7393
+ const u = process.env.H1_USERNAME?.trim();
7394
+ return u ? { "X-HackerOne-Research": u } : {};
7395
+ }
7396
+ function h1Username() {
7397
+ return process.env.H1_USERNAME?.trim() ?? "";
7398
+ }
7399
+
7391
7400
  // src/catalog.ts
7392
7401
  import { readFileSync as readFileSync6, readdirSync as readdirSync2 } from "node:fs";
7393
7402
  import { join as join6 } from "node:path";
@@ -15291,7 +15300,7 @@ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, readFileSync
15291
15300
  import { join as join16 } from "node:path";
15292
15301
  import { homedir as homedir5 } from "node:os";
15293
15302
  import { fileURLToPath as fileURLToPath9 } from "node:url";
15294
- import { spawnSync as spawnSync2 } from "node:child_process";
15303
+ import { spawnSync as spawnSync3 } from "node:child_process";
15295
15304
  function dirExists(p) {
15296
15305
  try {
15297
15306
  return statSync4(p).isDirectory();
@@ -15342,14 +15351,14 @@ function syncData() {
15342
15351
  copyDir(localTemplates, templatesPath);
15343
15352
  templatesSynced = true;
15344
15353
  }
15345
- const gitOk = spawnSync2("git", ["--version"]).status === 0;
15354
+ const gitOk = spawnSync3("git", ["--version"]).status === 0;
15346
15355
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
15347
15356
  const tmp = join16(DATA_ROOT2, ".tmp-clone");
15348
15357
  if (dirExists(tmp))
15349
15358
  rmSync4(tmp, { recursive: true, force: true });
15350
- const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
15359
+ const r = spawnSync3("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
15351
15360
  if (r.status === 0) {
15352
- const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
15361
+ const sparse = spawnSync3("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
15353
15362
  if (sparse.status === 0) {
15354
15363
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
15355
15364
  copyDir(join16(tmp, "payloads"), payloadsPath);
@@ -15370,7 +15379,7 @@ function syncData() {
15370
15379
  };
15371
15380
  }
15372
15381
  function copyDir(src, dst) {
15373
- const r = spawnSync2("cp", ["-r", src, dst], { timeout: 120000 });
15382
+ const r = spawnSync3("cp", ["-r", src, dst], { timeout: 120000 });
15374
15383
  if (r.status !== 0) {
15375
15384
  manualCopy(src, dst);
15376
15385
  }
@@ -15520,9 +15529,9 @@ import { readFileSync as readFileSync22, existsSync as existsSync10, writeFileSy
15520
15529
  import { join as join19, dirname as dirname2 } from "node:path";
15521
15530
  import { homedir as homedir6 } from "node:os";
15522
15531
  import { fileURLToPath as fileURLToPath12 } from "node:url";
15523
- import { spawnSync as spawnSync3 } from "node:child_process";
15532
+ import { spawnSync as spawnSync4 } from "node:child_process";
15524
15533
  function which(cmd) {
15525
- return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
15534
+ return spawnSync4(`command -v ${cmd}`, { shell: true }).status === 0;
15526
15535
  }
15527
15536
  function installedCount() {
15528
15537
  const tools = loadTools();
@@ -15555,6 +15564,13 @@ function runDoctor() {
15555
15564
  detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
15556
15565
  fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
15557
15566
  });
15567
+ const h1 = h1Username();
15568
+ issues.push({
15569
+ name: "HackerOne research header",
15570
+ status: h1 ? "ok" : "warn",
15571
+ detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
15572
+ fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
15573
+ });
15558
15574
  const dataOk = existsSync10(join19(ROOT10, "chains.json")) && existsSync10(join19(ROOT10, "tools-catalog.json"));
15559
15575
  issues.push({
15560
15576
  name: "Data layers (chains + tools-catalog)",
@@ -31115,7 +31131,7 @@ async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEO
31115
31131
  const res = await fetch(url, {
31116
31132
  method,
31117
31133
  body: method === "POST" ? data : undefined,
31118
- headers: { "User-Agent": "blitzstrike/1.0", ...headers ?? {} },
31134
+ headers: { "User-Agent": "blitzstrike/1.0", ...researchHeaders(), ...headers ?? {} },
31119
31135
  signal: controller.signal,
31120
31136
  redirect: "follow"
31121
31137
  });
@@ -31571,7 +31587,7 @@ async function browserValidate(check, target, payload) {
31571
31587
  }
31572
31588
 
31573
31589
  // src/devtools.ts
31574
- import { spawn as spawn2 } from "node:child_process";
31590
+ import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
31575
31591
  import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
31576
31592
  import { join as join7 } from "node:path";
31577
31593
  import { homedir as homedir3 } from "node:os";
@@ -31662,11 +31678,14 @@ function findChromium() {
31662
31678
  }
31663
31679
  return null;
31664
31680
  }
31665
- function chromeDevtoolsBin() {
31681
+ function chromeDevtoolsCommand() {
31666
31682
  for (const c of ["/usr/bin/chrome-devtools-mcp", "/usr/local/bin/chrome-devtools-mcp"]) {
31667
31683
  if (existsSync4(c))
31668
31684
  return { command: c, args: [] };
31669
31685
  }
31686
+ const npx = spawnSync2("npx", ["--version"], { timeout: 8000 });
31687
+ if (npx.error === undefined)
31688
+ return { command: "npx", args: ["-y", "chrome-devtools-mcp@latest"] };
31670
31689
  return null;
31671
31690
  }
31672
31691
  function parsePageId(text) {
@@ -31674,17 +31693,12 @@ function parsePageId(text) {
31674
31693
  return m ? parseInt(m[1], 10) : 1;
31675
31694
  }
31676
31695
  async function driveChromeDevtools(target, timeoutMs = 25000) {
31677
- const bin = chromeDevtoolsBin();
31696
+ const bin = chromeDevtoolsCommand();
31678
31697
  if (!bin) {
31679
- return { ok: false, error: "chrome-devtools-mcp not installed — run: npm install -g chrome-devtools-mcp (or npx chrome-devtools-mcp@latest)" };
31698
+ return { ok: false, error: "chrome-devtools-mcp not available (no global binary and no npx) — install Node/npm first" };
31680
31699
  }
31681
31700
  const chrome = findChromium();
31682
- const args = [
31683
- "--headless",
31684
- "--isolated",
31685
- "--chromeArg=--no-sandbox",
31686
- "--chromeArg=--disable-gpu"
31687
- ];
31701
+ const args = [...bin.args, "--headless", "--isolated", "--chromeArg=--no-sandbox", "--chromeArg=--disable-gpu"];
31688
31702
  if (chrome)
31689
31703
  args.push(`--executablePath=${chrome}`);
31690
31704
  const client = mcpClient(bin.command, args);
@@ -31940,7 +31954,6 @@ async function executeChainSteps(f, steps) {
31940
31954
  }
31941
31955
  return results;
31942
31956
  }
31943
-
31944
31957
  // src/orchestration.ts
31945
31958
  var STATE_MACHINE = [
31946
31959
  { state: "scope_check", description: "Validate target against scope + risk policy.", recommended_tools: ["scope_check"], required_evidence: ["scope_decision"], next: ["plan"], confidence: "high" },
@@ -33161,7 +33174,7 @@ async function httpGet(url, opts = {}) {
33161
33174
  const controller = new AbortController;
33162
33175
  const t = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
33163
33176
  try {
33164
- const res = await fetch(url, { signal: controller.signal, redirect: opts.redirect ?? "follow" });
33177
+ const res = await fetch(url, { signal: controller.signal, redirect: opts.redirect ?? "follow", headers: researchHeaders() });
33165
33178
  const body = await res.text();
33166
33179
  const headers = {};
33167
33180
  res.headers.forEach((v, k) => {
@@ -33937,7 +33950,7 @@ var PATH_PARAMS = ["file", "path", "page", "template", "lang", "include", "doc",
33937
33950
  var EVIL_HOST = "evil-blitz.example.com";
33938
33951
  async function httpGet2(u, extraHeaders) {
33939
33952
  try {
33940
- const res = await fetch(u, { redirect: "manual", headers: extraHeaders, signal: AbortSignal.timeout(1e4) });
33953
+ const res = await fetch(u, { redirect: "manual", headers: { ...researchHeaders(), ...extraHeaders ?? {} }, signal: AbortSignal.timeout(1e4) });
33941
33954
  const body = await res.text();
33942
33955
  const h = {};
33943
33956
  res.headers.forEach((v, k) => {
@@ -34068,7 +34081,7 @@ async function checkCrlf(base, p) {
34068
34081
  async function checkHostHeader(url) {
34069
34082
  try {
34070
34083
  const u = new URL(url);
34071
- const res = await fetch(u, { redirect: "manual", headers: { Host: HOST_HEADER_MARKER } });
34084
+ const res = await fetch(u, { redirect: "manual", headers: { ...researchHeaders(), Host: HOST_HEADER_MARKER } });
34072
34085
  const body = await res.text();
34073
34086
  const loc = res.headers.get("location") ?? "";
34074
34087
  const setCookie = res.headers.get("set-cookie") ?? "";
@@ -34081,7 +34094,7 @@ async function checkHostHeader(url) {
34081
34094
  async function checkJwt(url) {
34082
34095
  const out = [];
34083
34096
  try {
34084
- const res = await fetch(url, { redirect: "follow" });
34097
+ const res = await fetch(url, { redirect: "follow", headers: researchHeaders() });
34085
34098
  const body = await res.text();
34086
34099
  const setCookie = res.headers.get("set-cookie") ?? "";
34087
34100
  const jwtRe = /eyJ[a-zA-Z0-9_-]{5,}\.eyJ[a-zA-Z0-9_-]{5,}\.[a-zA-Z0-9_-]{5,}/g;
@@ -34092,7 +34105,7 @@ async function checkJwt(url) {
34092
34105
  const [h, p] = token.split(".");
34093
34106
  const header = JSON.parse(Buffer.from(h, "base64url").toString());
34094
34107
  const noneToken = `${h}.${p}.`;
34095
- const noneRes = await fetch(url, { headers: { Authorization: `Bearer ${noneToken}` } });
34108
+ const noneRes = await fetch(url, { headers: { ...researchHeaders(), Authorization: `Bearer ${noneToken}` } });
34096
34109
  if (noneRes.status !== 401 && noneRes.status !== 403) {
34097
34110
  out.push({ type: `alg:none accepted (${header.alg})`, token: noneToken.slice(0, 40), accepted: true });
34098
34111
  } else if (header.alg?.toUpperCase().startsWith("RS") || header.alg?.toUpperCase().startsWith("ES")) {
@@ -34699,7 +34712,7 @@ async function httpFetch(url, init) {
34699
34712
  const controller = new AbortController;
34700
34713
  const t = setTimeout(() => controller.abort(), TIMEOUT_MS2);
34701
34714
  try {
34702
- const res = await fetch(url, { ...init, signal: controller.signal, redirect: "follow" });
34715
+ const res = await fetch(url, { ...init, signal: controller.signal, redirect: "follow", headers: { ...researchHeaders(), ...init?.headers ?? {} } });
34703
34716
  const body = await res.text();
34704
34717
  const headers = {};
34705
34718
  res.headers.forEach((v, k) => {
@@ -56300,7 +56313,7 @@ function createServer() {
56300
56313
  let body = text ?? "";
56301
56314
  if (url && !body) {
56302
56315
  try {
56303
- const r = await fetch(url, { redirect: "follow" });
56316
+ const r = await fetch(url, { redirect: "follow", headers: researchHeaders() });
56304
56317
  body = await r.text();
56305
56318
  } catch {
56306
56319
  return { content: [{ type: "text", text: JSON.stringify({ error: "fetch failed", url }) }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
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": {