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.
- package/CHANGELOG.md +18 -0
- package/dist/index.js +315 -98
- 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:
|
|
16531
|
-
severity:
|
|
16542
|
+
category: cat,
|
|
16543
|
+
severity: sev,
|
|
16532
16544
|
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16533
|
-
detail
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
16953
|
-
const templatesPath =
|
|
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 =
|
|
16964
|
-
const localTemplates =
|
|
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 =
|
|
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(
|
|
16997
|
+
copyDir(join23(tmp, "payloads"), payloadsPath);
|
|
16986
16998
|
payloadsSynced = true;
|
|
16987
16999
|
}
|
|
16988
17000
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
16989
|
-
copyDir(
|
|
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 =
|
|
17012
|
-
const d =
|
|
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 =
|
|
17022
|
-
DATA_ROOT2 = process.env.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
|
|
17068
|
-
import { join as
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
17150
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
17247
|
-
if (which("bun") &&
|
|
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 =
|
|
17254
|
-
if (
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
17294
|
-
const dstDir =
|
|
17295
|
-
if (!
|
|
17305
|
+
const srcDir = join26(ROOT10, "opencode-agents");
|
|
17306
|
+
const dstDir = join26(homedir11(), ".config", "opencode", "agents");
|
|
17307
|
+
if (!existsSync14(srcDir))
|
|
17296
17308
|
return;
|
|
17297
|
-
if (!
|
|
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 =
|
|
17301
|
-
const dst =
|
|
17302
|
-
if (
|
|
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 (!
|
|
17322
|
+
if (!existsSync14(dir))
|
|
17311
17323
|
mkdirSync10(dir, { recursive: true });
|
|
17312
|
-
let existing =
|
|
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 (!
|
|
17341
|
+
if (!existsSync14(dir))
|
|
17330
17342
|
mkdirSync10(dir, { recursive: true });
|
|
17331
|
-
let existing =
|
|
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 =
|
|
17370
|
+
const claudeJson = join26(home, ".claude.json");
|
|
17359
17371
|
agents.push({
|
|
17360
17372
|
name: "Claude Code",
|
|
17361
17373
|
detectPath: claudeJson,
|
|
17362
|
-
installed: () =>
|
|
17374
|
+
installed: () => existsSync14(claudeJson) || which("claude"),
|
|
17363
17375
|
write: jsonMcpServersWrite(claudeJson)
|
|
17364
17376
|
});
|
|
17365
|
-
const claudeDesktop =
|
|
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: () =>
|
|
17381
|
+
installed: () => existsSync14(claudeDesktop),
|
|
17370
17382
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
17371
17383
|
});
|
|
17372
|
-
const cursor =
|
|
17384
|
+
const cursor = join26(home, ".cursor", "mcp.json");
|
|
17373
17385
|
agents.push({
|
|
17374
17386
|
name: "Cursor",
|
|
17375
17387
|
detectPath: cursor,
|
|
17376
|
-
installed: () =>
|
|
17388
|
+
installed: () => existsSync14(join26(home, ".cursor")) || which("cursor"),
|
|
17377
17389
|
write: jsonMcpServersWrite(cursor)
|
|
17378
17390
|
});
|
|
17379
|
-
const opencode =
|
|
17391
|
+
const opencode = join26(home, ".config", "opencode", "opencode.jsonc");
|
|
17380
17392
|
agents.push({
|
|
17381
17393
|
name: "OpenCode",
|
|
17382
17394
|
detectPath: opencode,
|
|
17383
|
-
installed: () =>
|
|
17395
|
+
installed: () => existsSync14(join26(home, ".config", "opencode")) || which("opencode"),
|
|
17384
17396
|
write: opencodeWrite(opencode)
|
|
17385
17397
|
});
|
|
17386
|
-
const codex =
|
|
17398
|
+
const codex = join26(home, ".codex", "config.toml");
|
|
17387
17399
|
agents.push({
|
|
17388
17400
|
name: "Codex",
|
|
17389
17401
|
detectPath: codex,
|
|
17390
|
-
installed: () =>
|
|
17402
|
+
installed: () => existsSync14(join26(home, ".codex")) || which("codex"),
|
|
17391
17403
|
write: codexWrite(codex)
|
|
17392
17404
|
});
|
|
17393
|
-
const hermes =
|
|
17405
|
+
const hermes = join26(home, ".hermes", "config.yaml");
|
|
17394
17406
|
agents.push({
|
|
17395
17407
|
name: "Hermes",
|
|
17396
17408
|
detectPath: hermes,
|
|
17397
|
-
installed: () =>
|
|
17409
|
+
installed: () => existsSync14(join26(home, ".hermes")) || which("hermes"),
|
|
17398
17410
|
write: hermesWrite(hermes)
|
|
17399
17411
|
});
|
|
17400
|
-
const gemini =
|
|
17412
|
+
const gemini = join26(home, ".gemini", "settings.json");
|
|
17401
17413
|
agents.push({
|
|
17402
17414
|
name: "Gemini CLI",
|
|
17403
17415
|
detectPath: gemini,
|
|
17404
|
-
installed: () =>
|
|
17416
|
+
installed: () => existsSync14(join26(home, ".gemini")) || which("gemini"),
|
|
17405
17417
|
write: jsonMcpServersWrite(gemini)
|
|
17406
17418
|
});
|
|
17407
|
-
const windsurf =
|
|
17419
|
+
const windsurf = join26(home, ".codeium", "windsurf", "mcp_config.json");
|
|
17408
17420
|
agents.push({
|
|
17409
17421
|
name: "Windsurf",
|
|
17410
17422
|
detectPath: windsurf,
|
|
17411
|
-
installed: () =>
|
|
17423
|
+
installed: () => existsSync14(join26(home, ".codeium")) || which("windsurf"),
|
|
17412
17424
|
write: jsonMcpServersWrite(windsurf)
|
|
17413
17425
|
});
|
|
17414
|
-
const copilot =
|
|
17426
|
+
const copilot = join26(home, ".copilot", "mcp.json");
|
|
17415
17427
|
agents.push({
|
|
17416
17428
|
name: "Copilot",
|
|
17417
17429
|
detectPath: copilot,
|
|
17418
|
-
installed: () =>
|
|
17430
|
+
installed: () => existsSync14(join26(home, ".copilot")),
|
|
17419
17431
|
write: jsonMcpServersWrite(copilot)
|
|
17420
17432
|
});
|
|
17421
|
-
const cline =
|
|
17433
|
+
const cline = join26(home, ".cline", "mcp_settings.json");
|
|
17422
17434
|
agents.push({
|
|
17423
17435
|
name: "Cline",
|
|
17424
17436
|
detectPath: cline,
|
|
17425
|
-
installed: () =>
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
57974
|
-
var CORPUS =
|
|
57975
|
-
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ??
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
58154
|
-
import { join as
|
|
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 ??
|
|
58158
|
-
var OBLIGATION_PATH =
|
|
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 (!
|
|
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 (!
|
|
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
|
|
59171
|
-
import { join as
|
|
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:
|
|
59176
|
-
{ id: "js-express", dir:
|
|
59177
|
-
{ id: "py-django", dir:
|
|
59178
|
-
{ id: "java-spring", dir:
|
|
59179
|
-
{ id: "wp-batch-route", dir:
|
|
59180
|
-
{ id: "php-complex", dir:
|
|
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 =
|
|
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 (!
|
|
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
|
|
61197
|
-
import { join as
|
|
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 =
|
|
61416
|
+
var ROOT11 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
|
|
61200
61417
|
function readVersion() {
|
|
61201
61418
|
try {
|
|
61202
|
-
const p =
|
|
61203
|
-
if (
|
|
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.
|
|
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": {
|