blitzstrike 2.4.4 → 2.4.5

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,18 @@ 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.5] — 2026-09-14
9
+
10
+ ### Added — responsible-disclosure header (X-HackerOne-Research)
11
+
12
+ - New `H1_USERNAME` env var: when set, every outbound security-testing request
13
+ (STRIKE verification, live recon, active scan, advanced checks, leak-source
14
+ fetch) carries `X-HackerOne-Research: <username>` so targets/triagers can
15
+ identify the researcher.
16
+ - `src/http.ts` central helper (`researchHeaders` / `withResearchHeaders` /
17
+ `h1Username`); injected across strike, live-recon, active, orchestrator, server.
18
+ - `blitzstrike doctor` now reports the HackerOne research-header status.
19
+
8
20
  ## [2.4.4] — 2026-09-14
9
21
 
10
22
  ### 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";
@@ -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
  });
@@ -31940,7 +31956,6 @@ async function executeChainSteps(f, steps) {
31940
31956
  }
31941
31957
  return results;
31942
31958
  }
31943
-
31944
31959
  // src/orchestration.ts
31945
31960
  var STATE_MACHINE = [
31946
31961
  { state: "scope_check", description: "Validate target against scope + risk policy.", recommended_tools: ["scope_check"], required_evidence: ["scope_decision"], next: ["plan"], confidence: "high" },
@@ -33161,7 +33176,7 @@ async function httpGet(url, opts = {}) {
33161
33176
  const controller = new AbortController;
33162
33177
  const t = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
33163
33178
  try {
33164
- const res = await fetch(url, { signal: controller.signal, redirect: opts.redirect ?? "follow" });
33179
+ const res = await fetch(url, { signal: controller.signal, redirect: opts.redirect ?? "follow", headers: researchHeaders() });
33165
33180
  const body = await res.text();
33166
33181
  const headers = {};
33167
33182
  res.headers.forEach((v, k) => {
@@ -33937,7 +33952,7 @@ var PATH_PARAMS = ["file", "path", "page", "template", "lang", "include", "doc",
33937
33952
  var EVIL_HOST = "evil-blitz.example.com";
33938
33953
  async function httpGet2(u, extraHeaders) {
33939
33954
  try {
33940
- const res = await fetch(u, { redirect: "manual", headers: extraHeaders, signal: AbortSignal.timeout(1e4) });
33955
+ const res = await fetch(u, { redirect: "manual", headers: { ...researchHeaders(), ...extraHeaders ?? {} }, signal: AbortSignal.timeout(1e4) });
33941
33956
  const body = await res.text();
33942
33957
  const h = {};
33943
33958
  res.headers.forEach((v, k) => {
@@ -34068,7 +34083,7 @@ async function checkCrlf(base, p) {
34068
34083
  async function checkHostHeader(url) {
34069
34084
  try {
34070
34085
  const u = new URL(url);
34071
- const res = await fetch(u, { redirect: "manual", headers: { Host: HOST_HEADER_MARKER } });
34086
+ const res = await fetch(u, { redirect: "manual", headers: { ...researchHeaders(), Host: HOST_HEADER_MARKER } });
34072
34087
  const body = await res.text();
34073
34088
  const loc = res.headers.get("location") ?? "";
34074
34089
  const setCookie = res.headers.get("set-cookie") ?? "";
@@ -34081,7 +34096,7 @@ async function checkHostHeader(url) {
34081
34096
  async function checkJwt(url) {
34082
34097
  const out = [];
34083
34098
  try {
34084
- const res = await fetch(url, { redirect: "follow" });
34099
+ const res = await fetch(url, { redirect: "follow", headers: researchHeaders() });
34085
34100
  const body = await res.text();
34086
34101
  const setCookie = res.headers.get("set-cookie") ?? "";
34087
34102
  const jwtRe = /eyJ[a-zA-Z0-9_-]{5,}\.eyJ[a-zA-Z0-9_-]{5,}\.[a-zA-Z0-9_-]{5,}/g;
@@ -34092,7 +34107,7 @@ async function checkJwt(url) {
34092
34107
  const [h, p] = token.split(".");
34093
34108
  const header = JSON.parse(Buffer.from(h, "base64url").toString());
34094
34109
  const noneToken = `${h}.${p}.`;
34095
- const noneRes = await fetch(url, { headers: { Authorization: `Bearer ${noneToken}` } });
34110
+ const noneRes = await fetch(url, { headers: { ...researchHeaders(), Authorization: `Bearer ${noneToken}` } });
34096
34111
  if (noneRes.status !== 401 && noneRes.status !== 403) {
34097
34112
  out.push({ type: `alg:none accepted (${header.alg})`, token: noneToken.slice(0, 40), accepted: true });
34098
34113
  } else if (header.alg?.toUpperCase().startsWith("RS") || header.alg?.toUpperCase().startsWith("ES")) {
@@ -34699,7 +34714,7 @@ async function httpFetch(url, init) {
34699
34714
  const controller = new AbortController;
34700
34715
  const t = setTimeout(() => controller.abort(), TIMEOUT_MS2);
34701
34716
  try {
34702
- const res = await fetch(url, { ...init, signal: controller.signal, redirect: "follow" });
34717
+ const res = await fetch(url, { ...init, signal: controller.signal, redirect: "follow", headers: { ...researchHeaders(), ...init?.headers ?? {} } });
34703
34718
  const body = await res.text();
34704
34719
  const headers = {};
34705
34720
  res.headers.forEach((v, k) => {
@@ -56300,7 +56315,7 @@ function createServer() {
56300
56315
  let body = text ?? "";
56301
56316
  if (url && !body) {
56302
56317
  try {
56303
- const r = await fetch(url, { redirect: "follow" });
56318
+ const r = await fetch(url, { redirect: "follow", headers: researchHeaders() });
56304
56319
  body = await r.text();
56305
56320
  } catch {
56306
56321
  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.5",
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": {