blitzstrike 2.4.66 → 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 +34 -0
- package/dist/index.js +328 -105
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ 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
|
+
|
|
26
|
+
## [2.4.67] — 2026-09-14
|
|
27
|
+
|
|
28
|
+
### Fixed — Whole-code interconnection audit (2 bugs)
|
|
29
|
+
|
|
30
|
+
- `submission_gate` is now FAIL-CLOSED: a question passes only when EXPLICITLY
|
|
31
|
+
asserted true (`=== true`, not `!== false`), so an empty/unset finding fails the
|
|
32
|
+
gate instead of passing 7/7 by default. One false = kill, and a missing field is
|
|
33
|
+
a false.
|
|
34
|
+
- Redaction now covers detection end-to-end: 6 secret formats the detector found
|
|
35
|
+
but the redactor did not strip (Slack webhook URL, Firebase URL, basic-auth URL,
|
|
36
|
+
Sentry DSN, ngrok auth token, Discord bot token) now redact — the invariant
|
|
37
|
+
"every secret the detector finds is stripped from reports/logs" is restored.
|
|
38
|
+
- Added a whole-code interconnection audit (detectors ↔ CWE ↔ patterns ↔ chains ↔
|
|
39
|
+
variants ↔ synonyms ↔ severity ↔ secrets + edge cases) + regression tests for
|
|
40
|
+
both fixes.
|
|
41
|
+
|
|
8
42
|
## [2.4.66] — 2026-09-14
|
|
9
43
|
|
|
10
44
|
### Added — Per-engagement pattern recall (intelligence ledger)
|
package/dist/index.js
CHANGED
|
@@ -7495,6 +7495,12 @@ var init_secrets = __esm(() => {
|
|
|
7495
7495
|
[/\blin_api_[A-Za-z0-9]{40}\b/g, "[REDACTED]"],
|
|
7496
7496
|
[/\b(?:NRAA|NRAK|NRBR)-[A-F0-9]{27}\b/g, "[REDACTED]"],
|
|
7497
7497
|
[/\b\d{8,10}:[A-Za-z0-9_-]{35}\b/g, "[REDACTED]"],
|
|
7498
|
+
[/\b[12][A-Za-z0-9]{26}_[A-Za-z0-9]{32,}\b/g, "[REDACTED]"],
|
|
7499
|
+
[/\b[MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27}\b/g, "[REDACTED]"],
|
|
7500
|
+
[/https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9\/]+/g, "[REDACTED]"],
|
|
7501
|
+
[/\bhttps?:\/\/[a-z0-9-]+\.firebaseio\.com\b/g, "[REDACTED]"],
|
|
7502
|
+
[/https:\/\/[a-f0-9]+@o[0-9]+\.ingest\.sentry\.io\/[0-9]+/g, "[REDACTED]"],
|
|
7503
|
+
[/https?:\/\/[^/\s:@]+:[^/\s:@]+@[^/\s]+/g, "[REDACTED]"],
|
|
7498
7504
|
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, "[REDACTED]"],
|
|
7499
7505
|
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "[REDACTED]"]
|
|
7500
7506
|
];
|
|
@@ -16517,14 +16523,26 @@ function detectHardcodedSecret(code, file, out) {
|
|
|
16517
16523
|
continue;
|
|
16518
16524
|
const ln = lineNo2(code, m.index);
|
|
16519
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
|
+
}
|
|
16520
16538
|
out.push({
|
|
16521
16539
|
file,
|
|
16522
16540
|
line: ln,
|
|
16523
16541
|
type: "hardcoded_secret",
|
|
16524
|
-
category:
|
|
16525
|
-
severity:
|
|
16542
|
+
category: cat,
|
|
16543
|
+
severity: sev,
|
|
16526
16544
|
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16527
|
-
detail
|
|
16545
|
+
detail
|
|
16528
16546
|
});
|
|
16529
16547
|
}
|
|
16530
16548
|
for (const fmt of SECRET_FORMATS) {
|
|
@@ -16848,11 +16866,11 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
|
|
|
16848
16866
|
|
|
16849
16867
|
// src/variants.ts
|
|
16850
16868
|
import { readFileSync as readFileSync19 } from "node:fs";
|
|
16851
|
-
import { join as
|
|
16869
|
+
import { join as join19, dirname as dirname4 } from "node:path";
|
|
16852
16870
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
16853
16871
|
function loadVariants() {
|
|
16854
16872
|
try {
|
|
16855
|
-
const doc = JSON.parse(readFileSync19(
|
|
16873
|
+
const doc = JSON.parse(readFileSync19(join19(__dirname4, "..", "intelligence", "variants.json"), "utf8"));
|
|
16856
16874
|
return doc.variants ?? [];
|
|
16857
16875
|
} catch {
|
|
16858
16876
|
return [];
|
|
@@ -16912,7 +16930,7 @@ var init_variants = __esm(() => {
|
|
|
16912
16930
|
|
|
16913
16931
|
// src/sync.ts
|
|
16914
16932
|
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync25, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
|
|
16915
|
-
import { join as
|
|
16933
|
+
import { join as join23 } from "node:path";
|
|
16916
16934
|
import { homedir as homedir10 } from "node:os";
|
|
16917
16935
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
16918
16936
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -16929,7 +16947,7 @@ function countFiles(dir) {
|
|
|
16929
16947
|
const walk = (d) => {
|
|
16930
16948
|
for (const e of readdirSync7(d, { withFileTypes: true })) {
|
|
16931
16949
|
if (e.isDirectory())
|
|
16932
|
-
walk(
|
|
16950
|
+
walk(join23(d, e.name));
|
|
16933
16951
|
else
|
|
16934
16952
|
n++;
|
|
16935
16953
|
}
|
|
@@ -16943,8 +16961,8 @@ function countFiles(dir) {
|
|
|
16943
16961
|
function syncData() {
|
|
16944
16962
|
if (!dirExists(DATA_ROOT2))
|
|
16945
16963
|
mkdirSync9(DATA_ROOT2, { recursive: true });
|
|
16946
|
-
const payloadsPath =
|
|
16947
|
-
const templatesPath =
|
|
16964
|
+
const payloadsPath = join23(DATA_ROOT2, "payloads");
|
|
16965
|
+
const templatesPath = join23(DATA_ROOT2, "templates");
|
|
16948
16966
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
16949
16967
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
16950
16968
|
if (hasPayloads && hasTemplates) {
|
|
@@ -16954,8 +16972,8 @@ function syncData() {
|
|
|
16954
16972
|
data_root: DATA_ROOT2
|
|
16955
16973
|
};
|
|
16956
16974
|
}
|
|
16957
|
-
const localPayloads =
|
|
16958
|
-
const localTemplates =
|
|
16975
|
+
const localPayloads = join23(ROOT7, "payloads");
|
|
16976
|
+
const localTemplates = join23(ROOT7, "templates");
|
|
16959
16977
|
let payloadsSynced = false;
|
|
16960
16978
|
let templatesSynced = false;
|
|
16961
16979
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -16968,7 +16986,7 @@ function syncData() {
|
|
|
16968
16986
|
}
|
|
16969
16987
|
const gitOk = spawnSync4("git", ["--version"]).status === 0;
|
|
16970
16988
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
16971
|
-
const tmp =
|
|
16989
|
+
const tmp = join23(DATA_ROOT2, ".tmp-clone");
|
|
16972
16990
|
if (dirExists(tmp))
|
|
16973
16991
|
rmSync4(tmp, { recursive: true, force: true });
|
|
16974
16992
|
const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -16976,11 +16994,11 @@ function syncData() {
|
|
|
16976
16994
|
const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
16977
16995
|
if (sparse.status === 0) {
|
|
16978
16996
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
16979
|
-
copyDir(
|
|
16997
|
+
copyDir(join23(tmp, "payloads"), payloadsPath);
|
|
16980
16998
|
payloadsSynced = true;
|
|
16981
16999
|
}
|
|
16982
17000
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
16983
|
-
copyDir(
|
|
17001
|
+
copyDir(join23(tmp, "templates"), templatesPath);
|
|
16984
17002
|
templatesSynced = true;
|
|
16985
17003
|
}
|
|
16986
17004
|
}
|
|
@@ -17002,8 +17020,8 @@ function copyDir(src, dst) {
|
|
|
17002
17020
|
function manualCopy(src, dst) {
|
|
17003
17021
|
mkdirSync9(dst, { recursive: true });
|
|
17004
17022
|
for (const e of readdirSync7(src, { withFileTypes: true })) {
|
|
17005
|
-
const s =
|
|
17006
|
-
const d =
|
|
17023
|
+
const s = join23(src, e.name);
|
|
17024
|
+
const d = join23(dst, e.name);
|
|
17007
17025
|
if (e.isDirectory())
|
|
17008
17026
|
manualCopy(s, d);
|
|
17009
17027
|
else
|
|
@@ -17012,8 +17030,8 @@ function manualCopy(src, dst) {
|
|
|
17012
17030
|
}
|
|
17013
17031
|
var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
17014
17032
|
var init_sync = __esm(() => {
|
|
17015
|
-
ROOT7 =
|
|
17016
|
-
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");
|
|
17017
17035
|
});
|
|
17018
17036
|
|
|
17019
17037
|
// src/update.ts
|
|
@@ -17058,15 +17076,15 @@ var init_update = __esm(() => {
|
|
|
17058
17076
|
});
|
|
17059
17077
|
|
|
17060
17078
|
// src/release.ts
|
|
17061
|
-
import { readFileSync as readFileSync27, existsSync as
|
|
17062
|
-
import { join as
|
|
17079
|
+
import { readFileSync as readFileSync27, existsSync as existsSync13 } from "node:fs";
|
|
17080
|
+
import { join as join24 } from "node:path";
|
|
17063
17081
|
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
17064
17082
|
import { execSync as execSync2 } from "node:child_process";
|
|
17065
17083
|
function validateRelease() {
|
|
17066
17084
|
const checks = [];
|
|
17067
17085
|
checks.push({ name: "package.json version", ok: true, detail: VERSION });
|
|
17068
17086
|
try {
|
|
17069
|
-
const changelog = readFileSync27(
|
|
17087
|
+
const changelog = readFileSync27(join24(ROOT8, "CHANGELOG.md"), "utf8");
|
|
17070
17088
|
const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
|
|
17071
17089
|
const changelogVersion = m?.[1] ?? null;
|
|
17072
17090
|
checks.push({
|
|
@@ -17098,22 +17116,22 @@ function validateRelease() {
|
|
|
17098
17116
|
return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
|
|
17099
17117
|
}
|
|
17100
17118
|
function lockfilePresent() {
|
|
17101
|
-
return
|
|
17119
|
+
return existsSync13(join24(ROOT8, "package-lock.json")) || existsSync13(join24(ROOT8, "bun.lock")) || existsSync13(join24(ROOT8, "bun.lockb"));
|
|
17102
17120
|
}
|
|
17103
17121
|
var ROOT8;
|
|
17104
17122
|
var init_release = __esm(() => {
|
|
17105
17123
|
init_version();
|
|
17106
|
-
ROOT8 =
|
|
17124
|
+
ROOT8 = join24(fileURLToPath13(new URL(".", import.meta.url)), "..");
|
|
17107
17125
|
});
|
|
17108
17126
|
|
|
17109
17127
|
// src/dependency.ts
|
|
17110
17128
|
import { readFileSync as readFileSync28 } from "node:fs";
|
|
17111
|
-
import { join as
|
|
17129
|
+
import { join as join25 } from "node:path";
|
|
17112
17130
|
import { fileURLToPath as fileURLToPath14 } from "node:url";
|
|
17113
17131
|
function checkDependencies() {
|
|
17114
17132
|
let pkg = {};
|
|
17115
17133
|
try {
|
|
17116
|
-
pkg = JSON.parse(readFileSync28(
|
|
17134
|
+
pkg = JSON.parse(readFileSync28(join25(ROOT9, "package.json"), "utf8"));
|
|
17117
17135
|
} catch {
|
|
17118
17136
|
return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
|
|
17119
17137
|
}
|
|
@@ -17136,12 +17154,12 @@ function checkDependencies() {
|
|
|
17136
17154
|
}
|
|
17137
17155
|
var ROOT9;
|
|
17138
17156
|
var init_dependency = __esm(() => {
|
|
17139
|
-
ROOT9 =
|
|
17157
|
+
ROOT9 = join25(fileURLToPath14(new URL(".", import.meta.url)), "..");
|
|
17140
17158
|
});
|
|
17141
17159
|
|
|
17142
17160
|
// src/cli.ts
|
|
17143
|
-
import { readFileSync as readFileSync29, existsSync as
|
|
17144
|
-
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";
|
|
17145
17163
|
import { homedir as homedir11 } from "node:os";
|
|
17146
17164
|
import { fileURLToPath as fileURLToPath15 } from "node:url";
|
|
17147
17165
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
@@ -17186,7 +17204,7 @@ async function runDoctor() {
|
|
|
17186
17204
|
detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
|
|
17187
17205
|
fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
|
|
17188
17206
|
});
|
|
17189
|
-
const dataOk =
|
|
17207
|
+
const dataOk = existsSync14(join26(ROOT10, "chains.json")) && existsSync14(join26(ROOT10, "tools-catalog.json"));
|
|
17190
17208
|
issues.push({
|
|
17191
17209
|
name: "Data layers (chains + tools-catalog)",
|
|
17192
17210
|
status: dataOk ? "ok" : "fail",
|
|
@@ -17237,15 +17255,15 @@ BlitzStrike — ${verdict}`);
|
|
|
17237
17255
|
function resolveCommand() {
|
|
17238
17256
|
if (which("blitzstrike"))
|
|
17239
17257
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
17240
|
-
const src =
|
|
17241
|
-
if (which("bun") &&
|
|
17258
|
+
const src = join26(ROOT10, "src", "index.ts");
|
|
17259
|
+
if (which("bun") && existsSync14(src))
|
|
17242
17260
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
17243
17261
|
if (which("npx"))
|
|
17244
17262
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
17245
17263
|
if (which("bunx"))
|
|
17246
17264
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
17247
|
-
const dist =
|
|
17248
|
-
if (
|
|
17265
|
+
const dist = join26(ROOT10, "dist", "index.js");
|
|
17266
|
+
if (existsSync14(dist))
|
|
17249
17267
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
17250
17268
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
17251
17269
|
}
|
|
@@ -17259,7 +17277,7 @@ function readJson(p) {
|
|
|
17259
17277
|
function jsonMcpServersWrite(p) {
|
|
17260
17278
|
return (command, args) => {
|
|
17261
17279
|
const dir = dirname5(p);
|
|
17262
|
-
if (!
|
|
17280
|
+
if (!existsSync14(dir))
|
|
17263
17281
|
mkdirSync10(dir, { recursive: true });
|
|
17264
17282
|
const existing = readJson(p) ?? {};
|
|
17265
17283
|
existing.mcpServers = {
|
|
@@ -17272,7 +17290,7 @@ function jsonMcpServersWrite(p) {
|
|
|
17272
17290
|
function opencodeWrite(p) {
|
|
17273
17291
|
return (command, args) => {
|
|
17274
17292
|
const dir = dirname5(p);
|
|
17275
|
-
if (!
|
|
17293
|
+
if (!existsSync14(dir))
|
|
17276
17294
|
mkdirSync10(dir, { recursive: true });
|
|
17277
17295
|
const existing = readJson(p) ?? {};
|
|
17278
17296
|
existing.mcp = {
|
|
@@ -17284,16 +17302,16 @@ function opencodeWrite(p) {
|
|
|
17284
17302
|
};
|
|
17285
17303
|
}
|
|
17286
17304
|
function copyOpenCodeAgents() {
|
|
17287
|
-
const srcDir =
|
|
17288
|
-
const dstDir =
|
|
17289
|
-
if (!
|
|
17305
|
+
const srcDir = join26(ROOT10, "opencode-agents");
|
|
17306
|
+
const dstDir = join26(homedir11(), ".config", "opencode", "agents");
|
|
17307
|
+
if (!existsSync14(srcDir))
|
|
17290
17308
|
return;
|
|
17291
|
-
if (!
|
|
17309
|
+
if (!existsSync14(dstDir))
|
|
17292
17310
|
mkdirSync10(dstDir, { recursive: true });
|
|
17293
17311
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
17294
|
-
const src =
|
|
17295
|
-
const dst =
|
|
17296
|
-
if (
|
|
17312
|
+
const src = join26(srcDir, f);
|
|
17313
|
+
const dst = join26(dstDir, f);
|
|
17314
|
+
if (existsSync14(src)) {
|
|
17297
17315
|
writeFileSync9(dst, readFileSync29(src, "utf8"));
|
|
17298
17316
|
}
|
|
17299
17317
|
}
|
|
@@ -17301,9 +17319,9 @@ function copyOpenCodeAgents() {
|
|
|
17301
17319
|
function codexWrite(p) {
|
|
17302
17320
|
return (command, args) => {
|
|
17303
17321
|
const dir = dirname5(p);
|
|
17304
|
-
if (!
|
|
17322
|
+
if (!existsSync14(dir))
|
|
17305
17323
|
mkdirSync10(dir, { recursive: true });
|
|
17306
|
-
let existing =
|
|
17324
|
+
let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
|
|
17307
17325
|
if (!existing.trimEnd().endsWith(`
|
|
17308
17326
|
`))
|
|
17309
17327
|
existing += `
|
|
@@ -17320,9 +17338,9 @@ args = ${JSON.stringify(args)}
|
|
|
17320
17338
|
function hermesWrite(p) {
|
|
17321
17339
|
return (command, args) => {
|
|
17322
17340
|
const dir = dirname5(p);
|
|
17323
|
-
if (!
|
|
17341
|
+
if (!existsSync14(dir))
|
|
17324
17342
|
mkdirSync10(dir, { recursive: true });
|
|
17325
|
-
let existing =
|
|
17343
|
+
let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
|
|
17326
17344
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
17327
17345
|
if (!existing.trimEnd().endsWith(`
|
|
17328
17346
|
`))
|
|
@@ -17349,74 +17367,74 @@ mcp_servers:
|
|
|
17349
17367
|
function detectAgents() {
|
|
17350
17368
|
const home = homedir11();
|
|
17351
17369
|
const agents = [];
|
|
17352
|
-
const claudeJson =
|
|
17370
|
+
const claudeJson = join26(home, ".claude.json");
|
|
17353
17371
|
agents.push({
|
|
17354
17372
|
name: "Claude Code",
|
|
17355
17373
|
detectPath: claudeJson,
|
|
17356
|
-
installed: () =>
|
|
17374
|
+
installed: () => existsSync14(claudeJson) || which("claude"),
|
|
17357
17375
|
write: jsonMcpServersWrite(claudeJson)
|
|
17358
17376
|
});
|
|
17359
|
-
const claudeDesktop =
|
|
17377
|
+
const claudeDesktop = join26(home, ".config", "Claude", "claude_desktop_config.json");
|
|
17360
17378
|
agents.push({
|
|
17361
17379
|
name: "Claude Desktop",
|
|
17362
17380
|
detectPath: claudeDesktop,
|
|
17363
|
-
installed: () =>
|
|
17381
|
+
installed: () => existsSync14(claudeDesktop),
|
|
17364
17382
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
17365
17383
|
});
|
|
17366
|
-
const cursor =
|
|
17384
|
+
const cursor = join26(home, ".cursor", "mcp.json");
|
|
17367
17385
|
agents.push({
|
|
17368
17386
|
name: "Cursor",
|
|
17369
17387
|
detectPath: cursor,
|
|
17370
|
-
installed: () =>
|
|
17388
|
+
installed: () => existsSync14(join26(home, ".cursor")) || which("cursor"),
|
|
17371
17389
|
write: jsonMcpServersWrite(cursor)
|
|
17372
17390
|
});
|
|
17373
|
-
const opencode =
|
|
17391
|
+
const opencode = join26(home, ".config", "opencode", "opencode.jsonc");
|
|
17374
17392
|
agents.push({
|
|
17375
17393
|
name: "OpenCode",
|
|
17376
17394
|
detectPath: opencode,
|
|
17377
|
-
installed: () =>
|
|
17395
|
+
installed: () => existsSync14(join26(home, ".config", "opencode")) || which("opencode"),
|
|
17378
17396
|
write: opencodeWrite(opencode)
|
|
17379
17397
|
});
|
|
17380
|
-
const codex =
|
|
17398
|
+
const codex = join26(home, ".codex", "config.toml");
|
|
17381
17399
|
agents.push({
|
|
17382
17400
|
name: "Codex",
|
|
17383
17401
|
detectPath: codex,
|
|
17384
|
-
installed: () =>
|
|
17402
|
+
installed: () => existsSync14(join26(home, ".codex")) || which("codex"),
|
|
17385
17403
|
write: codexWrite(codex)
|
|
17386
17404
|
});
|
|
17387
|
-
const hermes =
|
|
17405
|
+
const hermes = join26(home, ".hermes", "config.yaml");
|
|
17388
17406
|
agents.push({
|
|
17389
17407
|
name: "Hermes",
|
|
17390
17408
|
detectPath: hermes,
|
|
17391
|
-
installed: () =>
|
|
17409
|
+
installed: () => existsSync14(join26(home, ".hermes")) || which("hermes"),
|
|
17392
17410
|
write: hermesWrite(hermes)
|
|
17393
17411
|
});
|
|
17394
|
-
const gemini =
|
|
17412
|
+
const gemini = join26(home, ".gemini", "settings.json");
|
|
17395
17413
|
agents.push({
|
|
17396
17414
|
name: "Gemini CLI",
|
|
17397
17415
|
detectPath: gemini,
|
|
17398
|
-
installed: () =>
|
|
17416
|
+
installed: () => existsSync14(join26(home, ".gemini")) || which("gemini"),
|
|
17399
17417
|
write: jsonMcpServersWrite(gemini)
|
|
17400
17418
|
});
|
|
17401
|
-
const windsurf =
|
|
17419
|
+
const windsurf = join26(home, ".codeium", "windsurf", "mcp_config.json");
|
|
17402
17420
|
agents.push({
|
|
17403
17421
|
name: "Windsurf",
|
|
17404
17422
|
detectPath: windsurf,
|
|
17405
|
-
installed: () =>
|
|
17423
|
+
installed: () => existsSync14(join26(home, ".codeium")) || which("windsurf"),
|
|
17406
17424
|
write: jsonMcpServersWrite(windsurf)
|
|
17407
17425
|
});
|
|
17408
|
-
const copilot =
|
|
17426
|
+
const copilot = join26(home, ".copilot", "mcp.json");
|
|
17409
17427
|
agents.push({
|
|
17410
17428
|
name: "Copilot",
|
|
17411
17429
|
detectPath: copilot,
|
|
17412
|
-
installed: () =>
|
|
17430
|
+
installed: () => existsSync14(join26(home, ".copilot")),
|
|
17413
17431
|
write: jsonMcpServersWrite(copilot)
|
|
17414
17432
|
});
|
|
17415
|
-
const cline =
|
|
17433
|
+
const cline = join26(home, ".cline", "mcp_settings.json");
|
|
17416
17434
|
agents.push({
|
|
17417
17435
|
name: "Cline",
|
|
17418
17436
|
detectPath: cline,
|
|
17419
|
-
installed: () =>
|
|
17437
|
+
installed: () => existsSync14(join26(home, ".cline")),
|
|
17420
17438
|
write: jsonMcpServersWrite(cline)
|
|
17421
17439
|
});
|
|
17422
17440
|
return agents;
|
|
@@ -17534,7 +17552,7 @@ var init_cli = __esm(() => {
|
|
|
17534
17552
|
init_release();
|
|
17535
17553
|
init_dependency();
|
|
17536
17554
|
init_mcp_status();
|
|
17537
|
-
ROOT10 =
|
|
17555
|
+
ROOT10 = join26(fileURLToPath15(new URL(".", import.meta.url)), "..");
|
|
17538
17556
|
});
|
|
17539
17557
|
|
|
17540
17558
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -57818,37 +57836,37 @@ function submissionGate(input) {
|
|
|
57818
57836
|
const questions = [
|
|
57819
57837
|
{
|
|
57820
57838
|
q: "Reproducible — a copy-paste-ready request or step list triggers it?",
|
|
57821
|
-
pass: input.reproducible
|
|
57839
|
+
pass: input.reproducible === true,
|
|
57822
57840
|
reason: "Without an exact reproduction the finding is a hypothesis, not a result."
|
|
57823
57841
|
},
|
|
57824
57842
|
{
|
|
57825
57843
|
q: "In scope — the vulnerable asset is in the engagement scope?",
|
|
57826
|
-
pass: input.in_scope
|
|
57844
|
+
pass: input.in_scope === true,
|
|
57827
57845
|
reason: "Out-of-scope assets must not be submitted."
|
|
57828
57846
|
},
|
|
57829
57847
|
{
|
|
57830
57848
|
q: "Real impact — maps to an accepted-impact class, not informational?",
|
|
57831
|
-
pass: input.real_impact
|
|
57849
|
+
pass: input.real_impact === true,
|
|
57832
57850
|
reason: "Informational-only findings dilute the validity ratio."
|
|
57833
57851
|
},
|
|
57834
57852
|
{
|
|
57835
57853
|
q: "No privileged assumption — works without unrealistic attacker prerequisites?",
|
|
57836
|
-
pass: input.no_privileged_assumption
|
|
57854
|
+
pass: input.no_privileged_assumption === true,
|
|
57837
57855
|
reason: "'Admin can do X' is a centralization risk, not a vulnerability."
|
|
57838
57856
|
},
|
|
57839
57857
|
{
|
|
57840
57858
|
q: "Verified live — confirmed with a marker + negative control, not just a static hint?",
|
|
57841
|
-
pass: input.verified_live
|
|
57859
|
+
pass: input.verified_live === true,
|
|
57842
57860
|
reason: "A static hit is a hypothesis until verified live (strike_verify / browser_validate)."
|
|
57843
57861
|
},
|
|
57844
57862
|
{
|
|
57845
57863
|
q: "Evidence attached AND redacted — secrets/PII stripped, integrity-tagged?",
|
|
57846
|
-
pass: input.evidence_redacted
|
|
57864
|
+
pass: input.evidence_redacted === true,
|
|
57847
57865
|
reason: "Evidence must carry SHA-256-tagged, redacted records — no leaked secrets or victim PII."
|
|
57848
57866
|
},
|
|
57849
57867
|
{
|
|
57850
57868
|
q: "Severity derived — CVSS/confidence computed, not guessed?",
|
|
57851
|
-
pass: input.severity_derived
|
|
57869
|
+
pass: input.severity_derived === true,
|
|
57852
57870
|
reason: "Severity is not confidence; derive it, don't assert it."
|
|
57853
57871
|
}
|
|
57854
57872
|
];
|
|
@@ -57862,6 +57880,185 @@ function submissionGate(input) {
|
|
|
57862
57880
|
};
|
|
57863
57881
|
}
|
|
57864
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
|
+
|
|
57865
58062
|
// src/severity.ts
|
|
57866
58063
|
var VRT_MAP = {
|
|
57867
58064
|
sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
|
|
@@ -57919,12 +58116,12 @@ function listSeverityMap() {
|
|
|
57919
58116
|
|
|
57920
58117
|
// src/datarefresh.ts
|
|
57921
58118
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
57922
|
-
import { join as
|
|
58119
|
+
import { join as join18, dirname as dirname3 } from "node:path";
|
|
57923
58120
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
57924
58121
|
var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
|
|
57925
58122
|
function loadJson2(rel) {
|
|
57926
58123
|
try {
|
|
57927
|
-
return JSON.parse(readFileSync18(
|
|
58124
|
+
return JSON.parse(readFileSync18(join18(__dirname3, "..", rel), "utf8"));
|
|
57928
58125
|
} catch {
|
|
57929
58126
|
return null;
|
|
57930
58127
|
}
|
|
@@ -57960,13 +58157,13 @@ init_variants();
|
|
|
57960
58157
|
|
|
57961
58158
|
// src/benchmark.ts
|
|
57962
58159
|
import { readFileSync as readFileSync20, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
57963
|
-
import { join as
|
|
58160
|
+
import { join as join20 } from "node:path";
|
|
57964
58161
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
57965
58162
|
import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
|
|
57966
58163
|
init_complex_bugs();
|
|
57967
|
-
var ROOT5 =
|
|
57968
|
-
var CORPUS =
|
|
57969
|
-
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");
|
|
57970
58167
|
function parseCorpusFile(path) {
|
|
57971
58168
|
let text;
|
|
57972
58169
|
try {
|
|
@@ -58026,7 +58223,7 @@ function loadDir(dir) {
|
|
|
58026
58223
|
for (const f of readdirSync5(dir)) {
|
|
58027
58224
|
if (!f.endsWith(".md"))
|
|
58028
58225
|
continue;
|
|
58029
|
-
const e = parseCorpusFile(
|
|
58226
|
+
const e = parseCorpusFile(join20(dir, f));
|
|
58030
58227
|
if (e)
|
|
58031
58228
|
entries.push(e);
|
|
58032
58229
|
}
|
|
@@ -58053,7 +58250,7 @@ function captureFalsePositive(input) {
|
|
|
58053
58250
|
input.code,
|
|
58054
58251
|
"```"
|
|
58055
58252
|
].filter((l) => l !== null);
|
|
58056
|
-
const path =
|
|
58253
|
+
const path = join20(CAPTURED_CORPUS_DIR, `${id}.md`);
|
|
58057
58254
|
writeFileSync6(path, lines.join(`
|
|
58058
58255
|
`) + `
|
|
58059
58256
|
`, "utf8");
|
|
@@ -58075,7 +58272,7 @@ function detect(entry) {
|
|
|
58075
58272
|
}
|
|
58076
58273
|
const lang = entry.language;
|
|
58077
58274
|
if (lang === "php") {
|
|
58078
|
-
const tmp =
|
|
58275
|
+
const tmp = join20(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
58079
58276
|
writeFileSync6(tmp, entry.code);
|
|
58080
58277
|
const r = analyzeDataFlow2(tmp);
|
|
58081
58278
|
rmSync3(tmp, { force: true });
|
|
@@ -58144,21 +58341,21 @@ function runBenchmark() {
|
|
|
58144
58341
|
}
|
|
58145
58342
|
|
|
58146
58343
|
// src/obligations.ts
|
|
58147
|
-
import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as
|
|
58148
|
-
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";
|
|
58149
58346
|
import { createHash as createHash6 } from "node:crypto";
|
|
58150
58347
|
import { homedir as homedir9 } from "node:os";
|
|
58151
|
-
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ??
|
|
58152
|
-
var OBLIGATION_PATH =
|
|
58348
|
+
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join21(homedir9(), ".blitzstrike");
|
|
58349
|
+
var OBLIGATION_PATH = join21(HOME_DIR4, "obligations.jsonl");
|
|
58153
58350
|
function ensureDir3() {
|
|
58154
|
-
if (!
|
|
58351
|
+
if (!existsSync11(HOME_DIR4))
|
|
58155
58352
|
mkdirSync8(HOME_DIR4, { recursive: true });
|
|
58156
58353
|
}
|
|
58157
58354
|
function shortId2(claim) {
|
|
58158
58355
|
return `obl-${createHash6("sha256").update(`${claim}\x00${Date.now()}`).digest("hex").slice(0, 12)}`;
|
|
58159
58356
|
}
|
|
58160
58357
|
function loadObligations() {
|
|
58161
|
-
if (!
|
|
58358
|
+
if (!existsSync11(OBLIGATION_PATH))
|
|
58162
58359
|
return [];
|
|
58163
58360
|
try {
|
|
58164
58361
|
const out = [];
|
|
@@ -59161,17 +59358,17 @@ function analyzeJwt(token, opts = {}) {
|
|
|
59161
59358
|
}
|
|
59162
59359
|
|
|
59163
59360
|
// src/enterprise-benchmark.ts
|
|
59164
|
-
import { existsSync as
|
|
59165
|
-
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";
|
|
59166
59363
|
init_complex_bugs();
|
|
59167
59364
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
59168
59365
|
var ENTERPRISE_FIXTURES = [
|
|
59169
|
-
{ id: "php-laravel", dir:
|
|
59170
|
-
{ id: "js-express", dir:
|
|
59171
|
-
{ id: "py-django", dir:
|
|
59172
|
-
{ id: "java-spring", dir:
|
|
59173
|
-
{ id: "wp-batch-route", dir:
|
|
59174
|
-
{ 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 }
|
|
59175
59372
|
];
|
|
59176
59373
|
function sourceFiles(root) {
|
|
59177
59374
|
const out = [];
|
|
@@ -59183,7 +59380,7 @@ function sourceFiles(root) {
|
|
|
59183
59380
|
return;
|
|
59184
59381
|
}
|
|
59185
59382
|
for (const e of entries) {
|
|
59186
|
-
const p =
|
|
59383
|
+
const p = join22(dir, e);
|
|
59187
59384
|
let st;
|
|
59188
59385
|
try {
|
|
59189
59386
|
st = statSync3(p);
|
|
@@ -59222,7 +59419,7 @@ function detectFixture(dir) {
|
|
|
59222
59419
|
function runEnterpriseBenchmark() {
|
|
59223
59420
|
const fixtures = [];
|
|
59224
59421
|
for (const fx of ENTERPRISE_FIXTURES) {
|
|
59225
|
-
if (!
|
|
59422
|
+
if (!existsSync12(fx.dir)) {
|
|
59226
59423
|
fixtures.push({ id: fx.id, available: false, detected: 0, expected: fx.expected_vulns, recall: 0 });
|
|
59227
59424
|
continue;
|
|
59228
59425
|
}
|
|
@@ -59399,7 +59596,7 @@ function createServer() {
|
|
|
59399
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).
|
|
59400
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.
|
|
59401
59598
|
|
|
59402
|
-
` + "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:
|
|
59403
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.
|
|
59404
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.
|
|
59405
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."
|
|
@@ -59843,6 +60040,32 @@ ${statusLine}` : statusLine },
|
|
|
59843
60040
|
const r = synonymsFor(term);
|
|
59844
60041
|
return { content: [{ type: "text", text: JSON.stringify({ ...r, pattern: patternLookup(r.canonical) }) }] };
|
|
59845
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
|
+
});
|
|
59846
60069
|
server.registerTool("fofa_search", {
|
|
59847
60070
|
title: "FOFA asset search",
|
|
59848
60071
|
description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
|
|
@@ -61187,14 +61410,14 @@ async function serve() {
|
|
|
61187
61410
|
|
|
61188
61411
|
// src/index.ts
|
|
61189
61412
|
init_cli();
|
|
61190
|
-
import { readFileSync as readFileSync30, existsSync as
|
|
61191
|
-
import { join as
|
|
61413
|
+
import { readFileSync as readFileSync30, existsSync as existsSync15 } from "node:fs";
|
|
61414
|
+
import { join as join27 } from "node:path";
|
|
61192
61415
|
import { fileURLToPath as fileURLToPath16 } from "node:url";
|
|
61193
|
-
var ROOT11 =
|
|
61416
|
+
var ROOT11 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
|
|
61194
61417
|
function readVersion() {
|
|
61195
61418
|
try {
|
|
61196
|
-
const p =
|
|
61197
|
-
if (
|
|
61419
|
+
const p = join27(ROOT11, "package.json");
|
|
61420
|
+
if (existsSync15(p))
|
|
61198
61421
|
return JSON.parse(readFileSync30(p, "utf8")).version ?? "1.0.0";
|
|
61199
61422
|
} catch {}
|
|
61200
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": {
|