blitzstrike 2.4.67 → 2.4.68

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 +18 -0
  2. package/dist/index.js +315 -98
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ 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.68] — 2026-09-14
9
+
10
+ ### Added — Git-history secret mining + leaked-key validation
11
+
12
+ - New `scan_git_history` tool: mine a repo's HISTORY (not just HEAD) for secrets in
13
+ files that were DELETED — the "removed later != fixed" trap. Runs
14
+ `git log --diff-filter=D` + `git show <sha>^:<path>` (read-only) and feeds every
15
+ recovered pre-deletion blob through the hardcoded_secret detector, with the
16
+ deletion commit + message attached. Also a pickaxe mode (git log -S) to trace one
17
+ specific token through all commits.
18
+ - New `validate_leaked_key` tool: prove a recovered credential is still ACTIVE on a
19
+ live API with a READ-ONLY differential test — the same endpoint is fetched
20
+ without the key (expect 401/403) and with the key (expect 200); the contrast is
21
+ proof of auth bypass. Accepts a raw key or a blobUrl + varName to fetch/parse it.
22
+ - Secret severity now ESCALATES: when a secret-field literal also matches a known
23
+ format (e.g. a Stripe live key under a SECRET_KEY field), the finding takes the
24
+ format's more precise severity (critical) instead of the generic field tier.
25
+
8
26
  ## [2.4.67] — 2026-09-14
9
27
 
10
28
  ### Fixed — Whole-code interconnection audit (2 bugs)
package/dist/index.js CHANGED
@@ -16523,14 +16523,26 @@ function detectHardcodedSecret(code, file, out) {
16523
16523
  continue;
16524
16524
  const ln = lineNo2(code, m.index);
16525
16525
  flaggedLines.add(ln);
16526
+ let sev = "high";
16527
+ let cat = "Hardcoded secret (credential in source)";
16528
+ let detail = `Secret field '${field}' is assigned a hardcoded value in source — the credential is exposed to anyone with code access (CWE-798 Hard-coded Credentials). Rotate it and load from the environment instead.`;
16529
+ for (const fmt of SECRET_FORMATS) {
16530
+ fmt.re.lastIndex = 0;
16531
+ if (fmt.re.test(value)) {
16532
+ sev = fmt.severity;
16533
+ cat = `Hardcoded secret (${fmt.category}: ${fmt.name})`;
16534
+ detail = `${fmt.name} was found hardcoded in source — the credential is exposed to anyone with code access (CWE-798, severity ${fmt.severity}). Rotate it and load from the environment instead.`;
16535
+ break;
16536
+ }
16537
+ }
16526
16538
  out.push({
16527
16539
  file,
16528
16540
  line: ln,
16529
16541
  type: "hardcoded_secret",
16530
- category: "Hardcoded secret (credential in source)",
16531
- severity: "high",
16542
+ category: cat,
16543
+ severity: sev,
16532
16544
  evidence: (ls[ln - 1] ?? "").trim(),
16533
- detail: `Secret field '${field}' is assigned a hardcoded value in source — the credential is exposed to anyone with code access (CWE-798 Hard-coded Credentials). Rotate it and load from the environment instead.`
16545
+ detail
16534
16546
  });
16535
16547
  }
16536
16548
  for (const fmt of SECRET_FORMATS) {
@@ -16854,11 +16866,11 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
16854
16866
 
16855
16867
  // src/variants.ts
16856
16868
  import { readFileSync as readFileSync19 } from "node:fs";
16857
- import { join as join18, dirname as dirname4 } from "node:path";
16869
+ import { join as join19, dirname as dirname4 } from "node:path";
16858
16870
  import { fileURLToPath as fileURLToPath10 } from "node:url";
16859
16871
  function loadVariants() {
16860
16872
  try {
16861
- const doc = JSON.parse(readFileSync19(join18(__dirname4, "..", "intelligence", "variants.json"), "utf8"));
16873
+ const doc = JSON.parse(readFileSync19(join19(__dirname4, "..", "intelligence", "variants.json"), "utf8"));
16862
16874
  return doc.variants ?? [];
16863
16875
  } catch {
16864
16876
  return [];
@@ -16918,7 +16930,7 @@ var init_variants = __esm(() => {
16918
16930
 
16919
16931
  // src/sync.ts
16920
16932
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync25, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
16921
- import { join as join22 } from "node:path";
16933
+ import { join as join23 } from "node:path";
16922
16934
  import { homedir as homedir10 } from "node:os";
16923
16935
  import { fileURLToPath as fileURLToPath12 } from "node:url";
16924
16936
  import { spawnSync as spawnSync4 } from "node:child_process";
@@ -16935,7 +16947,7 @@ function countFiles(dir) {
16935
16947
  const walk = (d) => {
16936
16948
  for (const e of readdirSync7(d, { withFileTypes: true })) {
16937
16949
  if (e.isDirectory())
16938
- walk(join22(d, e.name));
16950
+ walk(join23(d, e.name));
16939
16951
  else
16940
16952
  n++;
16941
16953
  }
@@ -16949,8 +16961,8 @@ function countFiles(dir) {
16949
16961
  function syncData() {
16950
16962
  if (!dirExists(DATA_ROOT2))
16951
16963
  mkdirSync9(DATA_ROOT2, { recursive: true });
16952
- const payloadsPath = join22(DATA_ROOT2, "payloads");
16953
- const templatesPath = join22(DATA_ROOT2, "templates");
16964
+ const payloadsPath = join23(DATA_ROOT2, "payloads");
16965
+ const templatesPath = join23(DATA_ROOT2, "templates");
16954
16966
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
16955
16967
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
16956
16968
  if (hasPayloads && hasTemplates) {
@@ -16960,8 +16972,8 @@ function syncData() {
16960
16972
  data_root: DATA_ROOT2
16961
16973
  };
16962
16974
  }
16963
- const localPayloads = join22(ROOT7, "payloads");
16964
- const localTemplates = join22(ROOT7, "templates");
16975
+ const localPayloads = join23(ROOT7, "payloads");
16976
+ const localTemplates = join23(ROOT7, "templates");
16965
16977
  let payloadsSynced = false;
16966
16978
  let templatesSynced = false;
16967
16979
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -16974,7 +16986,7 @@ function syncData() {
16974
16986
  }
16975
16987
  const gitOk = spawnSync4("git", ["--version"]).status === 0;
16976
16988
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
16977
- const tmp = join22(DATA_ROOT2, ".tmp-clone");
16989
+ const tmp = join23(DATA_ROOT2, ".tmp-clone");
16978
16990
  if (dirExists(tmp))
16979
16991
  rmSync4(tmp, { recursive: true, force: true });
16980
16992
  const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -16982,11 +16994,11 @@ function syncData() {
16982
16994
  const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
16983
16995
  if (sparse.status === 0) {
16984
16996
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
16985
- copyDir(join22(tmp, "payloads"), payloadsPath);
16997
+ copyDir(join23(tmp, "payloads"), payloadsPath);
16986
16998
  payloadsSynced = true;
16987
16999
  }
16988
17000
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
16989
- copyDir(join22(tmp, "templates"), templatesPath);
17001
+ copyDir(join23(tmp, "templates"), templatesPath);
16990
17002
  templatesSynced = true;
16991
17003
  }
16992
17004
  }
@@ -17008,8 +17020,8 @@ function copyDir(src, dst) {
17008
17020
  function manualCopy(src, dst) {
17009
17021
  mkdirSync9(dst, { recursive: true });
17010
17022
  for (const e of readdirSync7(src, { withFileTypes: true })) {
17011
- const s = join22(src, e.name);
17012
- const d = join22(dst, e.name);
17023
+ const s = join23(src, e.name);
17024
+ const d = join23(dst, e.name);
17013
17025
  if (e.isDirectory())
17014
17026
  manualCopy(s, d);
17015
17027
  else
@@ -17018,8 +17030,8 @@ function manualCopy(src, dst) {
17018
17030
  }
17019
17031
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
17020
17032
  var init_sync = __esm(() => {
17021
- ROOT7 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
17022
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join22(homedir10(), ".blitzstrike", "data");
17033
+ ROOT7 = join23(fileURLToPath12(new URL(".", import.meta.url)), "..");
17034
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join23(homedir10(), ".blitzstrike", "data");
17023
17035
  });
17024
17036
 
17025
17037
  // src/update.ts
@@ -17064,15 +17076,15 @@ var init_update = __esm(() => {
17064
17076
  });
17065
17077
 
17066
17078
  // src/release.ts
17067
- import { readFileSync as readFileSync27, existsSync as existsSync12 } from "node:fs";
17068
- import { join as join23 } from "node:path";
17079
+ import { readFileSync as readFileSync27, existsSync as existsSync13 } from "node:fs";
17080
+ import { join as join24 } from "node:path";
17069
17081
  import { fileURLToPath as fileURLToPath13 } from "node:url";
17070
17082
  import { execSync as execSync2 } from "node:child_process";
17071
17083
  function validateRelease() {
17072
17084
  const checks = [];
17073
17085
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
17074
17086
  try {
17075
- const changelog = readFileSync27(join23(ROOT8, "CHANGELOG.md"), "utf8");
17087
+ const changelog = readFileSync27(join24(ROOT8, "CHANGELOG.md"), "utf8");
17076
17088
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
17077
17089
  const changelogVersion = m?.[1] ?? null;
17078
17090
  checks.push({
@@ -17104,22 +17116,22 @@ function validateRelease() {
17104
17116
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
17105
17117
  }
17106
17118
  function lockfilePresent() {
17107
- return existsSync12(join23(ROOT8, "package-lock.json")) || existsSync12(join23(ROOT8, "bun.lock")) || existsSync12(join23(ROOT8, "bun.lockb"));
17119
+ return existsSync13(join24(ROOT8, "package-lock.json")) || existsSync13(join24(ROOT8, "bun.lock")) || existsSync13(join24(ROOT8, "bun.lockb"));
17108
17120
  }
17109
17121
  var ROOT8;
17110
17122
  var init_release = __esm(() => {
17111
17123
  init_version();
17112
- ROOT8 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
17124
+ ROOT8 = join24(fileURLToPath13(new URL(".", import.meta.url)), "..");
17113
17125
  });
17114
17126
 
17115
17127
  // src/dependency.ts
17116
17128
  import { readFileSync as readFileSync28 } from "node:fs";
17117
- import { join as join24 } from "node:path";
17129
+ import { join as join25 } from "node:path";
17118
17130
  import { fileURLToPath as fileURLToPath14 } from "node:url";
17119
17131
  function checkDependencies() {
17120
17132
  let pkg = {};
17121
17133
  try {
17122
- pkg = JSON.parse(readFileSync28(join24(ROOT9, "package.json"), "utf8"));
17134
+ pkg = JSON.parse(readFileSync28(join25(ROOT9, "package.json"), "utf8"));
17123
17135
  } catch {
17124
17136
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
17125
17137
  }
@@ -17142,12 +17154,12 @@ function checkDependencies() {
17142
17154
  }
17143
17155
  var ROOT9;
17144
17156
  var init_dependency = __esm(() => {
17145
- ROOT9 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
17157
+ ROOT9 = join25(fileURLToPath14(new URL(".", import.meta.url)), "..");
17146
17158
  });
17147
17159
 
17148
17160
  // src/cli.ts
17149
- import { readFileSync as readFileSync29, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17150
- import { join as join25, dirname as dirname5 } from "node:path";
17161
+ import { readFileSync as readFileSync29, existsSync as existsSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17162
+ import { join as join26, dirname as dirname5 } from "node:path";
17151
17163
  import { homedir as homedir11 } from "node:os";
17152
17164
  import { fileURLToPath as fileURLToPath15 } from "node:url";
17153
17165
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -17192,7 +17204,7 @@ async function runDoctor() {
17192
17204
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
17193
17205
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
17194
17206
  });
17195
- const dataOk = existsSync13(join25(ROOT10, "chains.json")) && existsSync13(join25(ROOT10, "tools-catalog.json"));
17207
+ const dataOk = existsSync14(join26(ROOT10, "chains.json")) && existsSync14(join26(ROOT10, "tools-catalog.json"));
17196
17208
  issues.push({
17197
17209
  name: "Data layers (chains + tools-catalog)",
17198
17210
  status: dataOk ? "ok" : "fail",
@@ -17243,15 +17255,15 @@ BlitzStrike — ${verdict}`);
17243
17255
  function resolveCommand() {
17244
17256
  if (which("blitzstrike"))
17245
17257
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
17246
- const src = join25(ROOT10, "src", "index.ts");
17247
- if (which("bun") && existsSync13(src))
17258
+ const src = join26(ROOT10, "src", "index.ts");
17259
+ if (which("bun") && existsSync14(src))
17248
17260
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
17249
17261
  if (which("npx"))
17250
17262
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17251
17263
  if (which("bunx"))
17252
17264
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
17253
- const dist = join25(ROOT10, "dist", "index.js");
17254
- if (existsSync13(dist))
17265
+ const dist = join26(ROOT10, "dist", "index.js");
17266
+ if (existsSync14(dist))
17255
17267
  return { command: "bun", args: [dist, "serve", "--mcp"] };
17256
17268
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17257
17269
  }
@@ -17265,7 +17277,7 @@ function readJson(p) {
17265
17277
  function jsonMcpServersWrite(p) {
17266
17278
  return (command, args) => {
17267
17279
  const dir = dirname5(p);
17268
- if (!existsSync13(dir))
17280
+ if (!existsSync14(dir))
17269
17281
  mkdirSync10(dir, { recursive: true });
17270
17282
  const existing = readJson(p) ?? {};
17271
17283
  existing.mcpServers = {
@@ -17278,7 +17290,7 @@ function jsonMcpServersWrite(p) {
17278
17290
  function opencodeWrite(p) {
17279
17291
  return (command, args) => {
17280
17292
  const dir = dirname5(p);
17281
- if (!existsSync13(dir))
17293
+ if (!existsSync14(dir))
17282
17294
  mkdirSync10(dir, { recursive: true });
17283
17295
  const existing = readJson(p) ?? {};
17284
17296
  existing.mcp = {
@@ -17290,16 +17302,16 @@ function opencodeWrite(p) {
17290
17302
  };
17291
17303
  }
17292
17304
  function copyOpenCodeAgents() {
17293
- const srcDir = join25(ROOT10, "opencode-agents");
17294
- const dstDir = join25(homedir11(), ".config", "opencode", "agents");
17295
- if (!existsSync13(srcDir))
17305
+ const srcDir = join26(ROOT10, "opencode-agents");
17306
+ const dstDir = join26(homedir11(), ".config", "opencode", "agents");
17307
+ if (!existsSync14(srcDir))
17296
17308
  return;
17297
- if (!existsSync13(dstDir))
17309
+ if (!existsSync14(dstDir))
17298
17310
  mkdirSync10(dstDir, { recursive: true });
17299
17311
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
17300
- const src = join25(srcDir, f);
17301
- const dst = join25(dstDir, f);
17302
- if (existsSync13(src)) {
17312
+ const src = join26(srcDir, f);
17313
+ const dst = join26(dstDir, f);
17314
+ if (existsSync14(src)) {
17303
17315
  writeFileSync9(dst, readFileSync29(src, "utf8"));
17304
17316
  }
17305
17317
  }
@@ -17307,9 +17319,9 @@ function copyOpenCodeAgents() {
17307
17319
  function codexWrite(p) {
17308
17320
  return (command, args) => {
17309
17321
  const dir = dirname5(p);
17310
- if (!existsSync13(dir))
17322
+ if (!existsSync14(dir))
17311
17323
  mkdirSync10(dir, { recursive: true });
17312
- let existing = existsSync13(p) ? readFileSync29(p, "utf8") : "";
17324
+ let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
17313
17325
  if (!existing.trimEnd().endsWith(`
17314
17326
  `))
17315
17327
  existing += `
@@ -17326,9 +17338,9 @@ args = ${JSON.stringify(args)}
17326
17338
  function hermesWrite(p) {
17327
17339
  return (command, args) => {
17328
17340
  const dir = dirname5(p);
17329
- if (!existsSync13(dir))
17341
+ if (!existsSync14(dir))
17330
17342
  mkdirSync10(dir, { recursive: true });
17331
- let existing = existsSync13(p) ? readFileSync29(p, "utf8") : "";
17343
+ let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
17332
17344
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
17333
17345
  if (!existing.trimEnd().endsWith(`
17334
17346
  `))
@@ -17355,74 +17367,74 @@ mcp_servers:
17355
17367
  function detectAgents() {
17356
17368
  const home = homedir11();
17357
17369
  const agents = [];
17358
- const claudeJson = join25(home, ".claude.json");
17370
+ const claudeJson = join26(home, ".claude.json");
17359
17371
  agents.push({
17360
17372
  name: "Claude Code",
17361
17373
  detectPath: claudeJson,
17362
- installed: () => existsSync13(claudeJson) || which("claude"),
17374
+ installed: () => existsSync14(claudeJson) || which("claude"),
17363
17375
  write: jsonMcpServersWrite(claudeJson)
17364
17376
  });
17365
- const claudeDesktop = join25(home, ".config", "Claude", "claude_desktop_config.json");
17377
+ const claudeDesktop = join26(home, ".config", "Claude", "claude_desktop_config.json");
17366
17378
  agents.push({
17367
17379
  name: "Claude Desktop",
17368
17380
  detectPath: claudeDesktop,
17369
- installed: () => existsSync13(claudeDesktop),
17381
+ installed: () => existsSync14(claudeDesktop),
17370
17382
  write: jsonMcpServersWrite(claudeDesktop)
17371
17383
  });
17372
- const cursor = join25(home, ".cursor", "mcp.json");
17384
+ const cursor = join26(home, ".cursor", "mcp.json");
17373
17385
  agents.push({
17374
17386
  name: "Cursor",
17375
17387
  detectPath: cursor,
17376
- installed: () => existsSync13(join25(home, ".cursor")) || which("cursor"),
17388
+ installed: () => existsSync14(join26(home, ".cursor")) || which("cursor"),
17377
17389
  write: jsonMcpServersWrite(cursor)
17378
17390
  });
17379
- const opencode = join25(home, ".config", "opencode", "opencode.jsonc");
17391
+ const opencode = join26(home, ".config", "opencode", "opencode.jsonc");
17380
17392
  agents.push({
17381
17393
  name: "OpenCode",
17382
17394
  detectPath: opencode,
17383
- installed: () => existsSync13(join25(home, ".config", "opencode")) || which("opencode"),
17395
+ installed: () => existsSync14(join26(home, ".config", "opencode")) || which("opencode"),
17384
17396
  write: opencodeWrite(opencode)
17385
17397
  });
17386
- const codex = join25(home, ".codex", "config.toml");
17398
+ const codex = join26(home, ".codex", "config.toml");
17387
17399
  agents.push({
17388
17400
  name: "Codex",
17389
17401
  detectPath: codex,
17390
- installed: () => existsSync13(join25(home, ".codex")) || which("codex"),
17402
+ installed: () => existsSync14(join26(home, ".codex")) || which("codex"),
17391
17403
  write: codexWrite(codex)
17392
17404
  });
17393
- const hermes = join25(home, ".hermes", "config.yaml");
17405
+ const hermes = join26(home, ".hermes", "config.yaml");
17394
17406
  agents.push({
17395
17407
  name: "Hermes",
17396
17408
  detectPath: hermes,
17397
- installed: () => existsSync13(join25(home, ".hermes")) || which("hermes"),
17409
+ installed: () => existsSync14(join26(home, ".hermes")) || which("hermes"),
17398
17410
  write: hermesWrite(hermes)
17399
17411
  });
17400
- const gemini = join25(home, ".gemini", "settings.json");
17412
+ const gemini = join26(home, ".gemini", "settings.json");
17401
17413
  agents.push({
17402
17414
  name: "Gemini CLI",
17403
17415
  detectPath: gemini,
17404
- installed: () => existsSync13(join25(home, ".gemini")) || which("gemini"),
17416
+ installed: () => existsSync14(join26(home, ".gemini")) || which("gemini"),
17405
17417
  write: jsonMcpServersWrite(gemini)
17406
17418
  });
17407
- const windsurf = join25(home, ".codeium", "windsurf", "mcp_config.json");
17419
+ const windsurf = join26(home, ".codeium", "windsurf", "mcp_config.json");
17408
17420
  agents.push({
17409
17421
  name: "Windsurf",
17410
17422
  detectPath: windsurf,
17411
- installed: () => existsSync13(join25(home, ".codeium")) || which("windsurf"),
17423
+ installed: () => existsSync14(join26(home, ".codeium")) || which("windsurf"),
17412
17424
  write: jsonMcpServersWrite(windsurf)
17413
17425
  });
17414
- const copilot = join25(home, ".copilot", "mcp.json");
17426
+ const copilot = join26(home, ".copilot", "mcp.json");
17415
17427
  agents.push({
17416
17428
  name: "Copilot",
17417
17429
  detectPath: copilot,
17418
- installed: () => existsSync13(join25(home, ".copilot")),
17430
+ installed: () => existsSync14(join26(home, ".copilot")),
17419
17431
  write: jsonMcpServersWrite(copilot)
17420
17432
  });
17421
- const cline = join25(home, ".cline", "mcp_settings.json");
17433
+ const cline = join26(home, ".cline", "mcp_settings.json");
17422
17434
  agents.push({
17423
17435
  name: "Cline",
17424
17436
  detectPath: cline,
17425
- installed: () => existsSync13(join25(home, ".cline")),
17437
+ installed: () => existsSync14(join26(home, ".cline")),
17426
17438
  write: jsonMcpServersWrite(cline)
17427
17439
  });
17428
17440
  return agents;
@@ -17540,7 +17552,7 @@ var init_cli = __esm(() => {
17540
17552
  init_release();
17541
17553
  init_dependency();
17542
17554
  init_mcp_status();
17543
- ROOT10 = join25(fileURLToPath15(new URL(".", import.meta.url)), "..");
17555
+ ROOT10 = join26(fileURLToPath15(new URL(".", import.meta.url)), "..");
17544
17556
  });
17545
17557
 
17546
17558
  // node_modules/zod/v3/helpers/util.js
@@ -57868,6 +57880,185 @@ function submissionGate(input) {
57868
57880
  };
57869
57881
  }
57870
57882
 
57883
+ // src/git-history.ts
57884
+ init_complex_bugs();
57885
+ import { execFileSync as execFileSync4 } from "node:child_process";
57886
+ import { existsSync as existsSync10 } from "node:fs";
57887
+ import { join as join17 } from "node:path";
57888
+ function parseDeletions(output) {
57889
+ const out = [];
57890
+ let commit = "";
57891
+ let msg = "";
57892
+ for (const line of output.split(`
57893
+ `)) {
57894
+ if (line.startsWith("__COMMIT__ ")) {
57895
+ const rest = line.slice("__COMMIT__ ".length);
57896
+ const sp = rest.indexOf(" ");
57897
+ commit = sp === -1 ? rest : rest.slice(0, sp);
57898
+ msg = sp === -1 ? "" : rest.slice(sp + 1);
57899
+ } else if (line.startsWith("D\t") || line.startsWith("D ")) {
57900
+ const path = line.slice(2).trim();
57901
+ if (path && commit)
57902
+ out.push({ commit, commit_msg: msg, path });
57903
+ }
57904
+ }
57905
+ return out;
57906
+ }
57907
+ function blobBeforeDeletion(root, commit, path) {
57908
+ return execFileSync4("git", ["-C", root, "show", `${commit}^:${path}`], {
57909
+ encoding: "utf8",
57910
+ maxBuffer: 10 * 1024 * 1024
57911
+ });
57912
+ }
57913
+ function scanGitHistory(root, opts) {
57914
+ const limit = opts?.limit ?? 30;
57915
+ const findings = [];
57916
+ if (!existsSync10(join17(root, ".git"))) {
57917
+ return { root, is_git_repo: false, total: 0, findings: [], error: "not a git repository (no .git dir)" };
57918
+ }
57919
+ if (opts?.secretPattern) {
57920
+ try {
57921
+ const pick = execFileSync4("git", ["-C", root, "log", "-S", opts.secretPattern, "--all", "--format=__COMMIT__ %H %s"], { encoding: "utf8", maxBuffer: 5 * 1024 * 1024 });
57922
+ const commits = pick.split(`
57923
+ `).filter(Boolean).map((l) => {
57924
+ const rest = l.startsWith("__COMMIT__ ") ? l.slice("__COMMIT__ ".length) : l;
57925
+ const sp = rest.indexOf(" ");
57926
+ return { hash: sp === -1 ? rest : rest.slice(0, sp), msg: sp === -1 ? "" : rest.slice(sp + 1) };
57927
+ });
57928
+ return {
57929
+ root,
57930
+ is_git_repo: true,
57931
+ mode: "pickaxe",
57932
+ pattern: opts.secretPattern,
57933
+ total: commits.length,
57934
+ commits,
57935
+ hint: "Commits where the pattern was added or removed — inspect each to recover the secret value."
57936
+ };
57937
+ } catch (e) {
57938
+ return { root, is_git_repo: true, mode: "pickaxe", pattern: opts.secretPattern, total: 0, error: String(e) };
57939
+ }
57940
+ }
57941
+ try {
57942
+ const log = execFileSync4("git", ["-C", root, "log", "--all", "--diff-filter=D", "--name-status", "--format=__COMMIT__ %H %s", "--no-renames"], { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
57943
+ const deletions = parseDeletions(log);
57944
+ const seen = new Set;
57945
+ let checked = 0;
57946
+ for (const d of deletions) {
57947
+ if (findings.length >= limit)
57948
+ break;
57949
+ const key = `${d.path}`;
57950
+ if (seen.has(key))
57951
+ continue;
57952
+ seen.add(key);
57953
+ try {
57954
+ const blob = blobBeforeDeletion(root, d.commit, d.path);
57955
+ checked++;
57956
+ const hits = detectComplexBugs(blob, d.path).filter((f) => f.type === "hardcoded_secret");
57957
+ for (const h of hits) {
57958
+ findings.push({
57959
+ path: d.path,
57960
+ commit: d.commit,
57961
+ commit_msg: d.commit_msg,
57962
+ severity: h.severity,
57963
+ category: h.category,
57964
+ evidence: h.evidence,
57965
+ detail: `A ${h.category} was found in a file DELETED from HEAD but still present in git history (commit ${d.commit}). Removed-later is not fixed — anyone with the repo history can recover it.`
57966
+ });
57967
+ }
57968
+ } catch {}
57969
+ }
57970
+ return {
57971
+ root,
57972
+ is_git_repo: true,
57973
+ mode: "deleted_files",
57974
+ deletion_commits: deletions.length,
57975
+ blobs_checked: checked,
57976
+ total: findings.length,
57977
+ findings,
57978
+ hint: "Secrets in deleted files remain in .git history. Rotate any recovered credential and rewrite/scrub history."
57979
+ };
57980
+ } catch (e) {
57981
+ return { root, is_git_repo: true, mode: "deleted_files", total: 0, findings: [], error: String(e) };
57982
+ }
57983
+ }
57984
+
57985
+ // src/key-validation.ts
57986
+ function parseValueFromBlob(text, varName) {
57987
+ const re = new RegExp(`${varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*[=:]\\s*["']?([^"'\\n]+)`);
57988
+ const m = text.match(re);
57989
+ if (!m)
57990
+ throw new Error(`variable ${varName} not found in blob`);
57991
+ return m[1].trim();
57992
+ }
57993
+ async function httpGet3(url, headers) {
57994
+ try {
57995
+ const res = await fetch(url, { headers, redirect: "follow" });
57996
+ return { status: res.status, body: await res.text() };
57997
+ } catch (e) {
57998
+ return { status: null, body: String(e) };
57999
+ }
58000
+ }
58001
+ async function validateLeakedKey(input) {
58002
+ const endpoints = input.endpoints ?? ["/items"];
58003
+ const scheme = input.scheme ?? "Bearer {value}";
58004
+ const header = input.header ?? "Authorization";
58005
+ let key = input.key ?? "";
58006
+ if (!key && input.blobUrl) {
58007
+ const blobRes = await httpGet3(input.blobUrl, { "User-Agent": "Mozilla/5.0 (recon-agent)" });
58008
+ if (blobRes.status !== 200) {
58009
+ return { ok: false, error: `blob fetch failed (HTTP ${blobRes.status}) — leak gone, privatized, or wrong sha`, step: "blob_fetch" };
58010
+ }
58011
+ try {
58012
+ key = parseValueFromBlob(blobRes.body, input.varName ?? "SECRET_KEY");
58013
+ } catch (e) {
58014
+ return { ok: false, error: String(e), step: "parse" };
58015
+ }
58016
+ }
58017
+ if (!key) {
58018
+ return { ok: false, error: "no key provided and no blobUrl to recover one from", step: "key" };
58019
+ }
58020
+ const authHeader = { [header]: scheme.replace("{value}", key) };
58021
+ const results = [];
58022
+ let proven = false;
58023
+ for (const ep of endpoints) {
58024
+ const url = input.apiBase.replace(/\/$/, "") + (ep.startsWith("/") ? ep : "/" + ep);
58025
+ const r0 = await httpGet3(url, { "User-Agent": "Mozilla/5.0 (recon-agent)" });
58026
+ const r1 = await httpGet3(url, { "User-Agent": "Mozilla/5.0 (recon-agent)", ...authHeader });
58027
+ let verdict = "-";
58028
+ if (r0.status === null || r1.status === null)
58029
+ verdict = "ERROR";
58030
+ else if ((r0.status === 401 || r0.status === 403) && r1.status === 200) {
58031
+ verdict = "BYPASS";
58032
+ proven = true;
58033
+ }
58034
+ let records = null;
58035
+ if (r1.status === 200) {
58036
+ try {
58037
+ records = (JSON.parse(r1.body).data ?? []).length;
58038
+ } catch {
58039
+ records = null;
58040
+ }
58041
+ }
58042
+ results.push({
58043
+ endpoint: ep,
58044
+ no_auth: r0.status,
58045
+ with_key: r1.status,
58046
+ records,
58047
+ verdict,
58048
+ error: verdict === "ERROR" ? r0.status === null ? r0.body : r1.body : undefined
58049
+ });
58050
+ }
58051
+ return {
58052
+ ok: true,
58053
+ proven,
58054
+ key_len: key.length,
58055
+ header,
58056
+ scheme,
58057
+ results,
58058
+ verdict: proven ? "AUTH BYPASS PROVEN — leaked credential valid on live system (read-only)" : "not proven — rotated? different header scheme? check manually"
58059
+ };
58060
+ }
58061
+
57871
58062
  // src/severity.ts
57872
58063
  var VRT_MAP = {
57873
58064
  sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
@@ -57925,12 +58116,12 @@ function listSeverityMap() {
57925
58116
 
57926
58117
  // src/datarefresh.ts
57927
58118
  import { readFileSync as readFileSync18 } from "node:fs";
57928
- import { join as join17, dirname as dirname3 } from "node:path";
58119
+ import { join as join18, dirname as dirname3 } from "node:path";
57929
58120
  import { fileURLToPath as fileURLToPath9 } from "node:url";
57930
58121
  var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
57931
58122
  function loadJson2(rel) {
57932
58123
  try {
57933
- return JSON.parse(readFileSync18(join17(__dirname3, "..", rel), "utf8"));
58124
+ return JSON.parse(readFileSync18(join18(__dirname3, "..", rel), "utf8"));
57934
58125
  } catch {
57935
58126
  return null;
57936
58127
  }
@@ -57966,13 +58157,13 @@ init_variants();
57966
58157
 
57967
58158
  // src/benchmark.ts
57968
58159
  import { readFileSync as readFileSync20, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
57969
- import { join as join19 } from "node:path";
58160
+ import { join as join20 } from "node:path";
57970
58161
  import { fileURLToPath as fileURLToPath11 } from "node:url";
57971
58162
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
57972
58163
  init_complex_bugs();
57973
- var ROOT5 = join19(fileURLToPath11(new URL(".", import.meta.url)), "..");
57974
- var CORPUS = join19(ROOT5, "bench", "corpus");
57975
- var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join19(homedir8(), ".blitzstrike", "corpus");
58164
+ var ROOT5 = join20(fileURLToPath11(new URL(".", import.meta.url)), "..");
58165
+ var CORPUS = join20(ROOT5, "bench", "corpus");
58166
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join20(homedir8(), ".blitzstrike", "corpus");
57976
58167
  function parseCorpusFile(path) {
57977
58168
  let text;
57978
58169
  try {
@@ -58032,7 +58223,7 @@ function loadDir(dir) {
58032
58223
  for (const f of readdirSync5(dir)) {
58033
58224
  if (!f.endsWith(".md"))
58034
58225
  continue;
58035
- const e = parseCorpusFile(join19(dir, f));
58226
+ const e = parseCorpusFile(join20(dir, f));
58036
58227
  if (e)
58037
58228
  entries.push(e);
58038
58229
  }
@@ -58059,7 +58250,7 @@ function captureFalsePositive(input) {
58059
58250
  input.code,
58060
58251
  "```"
58061
58252
  ].filter((l) => l !== null);
58062
- const path = join19(CAPTURED_CORPUS_DIR, `${id}.md`);
58253
+ const path = join20(CAPTURED_CORPUS_DIR, `${id}.md`);
58063
58254
  writeFileSync6(path, lines.join(`
58064
58255
  `) + `
58065
58256
  `, "utf8");
@@ -58081,7 +58272,7 @@ function detect(entry) {
58081
58272
  }
58082
58273
  const lang = entry.language;
58083
58274
  if (lang === "php") {
58084
- const tmp = join19(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
58275
+ const tmp = join20(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
58085
58276
  writeFileSync6(tmp, entry.code);
58086
58277
  const r = analyzeDataFlow2(tmp);
58087
58278
  rmSync3(tmp, { force: true });
@@ -58150,21 +58341,21 @@ function runBenchmark() {
58150
58341
  }
58151
58342
 
58152
58343
  // src/obligations.ts
58153
- import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
58154
- import { join as join20 } from "node:path";
58344
+ import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync7 } from "node:fs";
58345
+ import { join as join21 } from "node:path";
58155
58346
  import { createHash as createHash6 } from "node:crypto";
58156
58347
  import { homedir as homedir9 } from "node:os";
58157
- var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join20(homedir9(), ".blitzstrike");
58158
- var OBLIGATION_PATH = join20(HOME_DIR4, "obligations.jsonl");
58348
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join21(homedir9(), ".blitzstrike");
58349
+ var OBLIGATION_PATH = join21(HOME_DIR4, "obligations.jsonl");
58159
58350
  function ensureDir3() {
58160
- if (!existsSync10(HOME_DIR4))
58351
+ if (!existsSync11(HOME_DIR4))
58161
58352
  mkdirSync8(HOME_DIR4, { recursive: true });
58162
58353
  }
58163
58354
  function shortId2(claim) {
58164
58355
  return `obl-${createHash6("sha256").update(`${claim}\x00${Date.now()}`).digest("hex").slice(0, 12)}`;
58165
58356
  }
58166
58357
  function loadObligations() {
58167
- if (!existsSync10(OBLIGATION_PATH))
58358
+ if (!existsSync11(OBLIGATION_PATH))
58168
58359
  return [];
58169
58360
  try {
58170
58361
  const out = [];
@@ -59167,17 +59358,17 @@ function analyzeJwt(token, opts = {}) {
59167
59358
  }
59168
59359
 
59169
59360
  // src/enterprise-benchmark.ts
59170
- import { existsSync as existsSync11, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
59171
- import { join as join21, resolve } from "node:path";
59361
+ import { existsSync as existsSync12, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
59362
+ import { join as join22, resolve } from "node:path";
59172
59363
  init_complex_bugs();
59173
59364
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
59174
59365
  var ENTERPRISE_FIXTURES = [
59175
- { id: "php-laravel", dir: join21(ROOT6, "php-laravel"), expected_vulns: 4 },
59176
- { id: "js-express", dir: join21(ROOT6, "js-express"), expected_vulns: 2 },
59177
- { id: "py-django", dir: join21(ROOT6, "py-django"), expected_vulns: 2 },
59178
- { id: "java-spring", dir: join21(ROOT6, "java-spring"), expected_vulns: 2 },
59179
- { id: "wp-batch-route", dir: join21(ROOT6, "wp-batch-route"), expected_vulns: 3 },
59180
- { id: "php-complex", dir: join21(ROOT6, "php-complex"), expected_vulns: 4 }
59366
+ { id: "php-laravel", dir: join22(ROOT6, "php-laravel"), expected_vulns: 4 },
59367
+ { id: "js-express", dir: join22(ROOT6, "js-express"), expected_vulns: 2 },
59368
+ { id: "py-django", dir: join22(ROOT6, "py-django"), expected_vulns: 2 },
59369
+ { id: "java-spring", dir: join22(ROOT6, "java-spring"), expected_vulns: 2 },
59370
+ { id: "wp-batch-route", dir: join22(ROOT6, "wp-batch-route"), expected_vulns: 3 },
59371
+ { id: "php-complex", dir: join22(ROOT6, "php-complex"), expected_vulns: 4 }
59181
59372
  ];
59182
59373
  function sourceFiles(root) {
59183
59374
  const out = [];
@@ -59189,7 +59380,7 @@ function sourceFiles(root) {
59189
59380
  return;
59190
59381
  }
59191
59382
  for (const e of entries) {
59192
- const p = join21(dir, e);
59383
+ const p = join22(dir, e);
59193
59384
  let st;
59194
59385
  try {
59195
59386
  st = statSync3(p);
@@ -59228,7 +59419,7 @@ function detectFixture(dir) {
59228
59419
  function runEnterpriseBenchmark() {
59229
59420
  const fixtures = [];
59230
59421
  for (const fx of ENTERPRISE_FIXTURES) {
59231
- if (!existsSync11(fx.dir)) {
59422
+ if (!existsSync12(fx.dir)) {
59232
59423
  fixtures.push({ id: fx.id, available: false, detected: 0, expected: fx.expected_vulns, recall: 0 });
59233
59424
  continue;
59234
59425
  }
@@ -59405,7 +59596,7 @@ function createServer() {
59405
59596
  ` + `- A hard block (Cloudflare/WAF/IP-block/captcha) is NOT the end: mark the finding blocked + note the required vantage (residential / authorized / correct-SNI / --resolve direct-to-origin), then CONTINUE to the next lead (origin bypass, non-prod cluster, mobile APK, subdomain).
59406
59597
  ` + `- Keep trying every lead AND every chain step until each is verified/executed or blocked, THEN generate_report. Never deliver a bare surface map, and never stop at 'found X' when X enables more.
59407
59598
 
59408
- ` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + "DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Calibrate severity with severity_calibrate(type, cvss) to get a P1–P5 tier + VRT-style category. Start each engagement with engagement_scaffold(target, scope) for the folder + scope.md structure, and check data_refresh() to confirm the data layers are consistent. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.\n" + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
59599
+ ` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + "DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Calibrate severity with severity_calibrate(type, cvss) to get a P1–P5 tier + VRT-style category. Start each engagement with engagement_scaffold(target, scope) for the folder + scope.md structure, and check data_refresh() to confirm the data layers are consistent. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.\n" + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified. For a repo, also scan_git_history(root) — secrets DELETED from HEAD still live in .git history (the removed-later-is-not-fixed trap), and it feeds every recovered pre-deletion blob through the same secret detector. Once you recover a credential, prove it is still ACTIVE with validate_leaked_key(apiBase, endpoints, key|blobUrl) — a READ-ONLY differential test (401-without-key vs 200-with-key = auth bypass PROVEN).\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
59409
59600
  ` + `- When a lead needs DOM/JS precision — DOM-XSS sink execution, AJAX endpoint + payload interception, JS runtime errors, redirect chains, SSO/token flow, or anything a static HTML glance cannot prove — call drive_devtools(target). It spawns chrome-devtools-mcp and drives a REAL headless browser (navigate -> list_network_requests -> list_console_messages -> evaluate_script), giving reproducible evidence instead of a guess.
59410
59601
  ` + `- Before calling it, check_mcp() to confirm the browser MCP is available; if not, blitzstrike install-tools or npm i -g chrome-devtools-mcp. Never substitute eyeballing minified JS or a static sink guess when a live browser can PROVE it. This is the precision + consistency layer.
59411
59602
  ` + "EXTERNAL MCP SERVERS (verified, connect them — NOT one-shot CLIs): chrome-devtools-mcp (29 tools: list_network_requests/get_network_request for raw HTTP interception, list_console_messages for JS errors + DOM-XSS, evaluate_script to run JS, take_screenshot) -> stdio chrome-devtools-mcp --headless --chromeArg=--no-sandbox --chromeArg=--disable-gpu (as root/container this is MANDATORY or the browser crashes; add --proxyServer http://127.0.0.1:8080 to route through Burp, --acceptInsecureCerts for bad TLS). burp-suite-mcp (proxy history + active scan + repeater) -> NOT npm: build the JAR (git clone https://github.com/PortSwigger/mcp-server.git && cd mcp-server && ./gradlew embedProxyJar), load it in Burp Extensions, then connect the client to http://127.0.0.1:9876 (SSE) or the stdio proxy java -jar mcp-proxy-all.jar --sse-url http://127.0.0.1:9876."
@@ -59849,6 +60040,32 @@ ${statusLine}` : statusLine },
59849
60040
  const r = synonymsFor(term);
59850
60041
  return { content: [{ type: "text", text: JSON.stringify({ ...r, pattern: patternLookup(r.canonical) }) }] };
59851
60042
  });
60043
+ server.registerTool("scan_git_history", {
60044
+ title: "Mine git history for deleted secrets",
60045
+ description: "DETECT: scan a git repo's HISTORY (not just HEAD) for secrets in files that were DELETED — the 'removed later != fixed' trap. Runs git log --diff-filter=D + git show <sha>^:<path> (read-only) and feeds every recovered pre-deletion blob through the hardcoded_secret detector. Pass secretPattern to instead pickaxe-search for one specific token through all commits.",
60046
+ inputSchema: {
60047
+ root: string2().describe("Path to the git repo root (must contain .git)."),
60048
+ secretPattern: string2().optional().describe("Optional: a specific secret token to pickaxe-search (git log -S) through history."),
60049
+ limit: number2().int().optional().describe("Max findings (default 30).")
60050
+ }
60051
+ }, async ({ root, secretPattern, limit }) => {
60052
+ return { content: [{ type: "text", text: JSON.stringify(scanGitHistory(root, { secretPattern, limit })) }] };
60053
+ });
60054
+ server.registerTool("validate_leaked_key", {
60055
+ title: "Prove a leaked key is still active (differential)",
60056
+ description: "VERIFY: prove a recovered credential is ACTIVE on a live API with a READ-ONLY differential test — the same endpoint is fetched without the key (expect 401/403) and with the key (expect 200). A 401-without / 200-with contrast is proof of auth bypass. Pass the raw key, or a blobUrl + varName to fetch/parse it from a leaked file first. No mutating request is ever sent.",
60057
+ inputSchema: {
60058
+ key: string2().optional().describe("Raw secret value to test (skip blob fetch)."),
60059
+ blobUrl: string2().optional().describe("Or: raw-blob URL (e.g. forge raw/commit/<sha>^/<path>) to fetch + parse the key from."),
60060
+ varName: string2().optional().describe("Variable name to parse from the blob (default SECRET_KEY)."),
60061
+ apiBase: string2().describe("Live API base URL."),
60062
+ endpoints: array(string2()).optional().describe("Read-only GET endpoints to probe (default ['/items'])."),
60063
+ scheme: string2().optional().describe("Header value template, e.g. 'Bearer {value}' (default)."),
60064
+ header: string2().optional().describe("Header name (default Authorization).")
60065
+ }
60066
+ }, async (input) => {
60067
+ return { content: [{ type: "text", text: JSON.stringify(await validateLeakedKey(input)) }] };
60068
+ });
59852
60069
  server.registerTool("fofa_search", {
59853
60070
  title: "FOFA asset search",
59854
60071
  description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
@@ -61193,14 +61410,14 @@ async function serve() {
61193
61410
 
61194
61411
  // src/index.ts
61195
61412
  init_cli();
61196
- import { readFileSync as readFileSync30, existsSync as existsSync14 } from "node:fs";
61197
- import { join as join26 } from "node:path";
61413
+ import { readFileSync as readFileSync30, existsSync as existsSync15 } from "node:fs";
61414
+ import { join as join27 } from "node:path";
61198
61415
  import { fileURLToPath as fileURLToPath16 } from "node:url";
61199
- var ROOT11 = join26(fileURLToPath16(new URL(".", import.meta.url)), "..");
61416
+ var ROOT11 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
61200
61417
  function readVersion() {
61201
61418
  try {
61202
- const p = join26(ROOT11, "package.json");
61203
- if (existsSync14(p))
61419
+ const p = join27(ROOT11, "package.json");
61420
+ if (existsSync15(p))
61204
61421
  return JSON.parse(readFileSync30(p, "utf8")).version ?? "1.0.0";
61205
61422
  } catch {}
61206
61423
  return "1.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.67",
3
+ "version": "2.4.68",
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": {