blitzstrike 2.4.67 → 2.4.69
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 +39 -0
- package/dist/index.js +423 -103
- package/intelligence/cwe_map.json +321 -63
- package/intelligence/patterns.json +511 -100
- package/intelligence/variants.json +28 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16263,6 +16263,8 @@ function detectComplexBugs(code, file) {
|
|
|
16263
16263
|
detectGraphqlExposure(code, file, out);
|
|
16264
16264
|
detectOauthMisconfig(code, file, out);
|
|
16265
16265
|
detectGrpcReflection(code, file, out);
|
|
16266
|
+
detectDependencyConfusion(code, file, out);
|
|
16267
|
+
detectMlSupplyChain(code, file, out);
|
|
16266
16268
|
out.push(...detectWrongSanitizer(code, file));
|
|
16267
16269
|
out.push(...detectMissingAuthz(code, file));
|
|
16268
16270
|
const seen = new Set;
|
|
@@ -16523,14 +16525,26 @@ function detectHardcodedSecret(code, file, out) {
|
|
|
16523
16525
|
continue;
|
|
16524
16526
|
const ln = lineNo2(code, m.index);
|
|
16525
16527
|
flaggedLines.add(ln);
|
|
16528
|
+
let sev = "high";
|
|
16529
|
+
let cat = "Hardcoded secret (credential in source)";
|
|
16530
|
+
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.`;
|
|
16531
|
+
for (const fmt of SECRET_FORMATS) {
|
|
16532
|
+
fmt.re.lastIndex = 0;
|
|
16533
|
+
if (fmt.re.test(value)) {
|
|
16534
|
+
sev = fmt.severity;
|
|
16535
|
+
cat = `Hardcoded secret (${fmt.category}: ${fmt.name})`;
|
|
16536
|
+
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.`;
|
|
16537
|
+
break;
|
|
16538
|
+
}
|
|
16539
|
+
}
|
|
16526
16540
|
out.push({
|
|
16527
16541
|
file,
|
|
16528
16542
|
line: ln,
|
|
16529
16543
|
type: "hardcoded_secret",
|
|
16530
|
-
category:
|
|
16531
|
-
severity:
|
|
16544
|
+
category: cat,
|
|
16545
|
+
severity: sev,
|
|
16532
16546
|
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16533
|
-
detail
|
|
16547
|
+
detail
|
|
16534
16548
|
});
|
|
16535
16549
|
}
|
|
16536
16550
|
for (const fmt of SECRET_FORMATS) {
|
|
@@ -16667,6 +16681,78 @@ function detectGrpcReflection(code, file, out) {
|
|
|
16667
16681
|
});
|
|
16668
16682
|
}
|
|
16669
16683
|
}
|
|
16684
|
+
function detectDependencyConfusion(code, file, out) {
|
|
16685
|
+
const isManifest = /package\.json|requirements|pipfile|gemfile|go\.mod|pom\.xml|setup\.py|pyproject|\.npmrc|pip\.conf|dockerfile|\.ya?ml$/i.test(file);
|
|
16686
|
+
if (!isManifest)
|
|
16687
|
+
return;
|
|
16688
|
+
const ls = lines2(code);
|
|
16689
|
+
const idxRe = /--(?:extra-)?index-url\s+["']?(https?:\/\/[^"'<\s]+)["']?/gi;
|
|
16690
|
+
let m;
|
|
16691
|
+
while ((m = idxRe.exec(code)) !== null) {
|
|
16692
|
+
if (/pypi\.org|pythonhosted|npmjs\.org|rubygems\.org|golang\.org|crates\.io|maven\.apache\.org/i.test(m[1])) {
|
|
16693
|
+
const ln = lineNo2(code, m.index);
|
|
16694
|
+
out.push({
|
|
16695
|
+
file,
|
|
16696
|
+
line: ln,
|
|
16697
|
+
type: "dependency_confusion",
|
|
16698
|
+
category: "Dependency confusion — public registry in resolution path",
|
|
16699
|
+
severity: "high",
|
|
16700
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16701
|
+
detail: `A public registry (${m[1]}) is the resolution source — an internal package name that exists or can be squatted on the public registry resolves there instead of the private one, pulling attacker-controlled code (CWE-427). Pin a private registry and namespace internal packages uniquely.`
|
|
16702
|
+
});
|
|
16703
|
+
}
|
|
16704
|
+
}
|
|
16705
|
+
const hasPrivateRegistry = /--(?:index-url|registry)\s+["']?(?!https?:\/\/pypi\.org|https?:\/\/registry\.npmjs\.org)/i.test(code) || /(?:registry|index[-_]url)\s*[:=]\s*["']https?:\/\/(?!.*(?:pypi\.org|registry\.npmjs\.org))/i.test(code);
|
|
16706
|
+
if (!hasPrivateRegistry) {
|
|
16707
|
+
const scopedRe = /["'](@[a-z0-9][a-z0-9-]*\/[a-z0-9_.-]+)["']/g;
|
|
16708
|
+
while ((m = scopedRe.exec(code)) !== null) {
|
|
16709
|
+
const ln = lineNo2(code, m.index);
|
|
16710
|
+
out.push({
|
|
16711
|
+
file,
|
|
16712
|
+
line: ln,
|
|
16713
|
+
type: "dependency_confusion",
|
|
16714
|
+
category: "Dependency confusion — scoped package, no private registry",
|
|
16715
|
+
severity: "medium",
|
|
16716
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16717
|
+
detail: `The scoped package ${m[1]} is referenced but no private registry is pinned — if the @scope is not registered/reserved on the public registry, an attacker can publish ${m[1]} there and the build will pull malicious code (CWE-427). Verify the registry resolution and pin it.`
|
|
16718
|
+
});
|
|
16719
|
+
}
|
|
16720
|
+
}
|
|
16721
|
+
}
|
|
16722
|
+
function detectMlSupplyChain(code, file, out) {
|
|
16723
|
+
const ls = lines2(code);
|
|
16724
|
+
const clean = stripCommentsOnly(code);
|
|
16725
|
+
const sinks = [
|
|
16726
|
+
{ re: /torch\.load\s*\(/g, name: "torch.load", note: "uses pickle; pass weights_only=True", skipIfSafe: /weights_only\s*=\s*True/ },
|
|
16727
|
+
{ re: /\bjoblib\.load\s*\(/g, name: "joblib.load", note: "uses pickle (no safe flag)" },
|
|
16728
|
+
{ re: /(?:tf\.)?keras\.models\.load_model\s*\(/g, name: "keras load_model", note: "deserializes the model graph (pickle/h5)" },
|
|
16729
|
+
{ re: /\bnp\.load\s*\(/g, name: "np.load", note: "allow_pickle=True is RCE", requireFlag: /allow_pickle\s*=\s*True/i }
|
|
16730
|
+
];
|
|
16731
|
+
for (const s of sinks) {
|
|
16732
|
+
s.re.lastIndex = 0;
|
|
16733
|
+
let m;
|
|
16734
|
+
while ((m = s.re.exec(clean)) !== null) {
|
|
16735
|
+
const ln = lineNo2(code, m.index);
|
|
16736
|
+
const l = ls[ln - 1] ?? "";
|
|
16737
|
+
if (s.skipIfSafe && s.skipIfSafe.test(l))
|
|
16738
|
+
continue;
|
|
16739
|
+
if (s.requireFlag && !s.requireFlag.test(l))
|
|
16740
|
+
continue;
|
|
16741
|
+
const flagIsSignal = s.requireFlag != null;
|
|
16742
|
+
if (!flagIsSignal && !SOURCE_TOKENS3.test(l) && !/https?:\/\//.test(l) && !/download|request|upload|user|input|temp|tmp/i.test(l))
|
|
16743
|
+
continue;
|
|
16744
|
+
out.push({
|
|
16745
|
+
file,
|
|
16746
|
+
line: ln,
|
|
16747
|
+
type: "ml_supply_chain",
|
|
16748
|
+
category: "ML supply chain — untrusted model load",
|
|
16749
|
+
severity: "high",
|
|
16750
|
+
evidence: l.trim(),
|
|
16751
|
+
detail: `\`${s.name}\` loads a model from an untrusted source — ${s.note}. A poisoned model file executes arbitrary code on load (CWE-502). Pin hashes, load from a trusted artifact store, and use the safe flag where available.`
|
|
16752
|
+
});
|
|
16753
|
+
}
|
|
16754
|
+
}
|
|
16755
|
+
}
|
|
16670
16756
|
var SOURCE_TOKENS3, MAGIC_METHODS, SECRET_FIELD_RE, SECRET_PLACEHOLDER_RE, ENTROPY_EXCLUDE_VAR;
|
|
16671
16757
|
var init_complex_bugs = __esm(() => {
|
|
16672
16758
|
init_security_state();
|
|
@@ -16682,8 +16768,8 @@ var init_complex_bugs = __esm(() => {
|
|
|
16682
16768
|
// src/verify-harness.ts
|
|
16683
16769
|
function runVerificationHarness() {
|
|
16684
16770
|
const cases = CASES.map((c) => {
|
|
16685
|
-
const pos = detectComplexBugs(c.positive, "positive.php").filter((f) => f.type === c.detector);
|
|
16686
|
-
const neg = detectComplexBugs(c.negative, "negative.php").filter((f) => f.type === c.detector);
|
|
16771
|
+
const pos = detectComplexBugs(c.positive, c.file ?? "positive.php").filter((f) => f.type === c.detector);
|
|
16772
|
+
const neg = detectComplexBugs(c.negative, c.file ?? "negative.php").filter((f) => f.type === c.detector);
|
|
16687
16773
|
const positive_fired = pos.length > 0;
|
|
16688
16774
|
const negative_fired = neg.length > 0;
|
|
16689
16775
|
const tp = positive_fired;
|
|
@@ -16848,17 +16934,31 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
|
|
|
16848
16934
|
detector: "grpc_reflection",
|
|
16849
16935
|
positive: PHP(`$server->addService(enable_server_reflection());`),
|
|
16850
16936
|
negative: PHP(`$server->addService($my_service);`)
|
|
16937
|
+
},
|
|
16938
|
+
{
|
|
16939
|
+
id: "dependency-confusion",
|
|
16940
|
+
detector: "dependency_confusion",
|
|
16941
|
+
file: "requirements.txt",
|
|
16942
|
+
positive: `--extra-index-url https://pypi.org/simple\\nnumpy==1.24.0\\n`,
|
|
16943
|
+
negative: `--index-url https://pypi.internal.company.com/simple\\nnumpy==1.24.0\\n`
|
|
16944
|
+
},
|
|
16945
|
+
{
|
|
16946
|
+
id: "ml-supply-chain",
|
|
16947
|
+
detector: "ml_supply_chain",
|
|
16948
|
+
file: "train.py",
|
|
16949
|
+
positive: `import torch\\nmodel = torch.load(downloaded_model)\\n`,
|
|
16950
|
+
negative: `import torch\\nmodel = torch.load("model.pt", weights_only=True)\\n`
|
|
16851
16951
|
}
|
|
16852
16952
|
];
|
|
16853
16953
|
});
|
|
16854
16954
|
|
|
16855
16955
|
// src/variants.ts
|
|
16856
16956
|
import { readFileSync as readFileSync19 } from "node:fs";
|
|
16857
|
-
import { join as
|
|
16957
|
+
import { join as join19, dirname as dirname4 } from "node:path";
|
|
16858
16958
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
16859
16959
|
function loadVariants() {
|
|
16860
16960
|
try {
|
|
16861
|
-
const doc = JSON.parse(readFileSync19(
|
|
16961
|
+
const doc = JSON.parse(readFileSync19(join19(__dirname4, "..", "intelligence", "variants.json"), "utf8"));
|
|
16862
16962
|
return doc.variants ?? [];
|
|
16863
16963
|
} catch {
|
|
16864
16964
|
return [];
|
|
@@ -16867,7 +16967,7 @@ function loadVariants() {
|
|
|
16867
16967
|
function scanVariants() {
|
|
16868
16968
|
const variants = loadVariants().filter((v) => !CROSS_FILE_ONLY.has(v.class));
|
|
16869
16969
|
const results = variants.map((v, i) => {
|
|
16870
|
-
const findings = detectComplexBugs(v.code, `variant-${i}.php`).filter((f) => f.type === v.class);
|
|
16970
|
+
const findings = detectComplexBugs(v.code, v.file ?? `variant-${i}.php`).filter((f) => f.type === v.class);
|
|
16871
16971
|
return { id: `variant-${i}`, class: v.class, fired: findings.length > 0, note: v.note };
|
|
16872
16972
|
});
|
|
16873
16973
|
const per_class = {};
|
|
@@ -16918,7 +17018,7 @@ var init_variants = __esm(() => {
|
|
|
16918
17018
|
|
|
16919
17019
|
// src/sync.ts
|
|
16920
17020
|
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
|
|
17021
|
+
import { join as join23 } from "node:path";
|
|
16922
17022
|
import { homedir as homedir10 } from "node:os";
|
|
16923
17023
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
16924
17024
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -16935,7 +17035,7 @@ function countFiles(dir) {
|
|
|
16935
17035
|
const walk = (d) => {
|
|
16936
17036
|
for (const e of readdirSync7(d, { withFileTypes: true })) {
|
|
16937
17037
|
if (e.isDirectory())
|
|
16938
|
-
walk(
|
|
17038
|
+
walk(join23(d, e.name));
|
|
16939
17039
|
else
|
|
16940
17040
|
n++;
|
|
16941
17041
|
}
|
|
@@ -16949,8 +17049,8 @@ function countFiles(dir) {
|
|
|
16949
17049
|
function syncData() {
|
|
16950
17050
|
if (!dirExists(DATA_ROOT2))
|
|
16951
17051
|
mkdirSync9(DATA_ROOT2, { recursive: true });
|
|
16952
|
-
const payloadsPath =
|
|
16953
|
-
const templatesPath =
|
|
17052
|
+
const payloadsPath = join23(DATA_ROOT2, "payloads");
|
|
17053
|
+
const templatesPath = join23(DATA_ROOT2, "templates");
|
|
16954
17054
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
16955
17055
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
16956
17056
|
if (hasPayloads && hasTemplates) {
|
|
@@ -16960,8 +17060,8 @@ function syncData() {
|
|
|
16960
17060
|
data_root: DATA_ROOT2
|
|
16961
17061
|
};
|
|
16962
17062
|
}
|
|
16963
|
-
const localPayloads =
|
|
16964
|
-
const localTemplates =
|
|
17063
|
+
const localPayloads = join23(ROOT7, "payloads");
|
|
17064
|
+
const localTemplates = join23(ROOT7, "templates");
|
|
16965
17065
|
let payloadsSynced = false;
|
|
16966
17066
|
let templatesSynced = false;
|
|
16967
17067
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -16974,7 +17074,7 @@ function syncData() {
|
|
|
16974
17074
|
}
|
|
16975
17075
|
const gitOk = spawnSync4("git", ["--version"]).status === 0;
|
|
16976
17076
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
16977
|
-
const tmp =
|
|
17077
|
+
const tmp = join23(DATA_ROOT2, ".tmp-clone");
|
|
16978
17078
|
if (dirExists(tmp))
|
|
16979
17079
|
rmSync4(tmp, { recursive: true, force: true });
|
|
16980
17080
|
const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -16982,11 +17082,11 @@ function syncData() {
|
|
|
16982
17082
|
const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
16983
17083
|
if (sparse.status === 0) {
|
|
16984
17084
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
16985
|
-
copyDir(
|
|
17085
|
+
copyDir(join23(tmp, "payloads"), payloadsPath);
|
|
16986
17086
|
payloadsSynced = true;
|
|
16987
17087
|
}
|
|
16988
17088
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
16989
|
-
copyDir(
|
|
17089
|
+
copyDir(join23(tmp, "templates"), templatesPath);
|
|
16990
17090
|
templatesSynced = true;
|
|
16991
17091
|
}
|
|
16992
17092
|
}
|
|
@@ -17008,8 +17108,8 @@ function copyDir(src, dst) {
|
|
|
17008
17108
|
function manualCopy(src, dst) {
|
|
17009
17109
|
mkdirSync9(dst, { recursive: true });
|
|
17010
17110
|
for (const e of readdirSync7(src, { withFileTypes: true })) {
|
|
17011
|
-
const s =
|
|
17012
|
-
const d =
|
|
17111
|
+
const s = join23(src, e.name);
|
|
17112
|
+
const d = join23(dst, e.name);
|
|
17013
17113
|
if (e.isDirectory())
|
|
17014
17114
|
manualCopy(s, d);
|
|
17015
17115
|
else
|
|
@@ -17018,8 +17118,8 @@ function manualCopy(src, dst) {
|
|
|
17018
17118
|
}
|
|
17019
17119
|
var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
17020
17120
|
var init_sync = __esm(() => {
|
|
17021
|
-
ROOT7 =
|
|
17022
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
17121
|
+
ROOT7 = join23(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
17122
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join23(homedir10(), ".blitzstrike", "data");
|
|
17023
17123
|
});
|
|
17024
17124
|
|
|
17025
17125
|
// src/update.ts
|
|
@@ -17064,15 +17164,15 @@ var init_update = __esm(() => {
|
|
|
17064
17164
|
});
|
|
17065
17165
|
|
|
17066
17166
|
// src/release.ts
|
|
17067
|
-
import { readFileSync as readFileSync27, existsSync as
|
|
17068
|
-
import { join as
|
|
17167
|
+
import { readFileSync as readFileSync27, existsSync as existsSync13 } from "node:fs";
|
|
17168
|
+
import { join as join24 } from "node:path";
|
|
17069
17169
|
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
17070
17170
|
import { execSync as execSync2 } from "node:child_process";
|
|
17071
17171
|
function validateRelease() {
|
|
17072
17172
|
const checks = [];
|
|
17073
17173
|
checks.push({ name: "package.json version", ok: true, detail: VERSION });
|
|
17074
17174
|
try {
|
|
17075
|
-
const changelog = readFileSync27(
|
|
17175
|
+
const changelog = readFileSync27(join24(ROOT8, "CHANGELOG.md"), "utf8");
|
|
17076
17176
|
const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
|
|
17077
17177
|
const changelogVersion = m?.[1] ?? null;
|
|
17078
17178
|
checks.push({
|
|
@@ -17104,22 +17204,22 @@ function validateRelease() {
|
|
|
17104
17204
|
return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
|
|
17105
17205
|
}
|
|
17106
17206
|
function lockfilePresent() {
|
|
17107
|
-
return
|
|
17207
|
+
return existsSync13(join24(ROOT8, "package-lock.json")) || existsSync13(join24(ROOT8, "bun.lock")) || existsSync13(join24(ROOT8, "bun.lockb"));
|
|
17108
17208
|
}
|
|
17109
17209
|
var ROOT8;
|
|
17110
17210
|
var init_release = __esm(() => {
|
|
17111
17211
|
init_version();
|
|
17112
|
-
ROOT8 =
|
|
17212
|
+
ROOT8 = join24(fileURLToPath13(new URL(".", import.meta.url)), "..");
|
|
17113
17213
|
});
|
|
17114
17214
|
|
|
17115
17215
|
// src/dependency.ts
|
|
17116
17216
|
import { readFileSync as readFileSync28 } from "node:fs";
|
|
17117
|
-
import { join as
|
|
17217
|
+
import { join as join25 } from "node:path";
|
|
17118
17218
|
import { fileURLToPath as fileURLToPath14 } from "node:url";
|
|
17119
17219
|
function checkDependencies() {
|
|
17120
17220
|
let pkg = {};
|
|
17121
17221
|
try {
|
|
17122
|
-
pkg = JSON.parse(readFileSync28(
|
|
17222
|
+
pkg = JSON.parse(readFileSync28(join25(ROOT9, "package.json"), "utf8"));
|
|
17123
17223
|
} catch {
|
|
17124
17224
|
return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
|
|
17125
17225
|
}
|
|
@@ -17142,12 +17242,12 @@ function checkDependencies() {
|
|
|
17142
17242
|
}
|
|
17143
17243
|
var ROOT9;
|
|
17144
17244
|
var init_dependency = __esm(() => {
|
|
17145
|
-
ROOT9 =
|
|
17245
|
+
ROOT9 = join25(fileURLToPath14(new URL(".", import.meta.url)), "..");
|
|
17146
17246
|
});
|
|
17147
17247
|
|
|
17148
17248
|
// src/cli.ts
|
|
17149
|
-
import { readFileSync as readFileSync29, existsSync as
|
|
17150
|
-
import { join as
|
|
17249
|
+
import { readFileSync as readFileSync29, existsSync as existsSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
|
|
17250
|
+
import { join as join26, dirname as dirname5 } from "node:path";
|
|
17151
17251
|
import { homedir as homedir11 } from "node:os";
|
|
17152
17252
|
import { fileURLToPath as fileURLToPath15 } from "node:url";
|
|
17153
17253
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
@@ -17192,7 +17292,7 @@ async function runDoctor() {
|
|
|
17192
17292
|
detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
|
|
17193
17293
|
fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
|
|
17194
17294
|
});
|
|
17195
|
-
const dataOk =
|
|
17295
|
+
const dataOk = existsSync14(join26(ROOT10, "chains.json")) && existsSync14(join26(ROOT10, "tools-catalog.json"));
|
|
17196
17296
|
issues.push({
|
|
17197
17297
|
name: "Data layers (chains + tools-catalog)",
|
|
17198
17298
|
status: dataOk ? "ok" : "fail",
|
|
@@ -17243,15 +17343,15 @@ BlitzStrike — ${verdict}`);
|
|
|
17243
17343
|
function resolveCommand() {
|
|
17244
17344
|
if (which("blitzstrike"))
|
|
17245
17345
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
17246
|
-
const src =
|
|
17247
|
-
if (which("bun") &&
|
|
17346
|
+
const src = join26(ROOT10, "src", "index.ts");
|
|
17347
|
+
if (which("bun") && existsSync14(src))
|
|
17248
17348
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
17249
17349
|
if (which("npx"))
|
|
17250
17350
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
17251
17351
|
if (which("bunx"))
|
|
17252
17352
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
17253
|
-
const dist =
|
|
17254
|
-
if (
|
|
17353
|
+
const dist = join26(ROOT10, "dist", "index.js");
|
|
17354
|
+
if (existsSync14(dist))
|
|
17255
17355
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
17256
17356
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
17257
17357
|
}
|
|
@@ -17265,7 +17365,7 @@ function readJson(p) {
|
|
|
17265
17365
|
function jsonMcpServersWrite(p) {
|
|
17266
17366
|
return (command, args) => {
|
|
17267
17367
|
const dir = dirname5(p);
|
|
17268
|
-
if (!
|
|
17368
|
+
if (!existsSync14(dir))
|
|
17269
17369
|
mkdirSync10(dir, { recursive: true });
|
|
17270
17370
|
const existing = readJson(p) ?? {};
|
|
17271
17371
|
existing.mcpServers = {
|
|
@@ -17278,7 +17378,7 @@ function jsonMcpServersWrite(p) {
|
|
|
17278
17378
|
function opencodeWrite(p) {
|
|
17279
17379
|
return (command, args) => {
|
|
17280
17380
|
const dir = dirname5(p);
|
|
17281
|
-
if (!
|
|
17381
|
+
if (!existsSync14(dir))
|
|
17282
17382
|
mkdirSync10(dir, { recursive: true });
|
|
17283
17383
|
const existing = readJson(p) ?? {};
|
|
17284
17384
|
existing.mcp = {
|
|
@@ -17290,16 +17390,16 @@ function opencodeWrite(p) {
|
|
|
17290
17390
|
};
|
|
17291
17391
|
}
|
|
17292
17392
|
function copyOpenCodeAgents() {
|
|
17293
|
-
const srcDir =
|
|
17294
|
-
const dstDir =
|
|
17295
|
-
if (!
|
|
17393
|
+
const srcDir = join26(ROOT10, "opencode-agents");
|
|
17394
|
+
const dstDir = join26(homedir11(), ".config", "opencode", "agents");
|
|
17395
|
+
if (!existsSync14(srcDir))
|
|
17296
17396
|
return;
|
|
17297
|
-
if (!
|
|
17397
|
+
if (!existsSync14(dstDir))
|
|
17298
17398
|
mkdirSync10(dstDir, { recursive: true });
|
|
17299
17399
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
17300
|
-
const src =
|
|
17301
|
-
const dst =
|
|
17302
|
-
if (
|
|
17400
|
+
const src = join26(srcDir, f);
|
|
17401
|
+
const dst = join26(dstDir, f);
|
|
17402
|
+
if (existsSync14(src)) {
|
|
17303
17403
|
writeFileSync9(dst, readFileSync29(src, "utf8"));
|
|
17304
17404
|
}
|
|
17305
17405
|
}
|
|
@@ -17307,9 +17407,9 @@ function copyOpenCodeAgents() {
|
|
|
17307
17407
|
function codexWrite(p) {
|
|
17308
17408
|
return (command, args) => {
|
|
17309
17409
|
const dir = dirname5(p);
|
|
17310
|
-
if (!
|
|
17410
|
+
if (!existsSync14(dir))
|
|
17311
17411
|
mkdirSync10(dir, { recursive: true });
|
|
17312
|
-
let existing =
|
|
17412
|
+
let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
|
|
17313
17413
|
if (!existing.trimEnd().endsWith(`
|
|
17314
17414
|
`))
|
|
17315
17415
|
existing += `
|
|
@@ -17326,9 +17426,9 @@ args = ${JSON.stringify(args)}
|
|
|
17326
17426
|
function hermesWrite(p) {
|
|
17327
17427
|
return (command, args) => {
|
|
17328
17428
|
const dir = dirname5(p);
|
|
17329
|
-
if (!
|
|
17429
|
+
if (!existsSync14(dir))
|
|
17330
17430
|
mkdirSync10(dir, { recursive: true });
|
|
17331
|
-
let existing =
|
|
17431
|
+
let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
|
|
17332
17432
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
17333
17433
|
if (!existing.trimEnd().endsWith(`
|
|
17334
17434
|
`))
|
|
@@ -17355,74 +17455,74 @@ mcp_servers:
|
|
|
17355
17455
|
function detectAgents() {
|
|
17356
17456
|
const home = homedir11();
|
|
17357
17457
|
const agents = [];
|
|
17358
|
-
const claudeJson =
|
|
17458
|
+
const claudeJson = join26(home, ".claude.json");
|
|
17359
17459
|
agents.push({
|
|
17360
17460
|
name: "Claude Code",
|
|
17361
17461
|
detectPath: claudeJson,
|
|
17362
|
-
installed: () =>
|
|
17462
|
+
installed: () => existsSync14(claudeJson) || which("claude"),
|
|
17363
17463
|
write: jsonMcpServersWrite(claudeJson)
|
|
17364
17464
|
});
|
|
17365
|
-
const claudeDesktop =
|
|
17465
|
+
const claudeDesktop = join26(home, ".config", "Claude", "claude_desktop_config.json");
|
|
17366
17466
|
agents.push({
|
|
17367
17467
|
name: "Claude Desktop",
|
|
17368
17468
|
detectPath: claudeDesktop,
|
|
17369
|
-
installed: () =>
|
|
17469
|
+
installed: () => existsSync14(claudeDesktop),
|
|
17370
17470
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
17371
17471
|
});
|
|
17372
|
-
const cursor =
|
|
17472
|
+
const cursor = join26(home, ".cursor", "mcp.json");
|
|
17373
17473
|
agents.push({
|
|
17374
17474
|
name: "Cursor",
|
|
17375
17475
|
detectPath: cursor,
|
|
17376
|
-
installed: () =>
|
|
17476
|
+
installed: () => existsSync14(join26(home, ".cursor")) || which("cursor"),
|
|
17377
17477
|
write: jsonMcpServersWrite(cursor)
|
|
17378
17478
|
});
|
|
17379
|
-
const opencode =
|
|
17479
|
+
const opencode = join26(home, ".config", "opencode", "opencode.jsonc");
|
|
17380
17480
|
agents.push({
|
|
17381
17481
|
name: "OpenCode",
|
|
17382
17482
|
detectPath: opencode,
|
|
17383
|
-
installed: () =>
|
|
17483
|
+
installed: () => existsSync14(join26(home, ".config", "opencode")) || which("opencode"),
|
|
17384
17484
|
write: opencodeWrite(opencode)
|
|
17385
17485
|
});
|
|
17386
|
-
const codex =
|
|
17486
|
+
const codex = join26(home, ".codex", "config.toml");
|
|
17387
17487
|
agents.push({
|
|
17388
17488
|
name: "Codex",
|
|
17389
17489
|
detectPath: codex,
|
|
17390
|
-
installed: () =>
|
|
17490
|
+
installed: () => existsSync14(join26(home, ".codex")) || which("codex"),
|
|
17391
17491
|
write: codexWrite(codex)
|
|
17392
17492
|
});
|
|
17393
|
-
const hermes =
|
|
17493
|
+
const hermes = join26(home, ".hermes", "config.yaml");
|
|
17394
17494
|
agents.push({
|
|
17395
17495
|
name: "Hermes",
|
|
17396
17496
|
detectPath: hermes,
|
|
17397
|
-
installed: () =>
|
|
17497
|
+
installed: () => existsSync14(join26(home, ".hermes")) || which("hermes"),
|
|
17398
17498
|
write: hermesWrite(hermes)
|
|
17399
17499
|
});
|
|
17400
|
-
const gemini =
|
|
17500
|
+
const gemini = join26(home, ".gemini", "settings.json");
|
|
17401
17501
|
agents.push({
|
|
17402
17502
|
name: "Gemini CLI",
|
|
17403
17503
|
detectPath: gemini,
|
|
17404
|
-
installed: () =>
|
|
17504
|
+
installed: () => existsSync14(join26(home, ".gemini")) || which("gemini"),
|
|
17405
17505
|
write: jsonMcpServersWrite(gemini)
|
|
17406
17506
|
});
|
|
17407
|
-
const windsurf =
|
|
17507
|
+
const windsurf = join26(home, ".codeium", "windsurf", "mcp_config.json");
|
|
17408
17508
|
agents.push({
|
|
17409
17509
|
name: "Windsurf",
|
|
17410
17510
|
detectPath: windsurf,
|
|
17411
|
-
installed: () =>
|
|
17511
|
+
installed: () => existsSync14(join26(home, ".codeium")) || which("windsurf"),
|
|
17412
17512
|
write: jsonMcpServersWrite(windsurf)
|
|
17413
17513
|
});
|
|
17414
|
-
const copilot =
|
|
17514
|
+
const copilot = join26(home, ".copilot", "mcp.json");
|
|
17415
17515
|
agents.push({
|
|
17416
17516
|
name: "Copilot",
|
|
17417
17517
|
detectPath: copilot,
|
|
17418
|
-
installed: () =>
|
|
17518
|
+
installed: () => existsSync14(join26(home, ".copilot")),
|
|
17419
17519
|
write: jsonMcpServersWrite(copilot)
|
|
17420
17520
|
});
|
|
17421
|
-
const cline =
|
|
17521
|
+
const cline = join26(home, ".cline", "mcp_settings.json");
|
|
17422
17522
|
agents.push({
|
|
17423
17523
|
name: "Cline",
|
|
17424
17524
|
detectPath: cline,
|
|
17425
|
-
installed: () =>
|
|
17525
|
+
installed: () => existsSync14(join26(home, ".cline")),
|
|
17426
17526
|
write: jsonMcpServersWrite(cline)
|
|
17427
17527
|
});
|
|
17428
17528
|
return agents;
|
|
@@ -17540,7 +17640,7 @@ var init_cli = __esm(() => {
|
|
|
17540
17640
|
init_release();
|
|
17541
17641
|
init_dependency();
|
|
17542
17642
|
init_mcp_status();
|
|
17543
|
-
ROOT10 =
|
|
17643
|
+
ROOT10 = join26(fileURLToPath15(new URL(".", import.meta.url)), "..");
|
|
17544
17644
|
});
|
|
17545
17645
|
|
|
17546
17646
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -57779,7 +57879,16 @@ var ALIASES = {
|
|
|
57779
57879
|
"header injection": "crlf_injection",
|
|
57780
57880
|
"http response splitting": "crlf_injection",
|
|
57781
57881
|
"wrong sanitizer": "wrong_sanitizer",
|
|
57782
|
-
"wrong context sanitization": "wrong_sanitizer"
|
|
57882
|
+
"wrong context sanitization": "wrong_sanitizer",
|
|
57883
|
+
"dependency confusion": "dependency_confusion",
|
|
57884
|
+
"dep confusion": "dependency_confusion",
|
|
57885
|
+
"supply chain": "dependency_confusion",
|
|
57886
|
+
"supply chain attack": "dependency_confusion",
|
|
57887
|
+
"ml supply chain": "ml_supply_chain",
|
|
57888
|
+
"ai supply chain": "ml_supply_chain",
|
|
57889
|
+
"model poisoning": "ml_supply_chain",
|
|
57890
|
+
"ml security": "ml_supply_chain",
|
|
57891
|
+
"ai ml security": "ml_supply_chain"
|
|
57783
57892
|
};
|
|
57784
57893
|
function normalize(term) {
|
|
57785
57894
|
return term.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -57868,6 +57977,185 @@ function submissionGate(input) {
|
|
|
57868
57977
|
};
|
|
57869
57978
|
}
|
|
57870
57979
|
|
|
57980
|
+
// src/git-history.ts
|
|
57981
|
+
init_complex_bugs();
|
|
57982
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
57983
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
57984
|
+
import { join as join17 } from "node:path";
|
|
57985
|
+
function parseDeletions(output) {
|
|
57986
|
+
const out = [];
|
|
57987
|
+
let commit = "";
|
|
57988
|
+
let msg = "";
|
|
57989
|
+
for (const line of output.split(`
|
|
57990
|
+
`)) {
|
|
57991
|
+
if (line.startsWith("__COMMIT__ ")) {
|
|
57992
|
+
const rest = line.slice("__COMMIT__ ".length);
|
|
57993
|
+
const sp = rest.indexOf(" ");
|
|
57994
|
+
commit = sp === -1 ? rest : rest.slice(0, sp);
|
|
57995
|
+
msg = sp === -1 ? "" : rest.slice(sp + 1);
|
|
57996
|
+
} else if (line.startsWith("D\t") || line.startsWith("D ")) {
|
|
57997
|
+
const path = line.slice(2).trim();
|
|
57998
|
+
if (path && commit)
|
|
57999
|
+
out.push({ commit, commit_msg: msg, path });
|
|
58000
|
+
}
|
|
58001
|
+
}
|
|
58002
|
+
return out;
|
|
58003
|
+
}
|
|
58004
|
+
function blobBeforeDeletion(root, commit, path) {
|
|
58005
|
+
return execFileSync4("git", ["-C", root, "show", `${commit}^:${path}`], {
|
|
58006
|
+
encoding: "utf8",
|
|
58007
|
+
maxBuffer: 10 * 1024 * 1024
|
|
58008
|
+
});
|
|
58009
|
+
}
|
|
58010
|
+
function scanGitHistory(root, opts) {
|
|
58011
|
+
const limit = opts?.limit ?? 30;
|
|
58012
|
+
const findings = [];
|
|
58013
|
+
if (!existsSync10(join17(root, ".git"))) {
|
|
58014
|
+
return { root, is_git_repo: false, total: 0, findings: [], error: "not a git repository (no .git dir)" };
|
|
58015
|
+
}
|
|
58016
|
+
if (opts?.secretPattern) {
|
|
58017
|
+
try {
|
|
58018
|
+
const pick = execFileSync4("git", ["-C", root, "log", "-S", opts.secretPattern, "--all", "--format=__COMMIT__ %H %s"], { encoding: "utf8", maxBuffer: 5 * 1024 * 1024 });
|
|
58019
|
+
const commits = pick.split(`
|
|
58020
|
+
`).filter(Boolean).map((l) => {
|
|
58021
|
+
const rest = l.startsWith("__COMMIT__ ") ? l.slice("__COMMIT__ ".length) : l;
|
|
58022
|
+
const sp = rest.indexOf(" ");
|
|
58023
|
+
return { hash: sp === -1 ? rest : rest.slice(0, sp), msg: sp === -1 ? "" : rest.slice(sp + 1) };
|
|
58024
|
+
});
|
|
58025
|
+
return {
|
|
58026
|
+
root,
|
|
58027
|
+
is_git_repo: true,
|
|
58028
|
+
mode: "pickaxe",
|
|
58029
|
+
pattern: opts.secretPattern,
|
|
58030
|
+
total: commits.length,
|
|
58031
|
+
commits,
|
|
58032
|
+
hint: "Commits where the pattern was added or removed — inspect each to recover the secret value."
|
|
58033
|
+
};
|
|
58034
|
+
} catch (e) {
|
|
58035
|
+
return { root, is_git_repo: true, mode: "pickaxe", pattern: opts.secretPattern, total: 0, error: String(e) };
|
|
58036
|
+
}
|
|
58037
|
+
}
|
|
58038
|
+
try {
|
|
58039
|
+
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 });
|
|
58040
|
+
const deletions = parseDeletions(log);
|
|
58041
|
+
const seen = new Set;
|
|
58042
|
+
let checked = 0;
|
|
58043
|
+
for (const d of deletions) {
|
|
58044
|
+
if (findings.length >= limit)
|
|
58045
|
+
break;
|
|
58046
|
+
const key = `${d.path}`;
|
|
58047
|
+
if (seen.has(key))
|
|
58048
|
+
continue;
|
|
58049
|
+
seen.add(key);
|
|
58050
|
+
try {
|
|
58051
|
+
const blob = blobBeforeDeletion(root, d.commit, d.path);
|
|
58052
|
+
checked++;
|
|
58053
|
+
const hits = detectComplexBugs(blob, d.path).filter((f) => f.type === "hardcoded_secret");
|
|
58054
|
+
for (const h of hits) {
|
|
58055
|
+
findings.push({
|
|
58056
|
+
path: d.path,
|
|
58057
|
+
commit: d.commit,
|
|
58058
|
+
commit_msg: d.commit_msg,
|
|
58059
|
+
severity: h.severity,
|
|
58060
|
+
category: h.category,
|
|
58061
|
+
evidence: h.evidence,
|
|
58062
|
+
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.`
|
|
58063
|
+
});
|
|
58064
|
+
}
|
|
58065
|
+
} catch {}
|
|
58066
|
+
}
|
|
58067
|
+
return {
|
|
58068
|
+
root,
|
|
58069
|
+
is_git_repo: true,
|
|
58070
|
+
mode: "deleted_files",
|
|
58071
|
+
deletion_commits: deletions.length,
|
|
58072
|
+
blobs_checked: checked,
|
|
58073
|
+
total: findings.length,
|
|
58074
|
+
findings,
|
|
58075
|
+
hint: "Secrets in deleted files remain in .git history. Rotate any recovered credential and rewrite/scrub history."
|
|
58076
|
+
};
|
|
58077
|
+
} catch (e) {
|
|
58078
|
+
return { root, is_git_repo: true, mode: "deleted_files", total: 0, findings: [], error: String(e) };
|
|
58079
|
+
}
|
|
58080
|
+
}
|
|
58081
|
+
|
|
58082
|
+
// src/key-validation.ts
|
|
58083
|
+
function parseValueFromBlob(text, varName) {
|
|
58084
|
+
const re = new RegExp(`${varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*[=:]\\s*["']?([^"'\\n]+)`);
|
|
58085
|
+
const m = text.match(re);
|
|
58086
|
+
if (!m)
|
|
58087
|
+
throw new Error(`variable ${varName} not found in blob`);
|
|
58088
|
+
return m[1].trim();
|
|
58089
|
+
}
|
|
58090
|
+
async function httpGet3(url, headers) {
|
|
58091
|
+
try {
|
|
58092
|
+
const res = await fetch(url, { headers, redirect: "follow" });
|
|
58093
|
+
return { status: res.status, body: await res.text() };
|
|
58094
|
+
} catch (e) {
|
|
58095
|
+
return { status: null, body: String(e) };
|
|
58096
|
+
}
|
|
58097
|
+
}
|
|
58098
|
+
async function validateLeakedKey(input) {
|
|
58099
|
+
const endpoints = input.endpoints ?? ["/items"];
|
|
58100
|
+
const scheme = input.scheme ?? "Bearer {value}";
|
|
58101
|
+
const header = input.header ?? "Authorization";
|
|
58102
|
+
let key = input.key ?? "";
|
|
58103
|
+
if (!key && input.blobUrl) {
|
|
58104
|
+
const blobRes = await httpGet3(input.blobUrl, { "User-Agent": "Mozilla/5.0 (recon-agent)" });
|
|
58105
|
+
if (blobRes.status !== 200) {
|
|
58106
|
+
return { ok: false, error: `blob fetch failed (HTTP ${blobRes.status}) — leak gone, privatized, or wrong sha`, step: "blob_fetch" };
|
|
58107
|
+
}
|
|
58108
|
+
try {
|
|
58109
|
+
key = parseValueFromBlob(blobRes.body, input.varName ?? "SECRET_KEY");
|
|
58110
|
+
} catch (e) {
|
|
58111
|
+
return { ok: false, error: String(e), step: "parse" };
|
|
58112
|
+
}
|
|
58113
|
+
}
|
|
58114
|
+
if (!key) {
|
|
58115
|
+
return { ok: false, error: "no key provided and no blobUrl to recover one from", step: "key" };
|
|
58116
|
+
}
|
|
58117
|
+
const authHeader = { [header]: scheme.replace("{value}", key) };
|
|
58118
|
+
const results = [];
|
|
58119
|
+
let proven = false;
|
|
58120
|
+
for (const ep of endpoints) {
|
|
58121
|
+
const url = input.apiBase.replace(/\/$/, "") + (ep.startsWith("/") ? ep : "/" + ep);
|
|
58122
|
+
const r0 = await httpGet3(url, { "User-Agent": "Mozilla/5.0 (recon-agent)" });
|
|
58123
|
+
const r1 = await httpGet3(url, { "User-Agent": "Mozilla/5.0 (recon-agent)", ...authHeader });
|
|
58124
|
+
let verdict = "-";
|
|
58125
|
+
if (r0.status === null || r1.status === null)
|
|
58126
|
+
verdict = "ERROR";
|
|
58127
|
+
else if ((r0.status === 401 || r0.status === 403) && r1.status === 200) {
|
|
58128
|
+
verdict = "BYPASS";
|
|
58129
|
+
proven = true;
|
|
58130
|
+
}
|
|
58131
|
+
let records = null;
|
|
58132
|
+
if (r1.status === 200) {
|
|
58133
|
+
try {
|
|
58134
|
+
records = (JSON.parse(r1.body).data ?? []).length;
|
|
58135
|
+
} catch {
|
|
58136
|
+
records = null;
|
|
58137
|
+
}
|
|
58138
|
+
}
|
|
58139
|
+
results.push({
|
|
58140
|
+
endpoint: ep,
|
|
58141
|
+
no_auth: r0.status,
|
|
58142
|
+
with_key: r1.status,
|
|
58143
|
+
records,
|
|
58144
|
+
verdict,
|
|
58145
|
+
error: verdict === "ERROR" ? r0.status === null ? r0.body : r1.body : undefined
|
|
58146
|
+
});
|
|
58147
|
+
}
|
|
58148
|
+
return {
|
|
58149
|
+
ok: true,
|
|
58150
|
+
proven,
|
|
58151
|
+
key_len: key.length,
|
|
58152
|
+
header,
|
|
58153
|
+
scheme,
|
|
58154
|
+
results,
|
|
58155
|
+
verdict: proven ? "AUTH BYPASS PROVEN — leaked credential valid on live system (read-only)" : "not proven — rotated? different header scheme? check manually"
|
|
58156
|
+
};
|
|
58157
|
+
}
|
|
58158
|
+
|
|
57871
58159
|
// src/severity.ts
|
|
57872
58160
|
var VRT_MAP = {
|
|
57873
58161
|
sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
|
|
@@ -57885,7 +58173,13 @@ var VRT_MAP = {
|
|
|
57885
58173
|
prototype_pollution: { vrt: "injection > prototype_pollution", priority: "P2", severity: "high", rationale: "Object prototype corruption can escalate to code execution in gadget-heavy runtimes." },
|
|
57886
58174
|
crlf_injection: { vrt: "injection > crlf_injection", priority: "P3", severity: "medium", rationale: "Header injection enables cookie poisoning or cache poisoning." },
|
|
57887
58175
|
missing_nonce: { vrt: "broken_access_control > cross_site_request_forgery", priority: "P3", severity: "medium", rationale: "A state-changing action lacks an anti-CSRF token." },
|
|
57888
|
-
wrong_sanitizer: { vrt: "injection > improper_input_validation", priority: "P3", severity: "medium", rationale: "A value sanitized for the wrong context still reaches the sink; severity follows the sink." }
|
|
58176
|
+
wrong_sanitizer: { vrt: "injection > improper_input_validation", priority: "P3", severity: "medium", rationale: "A value sanitized for the wrong context still reaches the sink; severity follows the sink." },
|
|
58177
|
+
llm_injection: { vrt: "injection > prompt_injection", priority: "P2", severity: "high", rationale: "Prompt injection can exfiltrate the system prompt, RAG data, or trigger privileged tool use." },
|
|
58178
|
+
graphql_exposure: { vrt: "misconfiguration > graphql_introspection", priority: "P4", severity: "low", rationale: "GraphQL introspection exposes the schema but is not directly exploitable." },
|
|
58179
|
+
oauth_misconfig: { vrt: "authentication > oauth_misconfiguration", priority: "P2", severity: "high", rationale: "An OAuth/open-redirect misconfiguration can leak authorization codes → account takeover." },
|
|
58180
|
+
grpc_reflection: { vrt: "misconfiguration > debug_information_exposure", priority: "P5", severity: "low", rationale: "gRPC reflection exposes the API contract but is not directly exploitable." },
|
|
58181
|
+
dependency_confusion: { vrt: "insecure_design > dependency_confusion", priority: "P2", severity: "high", rationale: "An internal package resolves from a squattable public registry → attacker-controlled code in the build." },
|
|
58182
|
+
ml_supply_chain: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Untrusted model loading via pickle (torch/joblib/numpy) → arbitrary code execution on load." }
|
|
57889
58183
|
};
|
|
57890
58184
|
function severityCalibrate(input) {
|
|
57891
58185
|
const type = input.type?.toLowerCase() ?? "";
|
|
@@ -57925,12 +58219,12 @@ function listSeverityMap() {
|
|
|
57925
58219
|
|
|
57926
58220
|
// src/datarefresh.ts
|
|
57927
58221
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
57928
|
-
import { join as
|
|
58222
|
+
import { join as join18, dirname as dirname3 } from "node:path";
|
|
57929
58223
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
57930
58224
|
var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
|
|
57931
58225
|
function loadJson2(rel) {
|
|
57932
58226
|
try {
|
|
57933
|
-
return JSON.parse(readFileSync18(
|
|
58227
|
+
return JSON.parse(readFileSync18(join18(__dirname3, "..", rel), "utf8"));
|
|
57934
58228
|
} catch {
|
|
57935
58229
|
return null;
|
|
57936
58230
|
}
|
|
@@ -57966,13 +58260,13 @@ init_variants();
|
|
|
57966
58260
|
|
|
57967
58261
|
// src/benchmark.ts
|
|
57968
58262
|
import { readFileSync as readFileSync20, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
57969
|
-
import { join as
|
|
58263
|
+
import { join as join20 } from "node:path";
|
|
57970
58264
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
57971
58265
|
import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
|
|
57972
58266
|
init_complex_bugs();
|
|
57973
|
-
var ROOT5 =
|
|
57974
|
-
var CORPUS =
|
|
57975
|
-
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ??
|
|
58267
|
+
var ROOT5 = join20(fileURLToPath11(new URL(".", import.meta.url)), "..");
|
|
58268
|
+
var CORPUS = join20(ROOT5, "bench", "corpus");
|
|
58269
|
+
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join20(homedir8(), ".blitzstrike", "corpus");
|
|
57976
58270
|
function parseCorpusFile(path) {
|
|
57977
58271
|
let text;
|
|
57978
58272
|
try {
|
|
@@ -58032,7 +58326,7 @@ function loadDir(dir) {
|
|
|
58032
58326
|
for (const f of readdirSync5(dir)) {
|
|
58033
58327
|
if (!f.endsWith(".md"))
|
|
58034
58328
|
continue;
|
|
58035
|
-
const e = parseCorpusFile(
|
|
58329
|
+
const e = parseCorpusFile(join20(dir, f));
|
|
58036
58330
|
if (e)
|
|
58037
58331
|
entries.push(e);
|
|
58038
58332
|
}
|
|
@@ -58059,7 +58353,7 @@ function captureFalsePositive(input) {
|
|
|
58059
58353
|
input.code,
|
|
58060
58354
|
"```"
|
|
58061
58355
|
].filter((l) => l !== null);
|
|
58062
|
-
const path =
|
|
58356
|
+
const path = join20(CAPTURED_CORPUS_DIR, `${id}.md`);
|
|
58063
58357
|
writeFileSync6(path, lines.join(`
|
|
58064
58358
|
`) + `
|
|
58065
58359
|
`, "utf8");
|
|
@@ -58081,7 +58375,7 @@ function detect(entry) {
|
|
|
58081
58375
|
}
|
|
58082
58376
|
const lang = entry.language;
|
|
58083
58377
|
if (lang === "php") {
|
|
58084
|
-
const tmp =
|
|
58378
|
+
const tmp = join20(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
58085
58379
|
writeFileSync6(tmp, entry.code);
|
|
58086
58380
|
const r = analyzeDataFlow2(tmp);
|
|
58087
58381
|
rmSync3(tmp, { force: true });
|
|
@@ -58150,21 +58444,21 @@ function runBenchmark() {
|
|
|
58150
58444
|
}
|
|
58151
58445
|
|
|
58152
58446
|
// src/obligations.ts
|
|
58153
|
-
import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as
|
|
58154
|
-
import { join as
|
|
58447
|
+
import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync7 } from "node:fs";
|
|
58448
|
+
import { join as join21 } from "node:path";
|
|
58155
58449
|
import { createHash as createHash6 } from "node:crypto";
|
|
58156
58450
|
import { homedir as homedir9 } from "node:os";
|
|
58157
|
-
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ??
|
|
58158
|
-
var OBLIGATION_PATH =
|
|
58451
|
+
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join21(homedir9(), ".blitzstrike");
|
|
58452
|
+
var OBLIGATION_PATH = join21(HOME_DIR4, "obligations.jsonl");
|
|
58159
58453
|
function ensureDir3() {
|
|
58160
|
-
if (!
|
|
58454
|
+
if (!existsSync11(HOME_DIR4))
|
|
58161
58455
|
mkdirSync8(HOME_DIR4, { recursive: true });
|
|
58162
58456
|
}
|
|
58163
58457
|
function shortId2(claim) {
|
|
58164
58458
|
return `obl-${createHash6("sha256").update(`${claim}\x00${Date.now()}`).digest("hex").slice(0, 12)}`;
|
|
58165
58459
|
}
|
|
58166
58460
|
function loadObligations() {
|
|
58167
|
-
if (!
|
|
58461
|
+
if (!existsSync11(OBLIGATION_PATH))
|
|
58168
58462
|
return [];
|
|
58169
58463
|
try {
|
|
58170
58464
|
const out = [];
|
|
@@ -59167,17 +59461,17 @@ function analyzeJwt(token, opts = {}) {
|
|
|
59167
59461
|
}
|
|
59168
59462
|
|
|
59169
59463
|
// src/enterprise-benchmark.ts
|
|
59170
|
-
import { existsSync as
|
|
59171
|
-
import { join as
|
|
59464
|
+
import { existsSync as existsSync12, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
59465
|
+
import { join as join22, resolve } from "node:path";
|
|
59172
59466
|
init_complex_bugs();
|
|
59173
59467
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
59174
59468
|
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:
|
|
59469
|
+
{ id: "php-laravel", dir: join22(ROOT6, "php-laravel"), expected_vulns: 4 },
|
|
59470
|
+
{ id: "js-express", dir: join22(ROOT6, "js-express"), expected_vulns: 2 },
|
|
59471
|
+
{ id: "py-django", dir: join22(ROOT6, "py-django"), expected_vulns: 2 },
|
|
59472
|
+
{ id: "java-spring", dir: join22(ROOT6, "java-spring"), expected_vulns: 2 },
|
|
59473
|
+
{ id: "wp-batch-route", dir: join22(ROOT6, "wp-batch-route"), expected_vulns: 3 },
|
|
59474
|
+
{ id: "php-complex", dir: join22(ROOT6, "php-complex"), expected_vulns: 4 }
|
|
59181
59475
|
];
|
|
59182
59476
|
function sourceFiles(root) {
|
|
59183
59477
|
const out = [];
|
|
@@ -59189,7 +59483,7 @@ function sourceFiles(root) {
|
|
|
59189
59483
|
return;
|
|
59190
59484
|
}
|
|
59191
59485
|
for (const e of entries) {
|
|
59192
|
-
const p =
|
|
59486
|
+
const p = join22(dir, e);
|
|
59193
59487
|
let st;
|
|
59194
59488
|
try {
|
|
59195
59489
|
st = statSync3(p);
|
|
@@ -59228,7 +59522,7 @@ function detectFixture(dir) {
|
|
|
59228
59522
|
function runEnterpriseBenchmark() {
|
|
59229
59523
|
const fixtures = [];
|
|
59230
59524
|
for (const fx of ENTERPRISE_FIXTURES) {
|
|
59231
|
-
if (!
|
|
59525
|
+
if (!existsSync12(fx.dir)) {
|
|
59232
59526
|
fixtures.push({ id: fx.id, available: false, detected: 0, expected: fx.expected_vulns, recall: 0 });
|
|
59233
59527
|
continue;
|
|
59234
59528
|
}
|
|
@@ -59405,7 +59699,7 @@ function createServer() {
|
|
|
59405
59699
|
` + `- 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
59700
|
` + `- 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
59701
|
|
|
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:
|
|
59702
|
+
` + "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). Two supply-chain detectors: dependency_confusion (a manifest/build file resolving a package from a public registry — pypi.org/npmjs.org --extra-index-url, or a scoped @company/pkg with no private registry) and ml_supply_chain (torch.load without weights_only=True / joblib.load / np.load(allow_pickle=True) / keras load_model on an untrusted model → pickle RCE).\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
|
|
59409
59703
|
` + `- 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
59704
|
` + `- 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
59705
|
` + "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 +60143,32 @@ ${statusLine}` : statusLine },
|
|
|
59849
60143
|
const r = synonymsFor(term);
|
|
59850
60144
|
return { content: [{ type: "text", text: JSON.stringify({ ...r, pattern: patternLookup(r.canonical) }) }] };
|
|
59851
60145
|
});
|
|
60146
|
+
server.registerTool("scan_git_history", {
|
|
60147
|
+
title: "Mine git history for deleted secrets",
|
|
60148
|
+
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.",
|
|
60149
|
+
inputSchema: {
|
|
60150
|
+
root: string2().describe("Path to the git repo root (must contain .git)."),
|
|
60151
|
+
secretPattern: string2().optional().describe("Optional: a specific secret token to pickaxe-search (git log -S) through history."),
|
|
60152
|
+
limit: number2().int().optional().describe("Max findings (default 30).")
|
|
60153
|
+
}
|
|
60154
|
+
}, async ({ root, secretPattern, limit }) => {
|
|
60155
|
+
return { content: [{ type: "text", text: JSON.stringify(scanGitHistory(root, { secretPattern, limit })) }] };
|
|
60156
|
+
});
|
|
60157
|
+
server.registerTool("validate_leaked_key", {
|
|
60158
|
+
title: "Prove a leaked key is still active (differential)",
|
|
60159
|
+
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.",
|
|
60160
|
+
inputSchema: {
|
|
60161
|
+
key: string2().optional().describe("Raw secret value to test (skip blob fetch)."),
|
|
60162
|
+
blobUrl: string2().optional().describe("Or: raw-blob URL (e.g. forge raw/commit/<sha>^/<path>) to fetch + parse the key from."),
|
|
60163
|
+
varName: string2().optional().describe("Variable name to parse from the blob (default SECRET_KEY)."),
|
|
60164
|
+
apiBase: string2().describe("Live API base URL."),
|
|
60165
|
+
endpoints: array(string2()).optional().describe("Read-only GET endpoints to probe (default ['/items'])."),
|
|
60166
|
+
scheme: string2().optional().describe("Header value template, e.g. 'Bearer {value}' (default)."),
|
|
60167
|
+
header: string2().optional().describe("Header name (default Authorization).")
|
|
60168
|
+
}
|
|
60169
|
+
}, async (input) => {
|
|
60170
|
+
return { content: [{ type: "text", text: JSON.stringify(await validateLeakedKey(input)) }] };
|
|
60171
|
+
});
|
|
59852
60172
|
server.registerTool("fofa_search", {
|
|
59853
60173
|
title: "FOFA asset search",
|
|
59854
60174
|
description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
|
|
@@ -61193,14 +61513,14 @@ async function serve() {
|
|
|
61193
61513
|
|
|
61194
61514
|
// src/index.ts
|
|
61195
61515
|
init_cli();
|
|
61196
|
-
import { readFileSync as readFileSync30, existsSync as
|
|
61197
|
-
import { join as
|
|
61516
|
+
import { readFileSync as readFileSync30, existsSync as existsSync15 } from "node:fs";
|
|
61517
|
+
import { join as join27 } from "node:path";
|
|
61198
61518
|
import { fileURLToPath as fileURLToPath16 } from "node:url";
|
|
61199
|
-
var ROOT11 =
|
|
61519
|
+
var ROOT11 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
|
|
61200
61520
|
function readVersion() {
|
|
61201
61521
|
try {
|
|
61202
|
-
const p =
|
|
61203
|
-
if (
|
|
61522
|
+
const p = join27(ROOT11, "package.json");
|
|
61523
|
+
if (existsSync15(p))
|
|
61204
61524
|
return JSON.parse(readFileSync30(p, "utf8")).version ?? "1.0.0";
|
|
61205
61525
|
} catch {}
|
|
61206
61526
|
return "1.0.0";
|