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 +21 -0
- package/README.md +3 -0
- package/dist/index.js +39 -26
- package/package.json +1 -1
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
15532
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
15524
15533
|
function which(cmd) {
|
|
15525
|
-
return
|
|
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
|
|
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 =
|
|
31696
|
+
const bin = chromeDevtoolsCommand();
|
|
31678
31697
|
if (!bin) {
|
|
31679
|
-
return { ok: false, error: "chrome-devtools-mcp not
|
|
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.
|
|
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": {
|