blitzstrike 2.4.72 → 2.4.74

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.js +254 -99
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,36 @@ 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.74] — 2026-09-17
9
+
10
+ ### Fixed — live_recon tech fingerprint false-positive (benchmark-driven)
11
+
12
+ - Tech detection now uses header + cookie signals FIRST (authoritative), then
13
+ body regex as fallback, via a new `detectTech(headers, body)`:
14
+ - `x-powered-by: Express` / `connect.sid` cookie → nodejs (was missed).
15
+ - `laravel_session` / `XSRF-TOKEN` cookie → laravel.
16
+ - `PHPSESSID` → php, `csrftoken`+`sessionid` → django, `rack.session` → rails.
17
+ - Removed the over-broad `_token` / `csrf-token` → laravel body signature (those
18
+ are generic CSRF tokens, present in Node/Express apps too) — this caused the
19
+ Hackbot Arena labs03 (Node/Express) to be mis-flagged as "laravel".
20
+ - `gunicorn` no longer implies "flask" (it's a generic WSGI server shared by
21
+ Flask/Django/FastAPI); only `werkzeug` maps to flask.
22
+
23
+ ## [2.4.73] — 2026-09-17
24
+
25
+ ### Changed — crack_hash wordlist support (benchmark-driven fix)
26
+
27
+ - `crack_hash` gained a `wordlist` arg (comma-separated paths) + `maxCandidates`
28
+ arg, and `crackHash()` now STREAMS any supplied wordlist and AUTOLOADS the
29
+ standard seclists/rockyou locations (`/usr/share/wordlists/rockyou.txt`,
30
+ `/opt/seclists/.../rockyou-*.txt`, Common-Credentials) when present. Cracking
31
+ is line-streamed (no full-file load), so a 14M-line rockyou won't OOM.
32
+ - Added a pure-JS MD4 (NTLM) implementation so NTLM hashes also crack offline.
33
+ - Result now reports `source` (common/wordlist) + `candidates_checked`.
34
+ - Root cause: the Hackbot Arena labs04 run found the built-in common list was too
35
+ small (missed `md5("xNnWo6272k7x")`, only present in the full rockyou) — the
36
+ tool now reaches whatever wordlist is available on the box.
37
+
8
38
  ## [2.4.72] — 2026-09-14
9
39
 
10
40
  ### Added — Hackbot Arena benchmark integration
package/dist/index.js CHANGED
@@ -15584,11 +15584,11 @@ var require_src = __commonJS(function(exports, module) {
15584
15584
  });
15585
15585
 
15586
15586
  // src/mcp-status.ts
15587
- import { existsSync as existsSync9 } from "node:fs";
15587
+ import { existsSync as existsSync10 } from "node:fs";
15588
15588
  import { spawnSync as spawnSync3 } from "node:child_process";
15589
15589
  async function checkMcpServers() {
15590
15590
  const out = [];
15591
- const bin = ["/usr/bin/chrome-devtools-mcp", "/usr/local/bin/chrome-devtools-mcp"].find((p) => existsSync9(p));
15591
+ const bin = ["/usr/bin/chrome-devtools-mcp", "/usr/local/bin/chrome-devtools-mcp"].find((p) => existsSync10(p));
15592
15592
  const npxOk = spawnSync3("npx", ["--version"], { timeout: 8000 }).error === undefined;
15593
15593
  const devtoolsOk = Boolean(bin) || npxOk;
15594
15594
  out.push({
@@ -17220,7 +17220,7 @@ var init_update = __esm(() => {
17220
17220
  });
17221
17221
 
17222
17222
  // src/release.ts
17223
- import { readFileSync as readFileSync28, existsSync as existsSync13 } from "node:fs";
17223
+ import { readFileSync as readFileSync28, existsSync as existsSync14 } from "node:fs";
17224
17224
  import { join as join25 } from "node:path";
17225
17225
  import { fileURLToPath as fileURLToPath14 } from "node:url";
17226
17226
  import { execSync as execSync2 } from "node:child_process";
@@ -17260,7 +17260,7 @@ function validateRelease() {
17260
17260
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
17261
17261
  }
17262
17262
  function lockfilePresent() {
17263
- return existsSync13(join25(ROOT8, "package-lock.json")) || existsSync13(join25(ROOT8, "bun.lock")) || existsSync13(join25(ROOT8, "bun.lockb"));
17263
+ return existsSync14(join25(ROOT8, "package-lock.json")) || existsSync14(join25(ROOT8, "bun.lock")) || existsSync14(join25(ROOT8, "bun.lockb"));
17264
17264
  }
17265
17265
  var ROOT8;
17266
17266
  var init_release = __esm(() => {
@@ -17302,7 +17302,7 @@ var init_dependency = __esm(() => {
17302
17302
  });
17303
17303
 
17304
17304
  // src/cli.ts
17305
- import { readFileSync as readFileSync30, existsSync as existsSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17305
+ import { readFileSync as readFileSync30, existsSync as existsSync15, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17306
17306
  import { join as join27, dirname as dirname6 } from "node:path";
17307
17307
  import { homedir as homedir11 } from "node:os";
17308
17308
  import { fileURLToPath as fileURLToPath16 } from "node:url";
@@ -17348,7 +17348,7 @@ async function runDoctor() {
17348
17348
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
17349
17349
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
17350
17350
  });
17351
- const dataOk = existsSync14(join27(ROOT10, "chains.json")) && existsSync14(join27(ROOT10, "tools-catalog.json"));
17351
+ const dataOk = existsSync15(join27(ROOT10, "chains.json")) && existsSync15(join27(ROOT10, "tools-catalog.json"));
17352
17352
  issues.push({
17353
17353
  name: "Data layers (chains + tools-catalog)",
17354
17354
  status: dataOk ? "ok" : "fail",
@@ -17400,14 +17400,14 @@ function resolveCommand() {
17400
17400
  if (which("blitzstrike"))
17401
17401
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
17402
17402
  const src = join27(ROOT10, "src", "index.ts");
17403
- if (which("bun") && existsSync14(src))
17403
+ if (which("bun") && existsSync15(src))
17404
17404
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
17405
17405
  if (which("npx"))
17406
17406
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17407
17407
  if (which("bunx"))
17408
17408
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
17409
17409
  const dist = join27(ROOT10, "dist", "index.js");
17410
- if (existsSync14(dist))
17410
+ if (existsSync15(dist))
17411
17411
  return { command: "bun", args: [dist, "serve", "--mcp"] };
17412
17412
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17413
17413
  }
@@ -17421,7 +17421,7 @@ function readJson(p) {
17421
17421
  function jsonMcpServersWrite(p) {
17422
17422
  return (command, args) => {
17423
17423
  const dir = dirname6(p);
17424
- if (!existsSync14(dir))
17424
+ if (!existsSync15(dir))
17425
17425
  mkdirSync10(dir, { recursive: true });
17426
17426
  const existing = readJson(p) ?? {};
17427
17427
  existing.mcpServers = {
@@ -17434,7 +17434,7 @@ function jsonMcpServersWrite(p) {
17434
17434
  function opencodeWrite(p) {
17435
17435
  return (command, args) => {
17436
17436
  const dir = dirname6(p);
17437
- if (!existsSync14(dir))
17437
+ if (!existsSync15(dir))
17438
17438
  mkdirSync10(dir, { recursive: true });
17439
17439
  const existing = readJson(p) ?? {};
17440
17440
  existing.mcp = {
@@ -17448,14 +17448,14 @@ function opencodeWrite(p) {
17448
17448
  function copyOpenCodeAgents() {
17449
17449
  const srcDir = join27(ROOT10, "opencode-agents");
17450
17450
  const dstDir = join27(homedir11(), ".config", "opencode", "agents");
17451
- if (!existsSync14(srcDir))
17451
+ if (!existsSync15(srcDir))
17452
17452
  return;
17453
- if (!existsSync14(dstDir))
17453
+ if (!existsSync15(dstDir))
17454
17454
  mkdirSync10(dstDir, { recursive: true });
17455
17455
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
17456
17456
  const src = join27(srcDir, f);
17457
17457
  const dst = join27(dstDir, f);
17458
- if (existsSync14(src)) {
17458
+ if (existsSync15(src)) {
17459
17459
  writeFileSync9(dst, readFileSync30(src, "utf8"));
17460
17460
  }
17461
17461
  }
@@ -17463,9 +17463,9 @@ function copyOpenCodeAgents() {
17463
17463
  function codexWrite(p) {
17464
17464
  return (command, args) => {
17465
17465
  const dir = dirname6(p);
17466
- if (!existsSync14(dir))
17466
+ if (!existsSync15(dir))
17467
17467
  mkdirSync10(dir, { recursive: true });
17468
- let existing = existsSync14(p) ? readFileSync30(p, "utf8") : "";
17468
+ let existing = existsSync15(p) ? readFileSync30(p, "utf8") : "";
17469
17469
  if (!existing.trimEnd().endsWith(`
17470
17470
  `))
17471
17471
  existing += `
@@ -17482,9 +17482,9 @@ args = ${JSON.stringify(args)}
17482
17482
  function hermesWrite(p) {
17483
17483
  return (command, args) => {
17484
17484
  const dir = dirname6(p);
17485
- if (!existsSync14(dir))
17485
+ if (!existsSync15(dir))
17486
17486
  mkdirSync10(dir, { recursive: true });
17487
- let existing = existsSync14(p) ? readFileSync30(p, "utf8") : "";
17487
+ let existing = existsSync15(p) ? readFileSync30(p, "utf8") : "";
17488
17488
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
17489
17489
  if (!existing.trimEnd().endsWith(`
17490
17490
  `))
@@ -17515,70 +17515,70 @@ function detectAgents() {
17515
17515
  agents.push({
17516
17516
  name: "Claude Code",
17517
17517
  detectPath: claudeJson,
17518
- installed: () => existsSync14(claudeJson) || which("claude"),
17518
+ installed: () => existsSync15(claudeJson) || which("claude"),
17519
17519
  write: jsonMcpServersWrite(claudeJson)
17520
17520
  });
17521
17521
  const claudeDesktop = join27(home, ".config", "Claude", "claude_desktop_config.json");
17522
17522
  agents.push({
17523
17523
  name: "Claude Desktop",
17524
17524
  detectPath: claudeDesktop,
17525
- installed: () => existsSync14(claudeDesktop),
17525
+ installed: () => existsSync15(claudeDesktop),
17526
17526
  write: jsonMcpServersWrite(claudeDesktop)
17527
17527
  });
17528
17528
  const cursor = join27(home, ".cursor", "mcp.json");
17529
17529
  agents.push({
17530
17530
  name: "Cursor",
17531
17531
  detectPath: cursor,
17532
- installed: () => existsSync14(join27(home, ".cursor")) || which("cursor"),
17532
+ installed: () => existsSync15(join27(home, ".cursor")) || which("cursor"),
17533
17533
  write: jsonMcpServersWrite(cursor)
17534
17534
  });
17535
17535
  const opencode = join27(home, ".config", "opencode", "opencode.jsonc");
17536
17536
  agents.push({
17537
17537
  name: "OpenCode",
17538
17538
  detectPath: opencode,
17539
- installed: () => existsSync14(join27(home, ".config", "opencode")) || which("opencode"),
17539
+ installed: () => existsSync15(join27(home, ".config", "opencode")) || which("opencode"),
17540
17540
  write: opencodeWrite(opencode)
17541
17541
  });
17542
17542
  const codex = join27(home, ".codex", "config.toml");
17543
17543
  agents.push({
17544
17544
  name: "Codex",
17545
17545
  detectPath: codex,
17546
- installed: () => existsSync14(join27(home, ".codex")) || which("codex"),
17546
+ installed: () => existsSync15(join27(home, ".codex")) || which("codex"),
17547
17547
  write: codexWrite(codex)
17548
17548
  });
17549
17549
  const hermes = join27(home, ".hermes", "config.yaml");
17550
17550
  agents.push({
17551
17551
  name: "Hermes",
17552
17552
  detectPath: hermes,
17553
- installed: () => existsSync14(join27(home, ".hermes")) || which("hermes"),
17553
+ installed: () => existsSync15(join27(home, ".hermes")) || which("hermes"),
17554
17554
  write: hermesWrite(hermes)
17555
17555
  });
17556
17556
  const gemini = join27(home, ".gemini", "settings.json");
17557
17557
  agents.push({
17558
17558
  name: "Gemini CLI",
17559
17559
  detectPath: gemini,
17560
- installed: () => existsSync14(join27(home, ".gemini")) || which("gemini"),
17560
+ installed: () => existsSync15(join27(home, ".gemini")) || which("gemini"),
17561
17561
  write: jsonMcpServersWrite(gemini)
17562
17562
  });
17563
17563
  const windsurf = join27(home, ".codeium", "windsurf", "mcp_config.json");
17564
17564
  agents.push({
17565
17565
  name: "Windsurf",
17566
17566
  detectPath: windsurf,
17567
- installed: () => existsSync14(join27(home, ".codeium")) || which("windsurf"),
17567
+ installed: () => existsSync15(join27(home, ".codeium")) || which("windsurf"),
17568
17568
  write: jsonMcpServersWrite(windsurf)
17569
17569
  });
17570
17570
  const copilot = join27(home, ".copilot", "mcp.json");
17571
17571
  agents.push({
17572
17572
  name: "Copilot",
17573
17573
  detectPath: copilot,
17574
- installed: () => existsSync14(join27(home, ".copilot")),
17574
+ installed: () => existsSync15(join27(home, ".copilot")),
17575
17575
  write: jsonMcpServersWrite(copilot)
17576
17576
  });
17577
17577
  const cline = join27(home, ".cline", "mcp_settings.json");
17578
17578
  agents.push({
17579
17579
  name: "Cline",
17580
17580
  detectPath: cline,
17581
- installed: () => existsSync14(join27(home, ".cline")),
17581
+ installed: () => existsSync15(join27(home, ".cline")),
17582
17582
  write: jsonMcpServersWrite(cline)
17583
17583
  });
17584
17584
  return agents;
@@ -32417,7 +32417,7 @@ async function nvdLookup(cveId) {
32417
32417
 
32418
32418
  // src/orchestrator.ts
32419
32419
  init_scanner();
32420
- import { readFileSync as readFileSync9, existsSync as existsSync7, statSync as statSync2 } from "node:fs";
32420
+ import { readFileSync as readFileSync9, existsSync as existsSync8, statSync as statSync2 } from "node:fs";
32421
32421
  import { join as join11 } from "node:path";
32422
32422
  import { fileURLToPath as fileURLToPath5 } from "node:url";
32423
32423
 
@@ -33181,6 +33181,8 @@ function resolveFinding(finding, verdict) {
33181
33181
 
33182
33182
  // src/hash.ts
33183
33183
  import { createHash as createHash3 } from "node:crypto";
33184
+ import { createReadStream, existsSync as existsSync4 } from "node:fs";
33185
+ import { createInterface } from "node:readline";
33184
33186
  var COMMON_PASSWORDS = [
33185
33187
  "password",
33186
33188
  "123456",
@@ -33211,6 +33213,14 @@ var COMMON_PASSWORDS = [
33211
33213
  "P@ssw0rd",
33212
33214
  "qwertyuiop"
33213
33215
  ];
33216
+ var AUTOLOAD_WORDLISTS = [
33217
+ "/usr/share/wordlists/rockyou.txt",
33218
+ "/opt/seclists/Passwords/Leaked-Databases/rockyou-70.txt",
33219
+ "/opt/seclists/Passwords/Leaked-Databases/rockyou-35.txt",
33220
+ "/opt/seclists/Passwords/Leaked-Databases/rockyou-40.txt",
33221
+ "/opt/seclists/Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt",
33222
+ "/opt/seclists/Passwords/Common-Credentials/10k-most-common.txt"
33223
+ ];
33214
33224
  function identifyHash(hash) {
33215
33225
  if (/^\$2[aby]\$\d+\$/.test(hash))
33216
33226
  return "bcrypt";
@@ -33243,31 +33253,141 @@ var HASHCAT_MODE = {
33243
33253
  sha512crypt: "1800",
33244
33254
  ntlm: "1000"
33245
33255
  };
33246
- async function crackHash(hash, extra = []) {
33256
+ function hashMatches(type, pw, hash) {
33257
+ const target = hash.toLowerCase();
33258
+ if (type === "md5")
33259
+ return createHash3("md5").update(pw).digest("hex") === target;
33260
+ if (type === "sha1")
33261
+ return createHash3("sha1").update(pw).digest("hex") === target;
33262
+ if (type === "sha256")
33263
+ return createHash3("sha256").update(pw).digest("hex") === target;
33264
+ if (type === "sha512")
33265
+ return createHash3("sha512").update(pw).digest("hex") === target;
33266
+ if (type === "ntlm") {
33267
+ const md4 = (buf) => {
33268
+ const pad = (s) => {
33269
+ const len = s.length;
33270
+ const bitLen = len * 8;
33271
+ const out = Buffer.concat([s, Buffer.from([128])]);
33272
+ const mod = out.length % 64;
33273
+ const padLen = mod < 56 ? 56 - mod : 120 - mod;
33274
+ const padded = Buffer.concat([out, Buffer.alloc(padLen)]);
33275
+ const lenBuf = Buffer.alloc(8);
33276
+ lenBuf.writeUInt32LE(bitLen >>> 0, 0);
33277
+ lenBuf.writeUInt32LE(Math.floor(bitLen / 4294967296), 4);
33278
+ return Buffer.concat([padded, lenBuf]);
33279
+ };
33280
+ const rotl = (x, n) => (x << n | x >>> 32 - n) >>> 0;
33281
+ const F = (x, y, z) => x & y | ~x & z;
33282
+ const G = (x, y, z) => x & y | x & z | y & z;
33283
+ const H = (x, y, z) => x ^ y ^ z;
33284
+ const msg = pad(buf);
33285
+ let a0 = 1732584193, b0 = 4023233417, c0 = 2562383102, d0 = 271733878;
33286
+ for (let i = 0;i < msg.length; i += 64) {
33287
+ const X = [];
33288
+ for (let j = 0;j < 16; j++)
33289
+ X[j] = msg.readUInt32LE(i + j * 4);
33290
+ let A = a0, B = b0, C = c0, D = d0;
33291
+ for (let r = 0;r < 16; r++) {
33292
+ const k = r;
33293
+ const s = [3, 7, 11, 19][r % 4];
33294
+ const order = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
33295
+ A = rotl(A + F(B, C, D) + X[order[k]] + 0 >>> 0, s);
33296
+ [A, B, C, D] = [D, A, B, C];
33297
+ }
33298
+ for (let r = 0;r < 16; r++) {
33299
+ const order = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
33300
+ const s = [3, 5, 9, 13][r % 4];
33301
+ A = rotl(A + G(B, C, D) + X[order[r]] + 1518500249 >>> 0, s);
33302
+ [A, B, C, D] = [D, A, B, C];
33303
+ }
33304
+ for (let r = 0;r < 16; r++) {
33305
+ const order = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
33306
+ const s = [3, 9, 11, 15][r % 4];
33307
+ A = rotl(A + H(B, C, D) + X[order[r]] + 1859775393 >>> 0, s);
33308
+ [A, B, C, D] = [D, A, B, C];
33309
+ }
33310
+ a0 = a0 + A >>> 0;
33311
+ b0 = b0 + B >>> 0;
33312
+ c0 = c0 + C >>> 0;
33313
+ d0 = d0 + D >>> 0;
33314
+ }
33315
+ const le = (x) => Buffer.from([x & 255, x >>> 8 & 255, x >>> 16 & 255, x >>> 24 & 255]);
33316
+ return Buffer.concat([le(a0), le(b0), le(c0), le(d0)]).toString("hex");
33317
+ };
33318
+ const utf16le = Buffer.from(pw, "utf16le");
33319
+ return md4(utf16le) === target;
33320
+ }
33321
+ return false;
33322
+ }
33323
+ async function crackWordlists(hash, type, paths, maxCandidates) {
33324
+ let checked = 0;
33325
+ for (const p of paths) {
33326
+ if (!existsSync4(p))
33327
+ continue;
33328
+ const stream = createReadStream(p, { encoding: "utf8" });
33329
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
33330
+ let found = null;
33331
+ for await (const line of rl) {
33332
+ if (found)
33333
+ break;
33334
+ const pw = line.endsWith("\r") ? line.slice(0, -1) : line;
33335
+ if (!pw)
33336
+ continue;
33337
+ checked += 1;
33338
+ if (checked > maxCandidates)
33339
+ break;
33340
+ if (hashMatches(type, pw, hash))
33341
+ found = pw;
33342
+ }
33343
+ stream.destroy();
33344
+ if (found)
33345
+ return { password: found, checked };
33346
+ if (checked > maxCandidates)
33347
+ break;
33348
+ }
33349
+ return { password: null, checked };
33350
+ }
33351
+ async function crackHash(hash, extra = [], opts = {}) {
33247
33352
  const type = identifyHash(hash);
33248
- const wordlist = [...new Set([...extra, ...COMMON_PASSWORDS])];
33249
- const matches = [];
33250
- for (const pw of wordlist) {
33251
- try {
33252
- if (type === "bcrypt") {
33253
- const verify = globalThis.Bun;
33254
- if (verify?.password?.verifySync?.(pw, hash))
33255
- matches.push(pw);
33256
- } else {
33257
- const algo = type === "md5" ? "md5" : type === "sha1" ? "sha1" : type === "sha256" ? "sha256" : "sha512";
33258
- if (["md5", "sha1", "sha256", "sha512"].includes(type)) {
33259
- if (createHash3(algo).update(pw).digest("hex").toLowerCase() === hash.toLowerCase())
33260
- matches.push(pw);
33261
- }
33262
- }
33263
- } catch {}
33353
+ const maxCandidates = opts.maxCandidates ?? 5000000;
33354
+ const fast = [...new Set([...extra, ...COMMON_PASSWORDS])];
33355
+ let matched = "";
33356
+ for (const pw of fast) {
33357
+ if (hashMatches(type, pw, hash)) {
33358
+ matched = pw;
33359
+ break;
33360
+ }
33361
+ }
33362
+ if (matched) {
33363
+ return {
33364
+ type,
33365
+ cracked: true,
33366
+ matches: [matched],
33367
+ source: "common",
33368
+ recommendation: `CRACKED — authenticate with '${matched}' and continue the chain (login → dashboard → privileged actions).`
33369
+ };
33370
+ }
33371
+ const explicit = opts.wordlist ? Array.isArray(opts.wordlist) ? opts.wordlist : [opts.wordlist] : [];
33372
+ const paths = [...new Set([...explicit, ...AUTOLOAD_WORDLISTS])];
33373
+ const { password, checked } = await crackWordlists(hash, type, paths, maxCandidates);
33374
+ if (password) {
33375
+ return {
33376
+ type,
33377
+ cracked: true,
33378
+ matches: [password],
33379
+ source: "wordlist",
33380
+ candidates_checked: checked,
33381
+ recommendation: `CRACKED from wordlist — authenticate with '${password}' and continue the chain (login → dashboard → privileged actions).`
33382
+ };
33264
33383
  }
33265
33384
  const mode = HASHCAT_MODE[type];
33266
33385
  return {
33267
33386
  type,
33268
- cracked: matches.length > 0,
33269
- matches,
33270
- recommendation: matches.length > 0 ? `CRACKED — authenticate with '${matches[0]}' and continue the chain (login → dashboard → privileged actions).` : mode ? `Not in the built-in common list. Full crack: hashcat -m ${mode} -a 0 <hashes.txt> <wordlist> (bcrypt cost 12 needs GPU + time). Reuse the credential if it matches another account's hash (password reuse).` : "Unknown hash format — save it and identify manually before cracking."
33387
+ cracked: false,
33388
+ matches: [],
33389
+ candidates_checked: checked,
33390
+ recommendation: mode ? `Not in the built-in common list or ${checked} wordlist candidates. Full crack: hashcat -m ${mode} -a 0 <hashes.txt> <wordlist> (bcrypt cost 12 needs GPU + time). Pass a target-specific wordlist via the wordlist arg (e.g. company names, usernames, pattern guesses). Reuse the credential if it matches another account's hash (password reuse).` : "Unknown hash format — save it and identify manually before cracking."
33271
33391
  };
33272
33392
  }
33273
33393
 
@@ -33447,7 +33567,7 @@ async function browserValidate(check, target, payload) {
33447
33567
 
33448
33568
  // src/devtools.ts
33449
33569
  import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
33450
- import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
33570
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "node:fs";
33451
33571
  import { join as join7 } from "node:path";
33452
33572
  import { homedir as homedir4 } from "node:os";
33453
33573
  function mcpClient(command, args) {
@@ -33521,17 +33641,17 @@ function findChromium() {
33521
33641
  for (const c of candidates) {
33522
33642
  for (const dir of ["/usr/bin", "/usr/local/bin", "/opt/google/chrome"]) {
33523
33643
  const p = join7(dir, c);
33524
- if (existsSync4(p))
33644
+ if (existsSync5(p))
33525
33645
  return p;
33526
33646
  }
33527
33647
  }
33528
33648
  const pw = join7(homedir4(), ".cache", "ms-playwright");
33529
- if (existsSync4(pw)) {
33649
+ if (existsSync5(pw)) {
33530
33650
  for (const d of readdirSync3(pw)) {
33531
33651
  if (!d.startsWith("chromium"))
33532
33652
  continue;
33533
33653
  const full = join7(pw, d, "chrome-linux64", "chrome");
33534
- if (existsSync4(full))
33654
+ if (existsSync5(full))
33535
33655
  return full;
33536
33656
  }
33537
33657
  }
@@ -33539,7 +33659,7 @@ function findChromium() {
33539
33659
  }
33540
33660
  function chromeDevtoolsCommand() {
33541
33661
  for (const c of ["/usr/bin/chrome-devtools-mcp", "/usr/local/bin/chrome-devtools-mcp"]) {
33542
- if (existsSync4(c))
33662
+ if (existsSync5(c))
33543
33663
  return { command: c, args: [] };
33544
33664
  }
33545
33665
  const npx = spawnSync2("npx", ["--version"], { timeout: 8000 });
@@ -33851,12 +33971,12 @@ function uniqueFindings(findings) {
33851
33971
  }
33852
33972
 
33853
33973
  // src/intel.ts
33854
- import { readFileSync as readFileSync8, existsSync as existsSync6 } from "node:fs";
33974
+ import { readFileSync as readFileSync8, existsSync as existsSync7 } from "node:fs";
33855
33975
  import { join as join9 } from "node:path";
33856
33976
  import { fileURLToPath as fileURLToPath4 } from "node:url";
33857
33977
 
33858
33978
  // src/intelligence.ts
33859
- import { readFileSync as readFileSync7, appendFileSync as appendFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "node:fs";
33979
+ import { readFileSync as readFileSync7, appendFileSync as appendFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "node:fs";
33860
33980
  import { join as join8 } from "node:path";
33861
33981
  import { createHash as createHash4 } from "node:crypto";
33862
33982
  import { homedir as homedir5 } from "node:os";
@@ -33882,14 +34002,14 @@ function sinkToVector(sinkType) {
33882
34002
  return SINK_TO_VECTOR[sinkType.toLowerCase()] ?? sinkType.toLowerCase();
33883
34003
  }
33884
34004
  function ensureDir2() {
33885
- if (!existsSync5(HOME_DIR3))
34005
+ if (!existsSync6(HOME_DIR3))
33886
34006
  mkdirSync4(HOME_DIR3, { recursive: true });
33887
34007
  }
33888
34008
  function dedupHash2(vector, tech, target, outcome) {
33889
34009
  return createHash4("sha256").update(`${vector}\x00${tech}\x00${target}\x00${outcome}`).digest("hex").slice(0, 16);
33890
34010
  }
33891
34011
  function loadIntel() {
33892
- if (!existsSync5(INTEL_PATH))
34012
+ if (!existsSync6(INTEL_PATH))
33893
34013
  return [];
33894
34014
  try {
33895
34015
  const out = [];
@@ -34041,7 +34161,7 @@ var ROOT3 = join9(fileURLToPath4(new URL(".", import.meta.url)), "..");
34041
34161
  var INTEL = join9(ROOT3, "intelligence");
34042
34162
  function loadJson(name) {
34043
34163
  const p = join9(INTEL, `${name}.json`);
34044
- if (!existsSync6(p))
34164
+ if (!existsSync7(p))
34045
34165
  return null;
34046
34166
  try {
34047
34167
  return JSON.parse(readFileSync8(p, "utf8"));
@@ -34328,12 +34448,12 @@ var PACKAGE_PAYLOADS = join9(ROOT3, "payloads");
34328
34448
  var PACKAGE_TEMPLATES = join9(ROOT3, "templates");
34329
34449
  var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join9(homedir6(), ".blitzstrike", "data");
34330
34450
  function payloadsDir() {
34331
- if (existsSync6(PACKAGE_PAYLOADS))
34451
+ if (existsSync7(PACKAGE_PAYLOADS))
34332
34452
  return PACKAGE_PAYLOADS;
34333
34453
  return join9(DATA_ROOT, "payloads");
34334
34454
  }
34335
34455
  function templatesDir() {
34336
- if (existsSync6(PACKAGE_TEMPLATES))
34456
+ if (existsSync7(PACKAGE_TEMPLATES))
34337
34457
  return PACKAGE_TEMPLATES;
34338
34458
  return join9(DATA_ROOT, "templates");
34339
34459
  }
@@ -35764,6 +35884,59 @@ function extractVersion(headers, body) {
35764
35884
  }
35765
35885
  return out;
35766
35886
  }
35887
+ function detectTech(headers, body) {
35888
+ const techs = new Set;
35889
+ const low = (s) => (s ?? "").toLowerCase();
35890
+ const powered = low(headers["x-powered-by"]);
35891
+ const server = low(headers["server"]);
35892
+ const cookies = low(headers["set-cookie"]);
35893
+ const generator = low(headers["x-generator"]);
35894
+ if (/express|node\b|nodejs/i.test(powered))
35895
+ techs.add("nodejs");
35896
+ if (/^php\b|^php\//i.test(powered))
35897
+ techs.add("php");
35898
+ if (/asp\.net/i.test(powered) || (headers["x-aspnet-version"] ?? ""))
35899
+ techs.add("aspnet");
35900
+ if (/werkzeug/i.test(server))
35901
+ techs.add("flask");
35902
+ if (/drupal/i.test(generator))
35903
+ techs.add("drupal");
35904
+ if (/laravel_session|xsrf-token/i.test(cookies))
35905
+ techs.add("laravel");
35906
+ if (/phpsessid/i.test(cookies))
35907
+ techs.add("php");
35908
+ if (/connect\.sid/i.test(cookies))
35909
+ techs.add("nodejs");
35910
+ if (/csrftoken/i.test(cookies) && /sessionid/i.test(cookies))
35911
+ techs.add("django");
35912
+ if (/rack\.session/i.test(cookies))
35913
+ techs.add("rails");
35914
+ const bodySig = [
35915
+ [/wp-content|wp-includes|wp-json/i, "wordpress"],
35916
+ [/wp-login\.php|wp-admin/i, "wordpress"],
35917
+ [/powered by joomla|com_content/i, "joomla"],
35918
+ [/laravel_session|xsrf-token|\blaravel\b/i, "laravel"],
35919
+ [/react|__NEXT_DATA__|next\/static/i, "nextjs"],
35920
+ [/angular|ng-version/i, "angular"],
35921
+ [/vue|__vue__|v-data/i, "vue"],
35922
+ [/django|csrftoken|__debug__/i, "django"],
35923
+ [/ruby on rails|rails/i, "rails"],
35924
+ [/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
35925
+ [/phpBB|phpbb/i, "phpbb"],
35926
+ [/mybb|mybb/i, "mybb"],
35927
+ [/drupal|Drupal\.settings/i, "drupal"],
35928
+ [/magento|Mage\./i, "magento"],
35929
+ [/shopify|cdn\.shopify/i, "shopify"],
35930
+ [/grafana|kibana|elastic/i, "grafana-kibana"],
35931
+ [/node\.js|express/i, "nodejs"],
35932
+ [/flask|werkzeug/i, "flask"],
35933
+ [/spring|actuator/i, "spring"]
35934
+ ];
35935
+ for (const [re, t] of bodySig)
35936
+ if (re.test(body))
35937
+ techs.add(t);
35938
+ return [...techs];
35939
+ }
35767
35940
  function correlateIntel(techs, versionList, portList) {
35768
35941
  const tech = techs.map((t) => ({ tech: t, ...techCorrelation(t) }));
35769
35942
  const versions = versionList.map((v) => ({
@@ -35799,28 +35972,7 @@ async function liveRecon(target, includeActive = false) {
35799
35972
  body_size: root.body.length
35800
35973
  };
35801
35974
  const waf = detectWaf(root.headers, root.body);
35802
- const techSig = [
35803
- [/wp-content|wp-includes|wp-json/i, "wordpress"],
35804
- [/wp-login\.php|wp-admin/i, "wordpress"],
35805
- [/powered by joomla|com_content/i, "joomla"],
35806
- [/laravel|_token|csrf-token/i, "laravel"],
35807
- [/react|__NEXT_DATA__|next\/static/i, "nextjs"],
35808
- [/angular|ng-version/i, "angular"],
35809
- [/vue|__vue__|v-data/i, "vue"],
35810
- [/django|csrftoken|__debug__/i, "django"],
35811
- [/ruby on rails|rails/i, "rails"],
35812
- [/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
35813
- [/phpBB|phpbb/i, "phpbb"],
35814
- [/mybb|mybb/i, "mybb"],
35815
- [/drupal|Drupal\.settings/i, "drupal"],
35816
- [/magento|Mage\./i, "magento"],
35817
- [/shopify|cdn\.shopify/i, "shopify"],
35818
- [/grafana|kibana|elastic/i, "grafana-kibana"],
35819
- [/node\.js|express/i, "nodejs"],
35820
- [/flask|werkzeug/i, "flask"],
35821
- [/spring|actuator/i, "spring"]
35822
- ];
35823
- const techs = [...new Set(techSig.filter(([re]) => re.test(root.body)).map(([, t]) => t))];
35975
+ const techs = detectTech(root.headers, root.body);
35824
35976
  const versions = extractVersion(root.headers, root.body);
35825
35977
  const { links, scripts } = crawlLinks(base, root.body);
35826
35978
  const { robots, sitemap } = await fetchRobotsAndSitemap(base);
@@ -35941,7 +36093,7 @@ function classifyTarget(target) {
35941
36093
  if (/^https?:\/\//i.test(target))
35942
36094
  return { kind: "live", target };
35943
36095
  try {
35944
- if (existsSync7(target) && (statSync2(target).isDirectory() || statSync2(target).isFile())) {
36096
+ if (existsSync8(target) && (statSync2(target).isDirectory() || statSync2(target).isFile())) {
35945
36097
  return { kind: "source", target };
35946
36098
  }
35947
36099
  } catch {}
@@ -36082,7 +36234,7 @@ function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000,
36082
36234
  };
36083
36235
  const isPath = (() => {
36084
36236
  try {
36085
- return existsSync7(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
36237
+ return existsSync8(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
36086
36238
  } catch {
36087
36239
  return false;
36088
36240
  }
@@ -57558,7 +57710,7 @@ function analyzePatchFiles(oldPath, newPath, language) {
57558
57710
  }
57559
57711
 
57560
57712
  // src/frameworks.ts
57561
- import { readFileSync as readFileSync16, existsSync as existsSync8 } from "node:fs";
57713
+ import { readFileSync as readFileSync16, existsSync as existsSync9 } from "node:fs";
57562
57714
  import { join as join15 } from "node:path";
57563
57715
  import { fileURLToPath as fileURLToPath7 } from "node:url";
57564
57716
  var ROOT4 = join15(fileURLToPath7(new URL(".", import.meta.url)), "..");
@@ -57568,7 +57720,7 @@ function load2() {
57568
57720
  if (cache)
57569
57721
  return cache;
57570
57722
  const p = join15(INTEL2, "frameworks.json");
57571
- if (!existsSync8(p))
57723
+ if (!existsSync9(p))
57572
57724
  return null;
57573
57725
  try {
57574
57726
  cache = JSON.parse(readFileSync16(p, "utf8"));
@@ -58253,7 +58405,7 @@ function submissionGate(input) {
58253
58405
  // src/git-history.ts
58254
58406
  init_complex_bugs();
58255
58407
  import { execFileSync as execFileSync4 } from "node:child_process";
58256
- import { existsSync as existsSync10 } from "node:fs";
58408
+ import { existsSync as existsSync11 } from "node:fs";
58257
58409
  import { join as join17 } from "node:path";
58258
58410
  function parseDeletions(output) {
58259
58411
  const out = [];
@@ -58283,7 +58435,7 @@ function blobBeforeDeletion(root, commit, path) {
58283
58435
  function scanGitHistory(root, opts) {
58284
58436
  const limit = opts?.limit ?? 30;
58285
58437
  const findings = [];
58286
- if (!existsSync10(join17(root, ".git"))) {
58438
+ if (!existsSync11(join17(root, ".git"))) {
58287
58439
  return { root, is_git_repo: false, total: 0, findings: [], error: "not a git repository (no .git dir)" };
58288
58440
  }
58289
58441
  if (opts?.secretPattern) {
@@ -58816,21 +58968,21 @@ function runBenchmark() {
58816
58968
  }
58817
58969
 
58818
58970
  // src/obligations.ts
58819
- import { readFileSync as readFileSync22, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync7 } from "node:fs";
58971
+ import { readFileSync as readFileSync22, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "node:fs";
58820
58972
  import { join as join22 } from "node:path";
58821
58973
  import { createHash as createHash6 } from "node:crypto";
58822
58974
  import { homedir as homedir9 } from "node:os";
58823
58975
  var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join22(homedir9(), ".blitzstrike");
58824
58976
  var OBLIGATION_PATH = join22(HOME_DIR4, "obligations.jsonl");
58825
58977
  function ensureDir3() {
58826
- if (!existsSync11(HOME_DIR4))
58978
+ if (!existsSync12(HOME_DIR4))
58827
58979
  mkdirSync8(HOME_DIR4, { recursive: true });
58828
58980
  }
58829
58981
  function shortId2(claim) {
58830
58982
  return `obl-${createHash6("sha256").update(`${claim}\x00${Date.now()}`).digest("hex").slice(0, 12)}`;
58831
58983
  }
58832
58984
  function loadObligations() {
58833
- if (!existsSync11(OBLIGATION_PATH))
58985
+ if (!existsSync12(OBLIGATION_PATH))
58834
58986
  return [];
58835
58987
  try {
58836
58988
  const out = [];
@@ -59833,7 +59985,7 @@ function analyzeJwt(token, opts = {}) {
59833
59985
  }
59834
59986
 
59835
59987
  // src/enterprise-benchmark.ts
59836
- import { existsSync as existsSync12, readFileSync as readFileSync25, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
59988
+ import { existsSync as existsSync13, readFileSync as readFileSync25, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
59837
59989
  import { join as join23, resolve } from "node:path";
59838
59990
  init_complex_bugs();
59839
59991
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
@@ -59894,7 +60046,7 @@ function detectFixture(dir) {
59894
60046
  function runEnterpriseBenchmark() {
59895
60047
  const fixtures = [];
59896
60048
  for (const fx of ENTERPRISE_FIXTURES) {
59897
- if (!existsSync12(fx.dir)) {
60049
+ if (!existsSync13(fx.dir)) {
59898
60050
  fixtures.push({ id: fx.id, available: false, detected: 0, expected: fx.expected_vulns, recall: 0 });
59899
60051
  continue;
59900
60052
  }
@@ -60318,16 +60470,19 @@ function createServer() {
60318
60470
  });
60319
60471
  server.registerTool("crack_hash", {
60320
60472
  title: "Identify + crack a dumped credential hash",
60321
- description: "CREDENTIAL: when you dump password hashes, EXECUTE this instead of leaving 'crack the hashes' as a recommendation. Identifies the hash type (bcrypt/md5/sha1/sha256/sha512/ntlm) and immediately tries a built-in common-password list offline, then returns the exact hashcat/john command for the full crack. Pass one hash string or a list.",
60473
+ description: "CREDENTIAL: when you dump password hashes, EXECUTE this instead of leaving 'crack the hashes' as a recommendation. Identifies the hash type (bcrypt/md5/sha1/sha256/sha512/ntlm) and immediately tries a built-in common-password list offline, then streams any supplied wordlist (and autoloads seclists/rockyou when present), then returns the exact hashcat/john command for the full crack. Pass one hash string or a list.",
60322
60474
  inputSchema: {
60323
60475
  hash: string2().describe("The hash string (or a list of hashes, one per line)"),
60324
- extra: array(string2()).optional().describe("Extra candidate passwords to try (e.g. usernames, target-specific words)")
60476
+ extra: array(string2()).optional().describe("Extra candidate passwords to try (e.g. usernames, target-specific words)"),
60477
+ wordlist: string2().optional().describe("Optional wordlist file path(s) to stream (comma-separated). Autoloads rockyou/seclists if present."),
60478
+ maxCandidates: number2().int().optional().describe("Max wordlist candidates to stream (default 5,000,000)")
60325
60479
  }
60326
- }, async ({ hash, extra }) => {
60480
+ }, async ({ hash, extra, wordlist, maxCandidates }) => {
60327
60481
  const hashes = hash.split(/\r?\n/).map((h) => h.trim()).filter(Boolean).slice(0, 50);
60482
+ const wl = wordlist ? wordlist.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
60328
60483
  const results = [];
60329
60484
  for (const h of hashes) {
60330
- results.push({ hash: h.slice(0, 24) + (h.length > 24 ? "…" : ""), ...await crackHash(h, extra ?? []) });
60485
+ results.push({ hash: h.slice(0, 24) + (h.length > 24 ? "…" : ""), ...await crackHash(h, extra ?? [], { wordlist: wl, maxCandidates }) });
60331
60486
  }
60332
60487
  return { content: [{ type: "text", text: JSON.stringify({ hashes_scanned: results.length, results }) }] };
60333
60488
  });
@@ -61934,14 +62089,14 @@ async function serve() {
61934
62089
 
61935
62090
  // src/index.ts
61936
62091
  init_cli();
61937
- import { readFileSync as readFileSync31, existsSync as existsSync15 } from "node:fs";
62092
+ import { readFileSync as readFileSync31, existsSync as existsSync16 } from "node:fs";
61938
62093
  import { join as join28 } from "node:path";
61939
62094
  import { fileURLToPath as fileURLToPath17 } from "node:url";
61940
62095
  var ROOT11 = join28(fileURLToPath17(new URL(".", import.meta.url)), "..");
61941
62096
  function readVersion() {
61942
62097
  try {
61943
62098
  const p = join28(ROOT11, "package.json");
61944
- if (existsSync15(p))
62099
+ if (existsSync16(p))
61945
62100
  return JSON.parse(readFileSync31(p, "utf8")).version ?? "1.0.0";
61946
62101
  } catch {}
61947
62102
  return "1.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.72",
3
+ "version": "2.4.74",
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": {