blitzstrike 2.4.56 → 2.4.58
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 +36 -0
- package/dist/index.js +302 -110
- package/intelligence/cwe_map.json +4 -1
- package/intelligence/patterns.json +151 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ 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.58] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added — Empirical grounding v1 (pattern library + submission gate)
|
|
11
|
+
|
|
12
|
+
- New `intelligence/patterns.json` — the empirical grounding layer: all 16 detector
|
|
13
|
+
types are traced to their CWE, real-world signature, the escalation chains they
|
|
14
|
+
compose into (cross-ref chains.json), and citable references (CWE/OWASP). Detection
|
|
15
|
+
is now derived from disclosed-report-class knowledge, not abstract heuristics alone.
|
|
16
|
+
- New `src/patterns.ts` + `pattern_lookup` MCP tool — returns a detector hit's empirical
|
|
17
|
+
grounding (CWE, real-world signature, chains, references) to turn a raw hit into a
|
|
18
|
+
class-aware finding.
|
|
19
|
+
- New `submission_gate` MCP tool — a deterministic 7-question gate (reproducible,
|
|
20
|
+
in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted,
|
|
21
|
+
severity-derived); one false = the finding does not ship. Formally enforces
|
|
22
|
+
"never report unverified".
|
|
23
|
+
- Inventory self-test extended: every detector type must be empirically grounded in
|
|
24
|
+
patterns.json AND CWE-mapped (drift fails at build time).
|
|
25
|
+
|
|
26
|
+
## [2.4.57] — 2026-09-14
|
|
27
|
+
|
|
28
|
+
### Added — Secret detection v2 (single taxonomy + format + entropy tiers)
|
|
29
|
+
|
|
30
|
+
- New `src/secrets.ts` — the single source of truth for secret field names,
|
|
31
|
+
known-format signatures, and redaction patterns. `sinks.scanSinks`,
|
|
32
|
+
`complex-bugs.detectHardcodedSecret`, and the evidence redactor all import
|
|
33
|
+
from it (previously three divergent copies).
|
|
34
|
+
- `hardcoded_secret` now detects in THREE tiers: (1) a known secret FIELD
|
|
35
|
+
assigned a literal (HIGH), (2) a known secret FORMAT — JWT/AWS/GitHub/Slack/
|
|
36
|
+
Google/Twilio/SendGrid/Mailgun/PEM — regardless of variable name (HIGH), and
|
|
37
|
+
(3) a high-entropy token (Shannon ≥ 4.5 bits/char, ≥32 chars, mixed case+digit)
|
|
38
|
+
under a non-obvious name (MEDIUM, suspect). Entropy skips minified/bundled files.
|
|
39
|
+
- CWE map completed: added `type_juggling` (CWE-697), `path_confusion` (CWE-22),
|
|
40
|
+
`wrong_sanitizer` (CWE-20) — the compliance tool now maps all 16 detector types.
|
|
41
|
+
- Added a detector-inventory self-test: every ComplexBugType must have a CWE
|
|
42
|
+
mapping (fails at build time if a new detector forgets its mapping).
|
|
43
|
+
|
|
8
44
|
## [2.4.56] — 2026-09-14
|
|
9
45
|
|
|
10
46
|
### Added — Hardcoded-secret detector (CWE-798)
|
package/dist/index.js
CHANGED
|
@@ -15490,10 +15490,10 @@ async function checkMcpServers() {
|
|
|
15490
15490
|
var init_mcp_status = () => {};
|
|
15491
15491
|
|
|
15492
15492
|
// src/sync.ts
|
|
15493
|
-
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as
|
|
15494
|
-
import { join as
|
|
15493
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync23, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
|
|
15494
|
+
import { join as join20 } from "node:path";
|
|
15495
15495
|
import { homedir as homedir10 } from "node:os";
|
|
15496
|
-
import { fileURLToPath as
|
|
15496
|
+
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
15497
15497
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
15498
15498
|
function dirExists(p) {
|
|
15499
15499
|
try {
|
|
@@ -15508,7 +15508,7 @@ function countFiles(dir) {
|
|
|
15508
15508
|
const walk = (d) => {
|
|
15509
15509
|
for (const e of readdirSync7(d, { withFileTypes: true })) {
|
|
15510
15510
|
if (e.isDirectory())
|
|
15511
|
-
walk(
|
|
15511
|
+
walk(join20(d, e.name));
|
|
15512
15512
|
else
|
|
15513
15513
|
n++;
|
|
15514
15514
|
}
|
|
@@ -15522,8 +15522,8 @@ function countFiles(dir) {
|
|
|
15522
15522
|
function syncData() {
|
|
15523
15523
|
if (!dirExists(DATA_ROOT2))
|
|
15524
15524
|
mkdirSync9(DATA_ROOT2, { recursive: true });
|
|
15525
|
-
const payloadsPath =
|
|
15526
|
-
const templatesPath =
|
|
15525
|
+
const payloadsPath = join20(DATA_ROOT2, "payloads");
|
|
15526
|
+
const templatesPath = join20(DATA_ROOT2, "templates");
|
|
15527
15527
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
15528
15528
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
15529
15529
|
if (hasPayloads && hasTemplates) {
|
|
@@ -15533,8 +15533,8 @@ function syncData() {
|
|
|
15533
15533
|
data_root: DATA_ROOT2
|
|
15534
15534
|
};
|
|
15535
15535
|
}
|
|
15536
|
-
const localPayloads =
|
|
15537
|
-
const localTemplates =
|
|
15536
|
+
const localPayloads = join20(ROOT7, "payloads");
|
|
15537
|
+
const localTemplates = join20(ROOT7, "templates");
|
|
15538
15538
|
let payloadsSynced = false;
|
|
15539
15539
|
let templatesSynced = false;
|
|
15540
15540
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -15547,7 +15547,7 @@ function syncData() {
|
|
|
15547
15547
|
}
|
|
15548
15548
|
const gitOk = spawnSync4("git", ["--version"]).status === 0;
|
|
15549
15549
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
15550
|
-
const tmp =
|
|
15550
|
+
const tmp = join20(DATA_ROOT2, ".tmp-clone");
|
|
15551
15551
|
if (dirExists(tmp))
|
|
15552
15552
|
rmSync4(tmp, { recursive: true, force: true });
|
|
15553
15553
|
const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -15555,11 +15555,11 @@ function syncData() {
|
|
|
15555
15555
|
const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
15556
15556
|
if (sparse.status === 0) {
|
|
15557
15557
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
15558
|
-
copyDir(
|
|
15558
|
+
copyDir(join20(tmp, "payloads"), payloadsPath);
|
|
15559
15559
|
payloadsSynced = true;
|
|
15560
15560
|
}
|
|
15561
15561
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
15562
|
-
copyDir(
|
|
15562
|
+
copyDir(join20(tmp, "templates"), templatesPath);
|
|
15563
15563
|
templatesSynced = true;
|
|
15564
15564
|
}
|
|
15565
15565
|
}
|
|
@@ -15581,18 +15581,18 @@ function copyDir(src, dst) {
|
|
|
15581
15581
|
function manualCopy(src, dst) {
|
|
15582
15582
|
mkdirSync9(dst, { recursive: true });
|
|
15583
15583
|
for (const e of readdirSync7(src, { withFileTypes: true })) {
|
|
15584
|
-
const s =
|
|
15585
|
-
const d =
|
|
15584
|
+
const s = join20(src, e.name);
|
|
15585
|
+
const d = join20(dst, e.name);
|
|
15586
15586
|
if (e.isDirectory())
|
|
15587
15587
|
manualCopy(s, d);
|
|
15588
15588
|
else
|
|
15589
|
-
writeFileSync8(d,
|
|
15589
|
+
writeFileSync8(d, readFileSync23(s));
|
|
15590
15590
|
}
|
|
15591
15591
|
}
|
|
15592
15592
|
var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
15593
15593
|
var init_sync = __esm(() => {
|
|
15594
|
-
ROOT7 =
|
|
15595
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
15594
|
+
ROOT7 = join20(fileURLToPath10(new URL(".", import.meta.url)), "..");
|
|
15595
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join20(homedir10(), ".blitzstrike", "data");
|
|
15596
15596
|
});
|
|
15597
15597
|
|
|
15598
15598
|
// src/update.ts
|
|
@@ -15637,15 +15637,15 @@ var init_update = __esm(() => {
|
|
|
15637
15637
|
});
|
|
15638
15638
|
|
|
15639
15639
|
// src/release.ts
|
|
15640
|
-
import { readFileSync as
|
|
15641
|
-
import { join as
|
|
15642
|
-
import { fileURLToPath as
|
|
15640
|
+
import { readFileSync as readFileSync25, existsSync as existsSync12 } from "node:fs";
|
|
15641
|
+
import { join as join21 } from "node:path";
|
|
15642
|
+
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
15643
15643
|
import { execSync as execSync2 } from "node:child_process";
|
|
15644
15644
|
function validateRelease() {
|
|
15645
15645
|
const checks = [];
|
|
15646
15646
|
checks.push({ name: "package.json version", ok: true, detail: VERSION });
|
|
15647
15647
|
try {
|
|
15648
|
-
const changelog =
|
|
15648
|
+
const changelog = readFileSync25(join21(ROOT8, "CHANGELOG.md"), "utf8");
|
|
15649
15649
|
const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
|
|
15650
15650
|
const changelogVersion = m?.[1] ?? null;
|
|
15651
15651
|
checks.push({
|
|
@@ -15677,22 +15677,22 @@ function validateRelease() {
|
|
|
15677
15677
|
return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
|
|
15678
15678
|
}
|
|
15679
15679
|
function lockfilePresent() {
|
|
15680
|
-
return existsSync12(
|
|
15680
|
+
return existsSync12(join21(ROOT8, "package-lock.json")) || existsSync12(join21(ROOT8, "bun.lock")) || existsSync12(join21(ROOT8, "bun.lockb"));
|
|
15681
15681
|
}
|
|
15682
15682
|
var ROOT8;
|
|
15683
15683
|
var init_release = __esm(() => {
|
|
15684
15684
|
init_version();
|
|
15685
|
-
ROOT8 =
|
|
15685
|
+
ROOT8 = join21(fileURLToPath11(new URL(".", import.meta.url)), "..");
|
|
15686
15686
|
});
|
|
15687
15687
|
|
|
15688
15688
|
// src/dependency.ts
|
|
15689
|
-
import { readFileSync as
|
|
15690
|
-
import { join as
|
|
15691
|
-
import { fileURLToPath as
|
|
15689
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
15690
|
+
import { join as join22 } from "node:path";
|
|
15691
|
+
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
15692
15692
|
function checkDependencies() {
|
|
15693
15693
|
let pkg = {};
|
|
15694
15694
|
try {
|
|
15695
|
-
pkg = JSON.parse(
|
|
15695
|
+
pkg = JSON.parse(readFileSync26(join22(ROOT9, "package.json"), "utf8"));
|
|
15696
15696
|
} catch {
|
|
15697
15697
|
return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
|
|
15698
15698
|
}
|
|
@@ -15715,14 +15715,14 @@ function checkDependencies() {
|
|
|
15715
15715
|
}
|
|
15716
15716
|
var ROOT9;
|
|
15717
15717
|
var init_dependency = __esm(() => {
|
|
15718
|
-
ROOT9 =
|
|
15718
|
+
ROOT9 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
15719
15719
|
});
|
|
15720
15720
|
|
|
15721
15721
|
// src/cli.ts
|
|
15722
|
-
import { readFileSync as
|
|
15723
|
-
import { join as
|
|
15722
|
+
import { readFileSync as readFileSync27, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
|
|
15723
|
+
import { join as join23, dirname as dirname3 } from "node:path";
|
|
15724
15724
|
import { homedir as homedir11 } from "node:os";
|
|
15725
|
-
import { fileURLToPath as
|
|
15725
|
+
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
15726
15726
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
15727
15727
|
function which(cmd) {
|
|
15728
15728
|
return spawnSync5("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", cmd]).status === 0;
|
|
@@ -15765,7 +15765,7 @@ async function runDoctor() {
|
|
|
15765
15765
|
detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
|
|
15766
15766
|
fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
|
|
15767
15767
|
});
|
|
15768
|
-
const dataOk = existsSync13(
|
|
15768
|
+
const dataOk = existsSync13(join23(ROOT10, "chains.json")) && existsSync13(join23(ROOT10, "tools-catalog.json"));
|
|
15769
15769
|
issues.push({
|
|
15770
15770
|
name: "Data layers (chains + tools-catalog)",
|
|
15771
15771
|
status: dataOk ? "ok" : "fail",
|
|
@@ -15816,28 +15816,28 @@ BlitzStrike — ${verdict}`);
|
|
|
15816
15816
|
function resolveCommand() {
|
|
15817
15817
|
if (which("blitzstrike"))
|
|
15818
15818
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
15819
|
-
const src =
|
|
15819
|
+
const src = join23(ROOT10, "src", "index.ts");
|
|
15820
15820
|
if (which("bun") && existsSync13(src))
|
|
15821
15821
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
15822
15822
|
if (which("npx"))
|
|
15823
15823
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
15824
15824
|
if (which("bunx"))
|
|
15825
15825
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
15826
|
-
const dist =
|
|
15826
|
+
const dist = join23(ROOT10, "dist", "index.js");
|
|
15827
15827
|
if (existsSync13(dist))
|
|
15828
15828
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
15829
15829
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
15830
15830
|
}
|
|
15831
15831
|
function readJson(p) {
|
|
15832
15832
|
try {
|
|
15833
|
-
return JSON.parse(
|
|
15833
|
+
return JSON.parse(readFileSync27(p, "utf8"));
|
|
15834
15834
|
} catch {
|
|
15835
15835
|
return null;
|
|
15836
15836
|
}
|
|
15837
15837
|
}
|
|
15838
15838
|
function jsonMcpServersWrite(p) {
|
|
15839
15839
|
return (command, args) => {
|
|
15840
|
-
const dir =
|
|
15840
|
+
const dir = dirname3(p);
|
|
15841
15841
|
if (!existsSync13(dir))
|
|
15842
15842
|
mkdirSync10(dir, { recursive: true });
|
|
15843
15843
|
const existing = readJson(p) ?? {};
|
|
@@ -15850,7 +15850,7 @@ function jsonMcpServersWrite(p) {
|
|
|
15850
15850
|
}
|
|
15851
15851
|
function opencodeWrite(p) {
|
|
15852
15852
|
return (command, args) => {
|
|
15853
|
-
const dir =
|
|
15853
|
+
const dir = dirname3(p);
|
|
15854
15854
|
if (!existsSync13(dir))
|
|
15855
15855
|
mkdirSync10(dir, { recursive: true });
|
|
15856
15856
|
const existing = readJson(p) ?? {};
|
|
@@ -15863,26 +15863,26 @@ function opencodeWrite(p) {
|
|
|
15863
15863
|
};
|
|
15864
15864
|
}
|
|
15865
15865
|
function copyOpenCodeAgents() {
|
|
15866
|
-
const srcDir =
|
|
15867
|
-
const dstDir =
|
|
15866
|
+
const srcDir = join23(ROOT10, "opencode-agents");
|
|
15867
|
+
const dstDir = join23(homedir11(), ".config", "opencode", "agents");
|
|
15868
15868
|
if (!existsSync13(srcDir))
|
|
15869
15869
|
return;
|
|
15870
15870
|
if (!existsSync13(dstDir))
|
|
15871
15871
|
mkdirSync10(dstDir, { recursive: true });
|
|
15872
15872
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
15873
|
-
const src =
|
|
15874
|
-
const dst =
|
|
15873
|
+
const src = join23(srcDir, f);
|
|
15874
|
+
const dst = join23(dstDir, f);
|
|
15875
15875
|
if (existsSync13(src)) {
|
|
15876
|
-
writeFileSync9(dst,
|
|
15876
|
+
writeFileSync9(dst, readFileSync27(src, "utf8"));
|
|
15877
15877
|
}
|
|
15878
15878
|
}
|
|
15879
15879
|
}
|
|
15880
15880
|
function codexWrite(p) {
|
|
15881
15881
|
return (command, args) => {
|
|
15882
|
-
const dir =
|
|
15882
|
+
const dir = dirname3(p);
|
|
15883
15883
|
if (!existsSync13(dir))
|
|
15884
15884
|
mkdirSync10(dir, { recursive: true });
|
|
15885
|
-
let existing = existsSync13(p) ?
|
|
15885
|
+
let existing = existsSync13(p) ? readFileSync27(p, "utf8") : "";
|
|
15886
15886
|
if (!existing.trimEnd().endsWith(`
|
|
15887
15887
|
`))
|
|
15888
15888
|
existing += `
|
|
@@ -15898,10 +15898,10 @@ args = ${JSON.stringify(args)}
|
|
|
15898
15898
|
}
|
|
15899
15899
|
function hermesWrite(p) {
|
|
15900
15900
|
return (command, args) => {
|
|
15901
|
-
const dir =
|
|
15901
|
+
const dir = dirname3(p);
|
|
15902
15902
|
if (!existsSync13(dir))
|
|
15903
15903
|
mkdirSync10(dir, { recursive: true });
|
|
15904
|
-
let existing = existsSync13(p) ?
|
|
15904
|
+
let existing = existsSync13(p) ? readFileSync27(p, "utf8") : "";
|
|
15905
15905
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
15906
15906
|
if (!existing.trimEnd().endsWith(`
|
|
15907
15907
|
`))
|
|
@@ -15928,74 +15928,74 @@ mcp_servers:
|
|
|
15928
15928
|
function detectAgents() {
|
|
15929
15929
|
const home = homedir11();
|
|
15930
15930
|
const agents = [];
|
|
15931
|
-
const claudeJson =
|
|
15931
|
+
const claudeJson = join23(home, ".claude.json");
|
|
15932
15932
|
agents.push({
|
|
15933
15933
|
name: "Claude Code",
|
|
15934
15934
|
detectPath: claudeJson,
|
|
15935
15935
|
installed: () => existsSync13(claudeJson) || which("claude"),
|
|
15936
15936
|
write: jsonMcpServersWrite(claudeJson)
|
|
15937
15937
|
});
|
|
15938
|
-
const claudeDesktop =
|
|
15938
|
+
const claudeDesktop = join23(home, ".config", "Claude", "claude_desktop_config.json");
|
|
15939
15939
|
agents.push({
|
|
15940
15940
|
name: "Claude Desktop",
|
|
15941
15941
|
detectPath: claudeDesktop,
|
|
15942
15942
|
installed: () => existsSync13(claudeDesktop),
|
|
15943
15943
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
15944
15944
|
});
|
|
15945
|
-
const cursor =
|
|
15945
|
+
const cursor = join23(home, ".cursor", "mcp.json");
|
|
15946
15946
|
agents.push({
|
|
15947
15947
|
name: "Cursor",
|
|
15948
15948
|
detectPath: cursor,
|
|
15949
|
-
installed: () => existsSync13(
|
|
15949
|
+
installed: () => existsSync13(join23(home, ".cursor")) || which("cursor"),
|
|
15950
15950
|
write: jsonMcpServersWrite(cursor)
|
|
15951
15951
|
});
|
|
15952
|
-
const opencode =
|
|
15952
|
+
const opencode = join23(home, ".config", "opencode", "opencode.jsonc");
|
|
15953
15953
|
agents.push({
|
|
15954
15954
|
name: "OpenCode",
|
|
15955
15955
|
detectPath: opencode,
|
|
15956
|
-
installed: () => existsSync13(
|
|
15956
|
+
installed: () => existsSync13(join23(home, ".config", "opencode")) || which("opencode"),
|
|
15957
15957
|
write: opencodeWrite(opencode)
|
|
15958
15958
|
});
|
|
15959
|
-
const codex =
|
|
15959
|
+
const codex = join23(home, ".codex", "config.toml");
|
|
15960
15960
|
agents.push({
|
|
15961
15961
|
name: "Codex",
|
|
15962
15962
|
detectPath: codex,
|
|
15963
|
-
installed: () => existsSync13(
|
|
15963
|
+
installed: () => existsSync13(join23(home, ".codex")) || which("codex"),
|
|
15964
15964
|
write: codexWrite(codex)
|
|
15965
15965
|
});
|
|
15966
|
-
const hermes =
|
|
15966
|
+
const hermes = join23(home, ".hermes", "config.yaml");
|
|
15967
15967
|
agents.push({
|
|
15968
15968
|
name: "Hermes",
|
|
15969
15969
|
detectPath: hermes,
|
|
15970
|
-
installed: () => existsSync13(
|
|
15970
|
+
installed: () => existsSync13(join23(home, ".hermes")) || which("hermes"),
|
|
15971
15971
|
write: hermesWrite(hermes)
|
|
15972
15972
|
});
|
|
15973
|
-
const gemini =
|
|
15973
|
+
const gemini = join23(home, ".gemini", "settings.json");
|
|
15974
15974
|
agents.push({
|
|
15975
15975
|
name: "Gemini CLI",
|
|
15976
15976
|
detectPath: gemini,
|
|
15977
|
-
installed: () => existsSync13(
|
|
15977
|
+
installed: () => existsSync13(join23(home, ".gemini")) || which("gemini"),
|
|
15978
15978
|
write: jsonMcpServersWrite(gemini)
|
|
15979
15979
|
});
|
|
15980
|
-
const windsurf =
|
|
15980
|
+
const windsurf = join23(home, ".codeium", "windsurf", "mcp_config.json");
|
|
15981
15981
|
agents.push({
|
|
15982
15982
|
name: "Windsurf",
|
|
15983
15983
|
detectPath: windsurf,
|
|
15984
|
-
installed: () => existsSync13(
|
|
15984
|
+
installed: () => existsSync13(join23(home, ".codeium")) || which("windsurf"),
|
|
15985
15985
|
write: jsonMcpServersWrite(windsurf)
|
|
15986
15986
|
});
|
|
15987
|
-
const copilot =
|
|
15987
|
+
const copilot = join23(home, ".copilot", "mcp.json");
|
|
15988
15988
|
agents.push({
|
|
15989
15989
|
name: "Copilot",
|
|
15990
15990
|
detectPath: copilot,
|
|
15991
|
-
installed: () => existsSync13(
|
|
15991
|
+
installed: () => existsSync13(join23(home, ".copilot")),
|
|
15992
15992
|
write: jsonMcpServersWrite(copilot)
|
|
15993
15993
|
});
|
|
15994
|
-
const cline =
|
|
15994
|
+
const cline = join23(home, ".cline", "mcp_settings.json");
|
|
15995
15995
|
agents.push({
|
|
15996
15996
|
name: "Cline",
|
|
15997
15997
|
detectPath: cline,
|
|
15998
|
-
installed: () => existsSync13(
|
|
15998
|
+
installed: () => existsSync13(join23(home, ".cline")),
|
|
15999
15999
|
write: jsonMcpServersWrite(cline)
|
|
16000
16000
|
});
|
|
16001
16001
|
return agents;
|
|
@@ -16113,7 +16113,7 @@ var init_cli = __esm(() => {
|
|
|
16113
16113
|
init_release();
|
|
16114
16114
|
init_dependency();
|
|
16115
16115
|
init_mcp_status();
|
|
16116
|
-
ROOT10 =
|
|
16116
|
+
ROOT10 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
|
|
16117
16117
|
});
|
|
16118
16118
|
|
|
16119
16119
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -30751,7 +30751,7 @@ class StdioServerTransport {
|
|
|
30751
30751
|
|
|
30752
30752
|
// src/server.ts
|
|
30753
30753
|
init_scanner();
|
|
30754
|
-
import { readFileSync as
|
|
30754
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
30755
30755
|
|
|
30756
30756
|
// src/recon.ts
|
|
30757
30757
|
var TIMEOUT_MS = 25000;
|
|
@@ -31000,6 +31000,36 @@ function recordAudit(event, opts = {}) {
|
|
|
31000
31000
|
// src/evidence.ts
|
|
31001
31001
|
init_version();
|
|
31002
31002
|
import { createHash as createHash2 } from "node:crypto";
|
|
31003
|
+
|
|
31004
|
+
// src/secrets.ts
|
|
31005
|
+
var SECRET_FIELDS = "api[_-]?key|apikey|api[_-]?secret|access[_-]?key|access[_-]?token|secret[_-]?key|secret[_-]?access[_-]?key|auth[_-]?token|client[_-]?secret|consumer[_-]?secret|consumer[_-]?key|private[_-]?key|db[_-]?password|database[_-]?password|db[_-]?pass|passwd|password|jwt[_-]?secret|encryption[_-]?key|encryption[_-]?password|github[_-]?token|gitlab[_-]?token|stripe[_-]?key|sendgrid[_-]?api[_-]?key|datadog[_-]?api[_-]?key|cloudflare[_-]?api[_-]?key|slack[_-]?token|aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key|aws[_-]?session[_-]?token|bot[_-]?token";
|
|
31006
|
+
var SECRET_FORMATS = [
|
|
31007
|
+
{ name: "JWT", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g },
|
|
31008
|
+
{ name: "AWS access key", re: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
31009
|
+
{ name: "GitHub PAT (classic)", re: /\bghp_[A-Za-z0-9]{20,}\b/g },
|
|
31010
|
+
{ name: "GitHub PAT (fine-grained)", re: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g },
|
|
31011
|
+
{ name: "OpenAI/Stripe key", re: /\bsk-[A-Za-z0-9]{20,}\b/g },
|
|
31012
|
+
{ name: "Stripe live/test key", re: /\b(?:sk|pk|rk)_(?:live|test)_[0-9a-zA-Z]{10,}\b/g },
|
|
31013
|
+
{ name: "Slack token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
31014
|
+
{ name: "Google API key", re: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
31015
|
+
{ name: "Twilio account SID", re: /\bAC[0-9a-f]{32}\b/g },
|
|
31016
|
+
{ name: "SendGrid API key", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g },
|
|
31017
|
+
{ name: "Mailgun API key", re: /\bkey-[0-9a-zA-Z]{32}\b/g },
|
|
31018
|
+
{ name: "PEM private key", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g }
|
|
31019
|
+
];
|
|
31020
|
+
function shannonEntropy(s) {
|
|
31021
|
+
if (!s)
|
|
31022
|
+
return 0;
|
|
31023
|
+
const freq = new Map;
|
|
31024
|
+
for (const c of s)
|
|
31025
|
+
freq.set(c, (freq.get(c) ?? 0) + 1);
|
|
31026
|
+
let e = 0;
|
|
31027
|
+
for (const n of freq.values()) {
|
|
31028
|
+
const p = n / s.length;
|
|
31029
|
+
e -= p * Math.log2(p);
|
|
31030
|
+
}
|
|
31031
|
+
return e;
|
|
31032
|
+
}
|
|
31003
31033
|
var REDACTION_PATTERNS = [
|
|
31004
31034
|
[/(authorization\s*[:=]\s*)(bearer\s+)?[A-Za-z0-9._~+/=-]{8,}/gi, "$1$2[REDACTED]"],
|
|
31005
31035
|
[/(api[_-]?key\s*[:=]\s*)["']?[A-Za-z0-9._-]{16,}["']?/gi, "$1[REDACTED]"],
|
|
@@ -31011,10 +31041,17 @@ var REDACTION_PATTERNS = [
|
|
|
31011
31041
|
[/(session\s*[:=]\s*)["']?[A-Za-z0-9._-]{12,}["']?/gi, "$1[REDACTED]"],
|
|
31012
31042
|
[/(cookie\s*[:=]\s*)["']?[A-Za-z0-9._-]{12,}["']?/gi, "$1[REDACTED]"],
|
|
31013
31043
|
[/(set-cookie\s*:\s*)[^\r\n]+/gi, "$1[REDACTED]"],
|
|
31044
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, "[REDACTED]"],
|
|
31014
31045
|
[/AKIA[0-9A-Z]{16}/g, "[REDACTED]"],
|
|
31015
31046
|
[/ghp_[A-Za-z0-9]{20,}/g, "[REDACTED]"],
|
|
31016
31047
|
[/github_pat_[A-Za-z0-9_]{20,}/g, "[REDACTED]"],
|
|
31017
31048
|
[/sk-[A-Za-z0-9]{20,}/g, "[REDACTED]"],
|
|
31049
|
+
[/\b(?:sk|pk|rk)_(?:live|test)_[0-9a-zA-Z]{10,}\b/g, "[REDACTED]"],
|
|
31050
|
+
[/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, "[REDACTED]"],
|
|
31051
|
+
[/\bAIza[0-9A-Za-z_-]{35}\b/g, "[REDACTED]"],
|
|
31052
|
+
[/\bAC[0-9a-f]{32}\b/g, "[REDACTED]"],
|
|
31053
|
+
[/\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, "[REDACTED]"],
|
|
31054
|
+
[/\bkey-[0-9a-zA-Z]{32}\b/g, "[REDACTED]"],
|
|
31018
31055
|
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "[REDACTED]"]
|
|
31019
31056
|
];
|
|
31020
31057
|
function redactSecrets(text) {
|
|
@@ -31024,6 +31061,8 @@ function redactSecrets(text) {
|
|
|
31024
31061
|
}
|
|
31025
31062
|
return out;
|
|
31026
31063
|
}
|
|
31064
|
+
|
|
31065
|
+
// src/evidence.ts
|
|
31027
31066
|
function hasSecrets(text) {
|
|
31028
31067
|
return redactSecrets(text) !== text;
|
|
31029
31068
|
}
|
|
@@ -31321,7 +31360,8 @@ var PATTERNS = [
|
|
|
31321
31360
|
{ type: "credential", label: "password assignment", regex: /(?:password|passwd|pass|pwd)\s*[:=]\s*["'][^"']{3,}["']/i },
|
|
31322
31361
|
{ type: "credential", label: "hardcoded cred", regex: /\b(?:admin|root|user(?:name)?)\s*[:=]\s*["'][^"']{3,}["']/i },
|
|
31323
31362
|
{ type: "deserialization", label: "pickle/unserialize/yaml", regex: /\b(?:pickle\.loads|unserialize\s*\(|yaml\.load\s*\(|marshal\.loads|jsonpickle)/i },
|
|
31324
|
-
{ type: "xxe", label: "XML parse", regex: /\b(?:etree\.(?:parse|fromstring)|lxml\.etree|xml\.(?:dom|sax)|DocumentBuilder|simplexml_load)/i }
|
|
31363
|
+
{ type: "xxe", label: "XML parse", regex: /\b(?:etree\.(?:parse|fromstring)|lxml\.etree|xml\.(?:dom|sax)|DocumentBuilder|simplexml_load)/i },
|
|
31364
|
+
...SECRET_FORMATS.map((f) => ({ type: "secret", label: f.name, regex: f.re }))
|
|
31325
31365
|
];
|
|
31326
31366
|
function scanSinks(text) {
|
|
31327
31367
|
if (!text || text.length < 3)
|
|
@@ -57012,12 +57052,13 @@ function detectSqlInjection(code, file, out) {
|
|
|
57012
57052
|
});
|
|
57013
57053
|
}
|
|
57014
57054
|
}
|
|
57015
|
-
var SECRET_FIELDS = "api[_-]?key|apikey|api[_-]?secret|access[_-]?key|access[_-]?token|secret[_-]?key|secret[_-]?access[_-]?key|auth[_-]?token|client[_-]?secret|consumer[_-]?secret|consumer[_-]?key|private[_-]?key|db[_-]?password|database[_-]?password|db[_-]?pass|passwd|password|jwt[_-]?secret|encryption[_-]?key|encryption[_-]?password|github[_-]?token|gitlab[_-]?token|stripe[_-]?key|sendgrid[_-]?api[_-]?key|datadog[_-]?api[_-]?key|cloudflare[_-]?api[_-]?key|slack[_-]?token|aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key|aws[_-]?session[_-]?token|bot[_-]?token";
|
|
57016
57055
|
var SECRET_FIELD_RE = new RegExp(`\\b(${SECRET_FIELDS})\\s*(?:::|=>|=|:)\\s*`, "gi");
|
|
57017
57056
|
var SECRET_PLACEHOLDER_RE = /^(null|true|false|undefined|your_|xxx+|changeme|example|placeholder|test|secret|password|token|key|api|smtp|localhost|root|admin|pass|change|me|none|na|foo|bar|dummy)$/i;
|
|
57057
|
+
var ENTROPY_EXCLUDE_VAR = /\b(data|payload|content|body|base64|encoded|encode|image|binary|blob|hash|digest|checksum|nonce|uuid|etag|signature|sig|cipher|iv|ciphertext|plaintext|thumb|thumbnail)\b/i;
|
|
57018
57058
|
function detectHardcodedSecret(code, file, out) {
|
|
57019
57059
|
const ls = lines2(code);
|
|
57020
57060
|
const clean = stripCommentsOnly(code);
|
|
57061
|
+
const flaggedLines = new Set;
|
|
57021
57062
|
SECRET_FIELD_RE.lastIndex = 0;
|
|
57022
57063
|
let m;
|
|
57023
57064
|
while ((m = SECRET_FIELD_RE.exec(clean)) !== null) {
|
|
@@ -57038,6 +57079,7 @@ function detectHardcodedSecret(code, file, out) {
|
|
|
57038
57079
|
if (/\$[A-Za-z_]|[{}\[\]]|process\.env|getenv|os\.environ|config\s*\(|env\s*\(/.test(valueRaw))
|
|
57039
57080
|
continue;
|
|
57040
57081
|
const ln = lineNo2(code, m.index);
|
|
57082
|
+
flaggedLines.add(ln);
|
|
57041
57083
|
out.push({
|
|
57042
57084
|
file,
|
|
57043
57085
|
line: ln,
|
|
@@ -57048,6 +57090,54 @@ function detectHardcodedSecret(code, file, out) {
|
|
|
57048
57090
|
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.`
|
|
57049
57091
|
});
|
|
57050
57092
|
}
|
|
57093
|
+
for (const fmt of SECRET_FORMATS) {
|
|
57094
|
+
fmt.re.lastIndex = 0;
|
|
57095
|
+
let fm;
|
|
57096
|
+
while ((fm = fmt.re.exec(clean)) !== null) {
|
|
57097
|
+
const ln = lineNo2(code, fm.index);
|
|
57098
|
+
if (flaggedLines.has(ln))
|
|
57099
|
+
continue;
|
|
57100
|
+
flaggedLines.add(ln);
|
|
57101
|
+
out.push({
|
|
57102
|
+
file,
|
|
57103
|
+
line: ln,
|
|
57104
|
+
type: "hardcoded_secret",
|
|
57105
|
+
category: "Hardcoded secret (known format)",
|
|
57106
|
+
severity: "high",
|
|
57107
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
57108
|
+
detail: `A ${fmt.name} was found hardcoded in source — the credential is exposed to anyone with code access (CWE-798). Rotate it and load from the environment instead.`
|
|
57109
|
+
});
|
|
57110
|
+
}
|
|
57111
|
+
}
|
|
57112
|
+
const isMinified = /\.min\.(js|css)$/i.test(file) || /[\\/](bundle|vendor|dist|build)[\\/]/i.test(file);
|
|
57113
|
+
if (!isMinified) {
|
|
57114
|
+
const entropyRe = /(\b[A-Za-z_]\w*)\s*(?:::|=>|=|:)\s*["']([A-Za-z0-9_-]{32,})["']/g;
|
|
57115
|
+
entropyRe.lastIndex = 0;
|
|
57116
|
+
let em;
|
|
57117
|
+
while ((em = entropyRe.exec(clean)) !== null) {
|
|
57118
|
+
const varName = em[1];
|
|
57119
|
+
const value = em[2];
|
|
57120
|
+
const ln = lineNo2(code, em.index);
|
|
57121
|
+
if (flaggedLines.has(ln))
|
|
57122
|
+
continue;
|
|
57123
|
+
if (ENTROPY_EXCLUDE_VAR.test(varName))
|
|
57124
|
+
continue;
|
|
57125
|
+
if (!/[A-Z]/.test(value) || !/[a-z]/.test(value) || !/[0-9]/.test(value))
|
|
57126
|
+
continue;
|
|
57127
|
+
if (shannonEntropy(value) < 4.5)
|
|
57128
|
+
continue;
|
|
57129
|
+
flaggedLines.add(ln);
|
|
57130
|
+
out.push({
|
|
57131
|
+
file,
|
|
57132
|
+
line: ln,
|
|
57133
|
+
type: "hardcoded_secret",
|
|
57134
|
+
category: "Suspected secret (high-entropy token)",
|
|
57135
|
+
severity: "medium",
|
|
57136
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
57137
|
+
detail: `'${varName}' is assigned a high-entropy token (Shannon ${shannonEntropy(value).toFixed(2)} bits/char) that looks like a generated credential — verify it is not a secret key/token (CWE-798).`
|
|
57138
|
+
});
|
|
57139
|
+
}
|
|
57140
|
+
}
|
|
57051
57141
|
}
|
|
57052
57142
|
|
|
57053
57143
|
// src/guidance.ts
|
|
@@ -57105,18 +57195,89 @@ function nextSteps(ctx) {
|
|
|
57105
57195
|
return steps;
|
|
57106
57196
|
}
|
|
57107
57197
|
|
|
57108
|
-
// src/
|
|
57109
|
-
import { readFileSync as readFileSync17
|
|
57110
|
-
import { join as join16 } from "node:path";
|
|
57198
|
+
// src/patterns.ts
|
|
57199
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
57200
|
+
import { join as join16, dirname as dirname2 } from "node:path";
|
|
57111
57201
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
57202
|
+
var __dirname2 = dirname2(fileURLToPath8(import.meta.url));
|
|
57203
|
+
var _doc = null;
|
|
57204
|
+
function load3() {
|
|
57205
|
+
if (_doc)
|
|
57206
|
+
return _doc;
|
|
57207
|
+
try {
|
|
57208
|
+
_doc = JSON.parse(readFileSync17(join16(__dirname2, "..", "intelligence", "patterns.json"), "utf8"));
|
|
57209
|
+
} catch {
|
|
57210
|
+
_doc = { patterns: [] };
|
|
57211
|
+
}
|
|
57212
|
+
return _doc;
|
|
57213
|
+
}
|
|
57214
|
+
function patternLookup(id) {
|
|
57215
|
+
const p = load3().patterns.find((x) => x.id === id.toLowerCase());
|
|
57216
|
+
return p ?? null;
|
|
57217
|
+
}
|
|
57218
|
+
function listPatterns() {
|
|
57219
|
+
return load3().patterns;
|
|
57220
|
+
}
|
|
57221
|
+
function submissionGate(input) {
|
|
57222
|
+
const questions = [
|
|
57223
|
+
{
|
|
57224
|
+
q: "Reproducible — a copy-paste-ready request or step list triggers it?",
|
|
57225
|
+
pass: input.reproducible !== false,
|
|
57226
|
+
reason: "Without an exact reproduction the finding is a hypothesis, not a result."
|
|
57227
|
+
},
|
|
57228
|
+
{
|
|
57229
|
+
q: "In scope — the vulnerable asset is in the engagement scope?",
|
|
57230
|
+
pass: input.in_scope !== false,
|
|
57231
|
+
reason: "Out-of-scope assets must not be submitted."
|
|
57232
|
+
},
|
|
57233
|
+
{
|
|
57234
|
+
q: "Real impact — maps to an accepted-impact class, not informational?",
|
|
57235
|
+
pass: input.real_impact !== false,
|
|
57236
|
+
reason: "Informational-only findings dilute the validity ratio."
|
|
57237
|
+
},
|
|
57238
|
+
{
|
|
57239
|
+
q: "No privileged assumption — works without unrealistic attacker prerequisites?",
|
|
57240
|
+
pass: input.no_privileged_assumption !== false,
|
|
57241
|
+
reason: "'Admin can do X' is a centralization risk, not a vulnerability."
|
|
57242
|
+
},
|
|
57243
|
+
{
|
|
57244
|
+
q: "Verified live — confirmed with a marker + negative control, not just a static hint?",
|
|
57245
|
+
pass: input.verified_live !== false,
|
|
57246
|
+
reason: "A static hit is a hypothesis until verified live (strike_verify / browser_validate)."
|
|
57247
|
+
},
|
|
57248
|
+
{
|
|
57249
|
+
q: "Evidence attached AND redacted — secrets/PII stripped, integrity-tagged?",
|
|
57250
|
+
pass: input.evidence_redacted !== false,
|
|
57251
|
+
reason: "Evidence must carry SHA-256-tagged, redacted records — no leaked secrets or victim PII."
|
|
57252
|
+
},
|
|
57253
|
+
{
|
|
57254
|
+
q: "Severity derived — CVSS/confidence computed, not guessed?",
|
|
57255
|
+
pass: input.severity_derived !== false,
|
|
57256
|
+
reason: "Severity is not confidence; derive it, don't assert it."
|
|
57257
|
+
}
|
|
57258
|
+
];
|
|
57259
|
+
const failed = questions.find((q) => !q.pass);
|
|
57260
|
+
return {
|
|
57261
|
+
verdict: failed ? "fail" : "pass",
|
|
57262
|
+
passed: questions.filter((q) => q.pass).length,
|
|
57263
|
+
total: questions.length,
|
|
57264
|
+
questions,
|
|
57265
|
+
...failed ? { blocker: failed.q } : {}
|
|
57266
|
+
};
|
|
57267
|
+
}
|
|
57268
|
+
|
|
57269
|
+
// src/benchmark.ts
|
|
57270
|
+
import { readFileSync as readFileSync18, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
57271
|
+
import { join as join17 } from "node:path";
|
|
57272
|
+
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
57112
57273
|
import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
|
|
57113
|
-
var ROOT5 =
|
|
57114
|
-
var CORPUS =
|
|
57115
|
-
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ??
|
|
57274
|
+
var ROOT5 = join17(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
57275
|
+
var CORPUS = join17(ROOT5, "bench", "corpus");
|
|
57276
|
+
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join17(homedir8(), ".blitzstrike", "corpus");
|
|
57116
57277
|
function parseCorpusFile(path) {
|
|
57117
57278
|
let text;
|
|
57118
57279
|
try {
|
|
57119
|
-
text =
|
|
57280
|
+
text = readFileSync18(path, "utf8");
|
|
57120
57281
|
} catch {
|
|
57121
57282
|
return null;
|
|
57122
57283
|
}
|
|
@@ -57172,7 +57333,7 @@ function loadDir(dir) {
|
|
|
57172
57333
|
for (const f of readdirSync5(dir)) {
|
|
57173
57334
|
if (!f.endsWith(".md"))
|
|
57174
57335
|
continue;
|
|
57175
|
-
const e = parseCorpusFile(
|
|
57336
|
+
const e = parseCorpusFile(join17(dir, f));
|
|
57176
57337
|
if (e)
|
|
57177
57338
|
entries.push(e);
|
|
57178
57339
|
}
|
|
@@ -57199,7 +57360,7 @@ function captureFalsePositive(input) {
|
|
|
57199
57360
|
input.code,
|
|
57200
57361
|
"```"
|
|
57201
57362
|
].filter((l) => l !== null);
|
|
57202
|
-
const path =
|
|
57363
|
+
const path = join17(CAPTURED_CORPUS_DIR, `${id}.md`);
|
|
57203
57364
|
writeFileSync6(path, lines.join(`
|
|
57204
57365
|
`) + `
|
|
57205
57366
|
`, "utf8");
|
|
@@ -57221,7 +57382,7 @@ function detect(entry) {
|
|
|
57221
57382
|
}
|
|
57222
57383
|
const lang = entry.language;
|
|
57223
57384
|
if (lang === "php") {
|
|
57224
|
-
const tmp =
|
|
57385
|
+
const tmp = join17(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
57225
57386
|
writeFileSync6(tmp, entry.code);
|
|
57226
57387
|
const r = analyzeDataFlow2(tmp);
|
|
57227
57388
|
rmSync3(tmp, { force: true });
|
|
@@ -57290,12 +57451,12 @@ function runBenchmark() {
|
|
|
57290
57451
|
}
|
|
57291
57452
|
|
|
57292
57453
|
// src/obligations.ts
|
|
57293
|
-
import { readFileSync as
|
|
57294
|
-
import { join as
|
|
57454
|
+
import { readFileSync as readFileSync19, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
57455
|
+
import { join as join18 } from "node:path";
|
|
57295
57456
|
import { createHash as createHash6 } from "node:crypto";
|
|
57296
57457
|
import { homedir as homedir9 } from "node:os";
|
|
57297
|
-
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ??
|
|
57298
|
-
var OBLIGATION_PATH =
|
|
57458
|
+
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join18(homedir9(), ".blitzstrike");
|
|
57459
|
+
var OBLIGATION_PATH = join18(HOME_DIR4, "obligations.jsonl");
|
|
57299
57460
|
function ensureDir3() {
|
|
57300
57461
|
if (!existsSync10(HOME_DIR4))
|
|
57301
57462
|
mkdirSync8(HOME_DIR4, { recursive: true });
|
|
@@ -57308,7 +57469,7 @@ function loadObligations() {
|
|
|
57308
57469
|
return [];
|
|
57309
57470
|
try {
|
|
57310
57471
|
const out = [];
|
|
57311
|
-
for (const line of
|
|
57472
|
+
for (const line of readFileSync19(OBLIGATION_PATH, "utf8").split(`
|
|
57312
57473
|
`)) {
|
|
57313
57474
|
const t = line.trim();
|
|
57314
57475
|
if (!t)
|
|
@@ -57407,11 +57568,11 @@ function openCount() {
|
|
|
57407
57568
|
|
|
57408
57569
|
// src/variant-scan.ts
|
|
57409
57570
|
init_scanner();
|
|
57410
|
-
import { readFileSync as
|
|
57571
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
57411
57572
|
|
|
57412
57573
|
// src/cross-file.ts
|
|
57413
57574
|
init_scanner();
|
|
57414
|
-
import { readFileSync as
|
|
57575
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
57415
57576
|
var PUBLIC_HOOK_RE = /(?:wp_ajax_nopriv_\w+|admin_post_nopriv_\w+|init|template_redirect|wp_loaded|parse_request|user_register|um_user_register|register_new_user|authenticate)/;
|
|
57416
57577
|
var PRIV_SINK_RE = /\b(set_role|add_role|remove_role|set_user_role|wp_set_current_user|wp_set_auth_cookie|wp_insert_user|wp_create_user|wp_update_user)\s*\(/;
|
|
57417
57578
|
var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
|
|
@@ -57463,7 +57624,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
57463
57624
|
for (const file of files) {
|
|
57464
57625
|
let code;
|
|
57465
57626
|
try {
|
|
57466
|
-
code = stripComments(
|
|
57627
|
+
code = stripComments(readFileSync20(file, "utf8"));
|
|
57467
57628
|
} catch {
|
|
57468
57629
|
continue;
|
|
57469
57630
|
}
|
|
@@ -57504,7 +57665,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
57504
57665
|
for (const file of files) {
|
|
57505
57666
|
let code;
|
|
57506
57667
|
try {
|
|
57507
|
-
code = stripComments(
|
|
57668
|
+
code = stripComments(readFileSync20(file, "utf8"));
|
|
57508
57669
|
} catch {
|
|
57509
57670
|
continue;
|
|
57510
57671
|
}
|
|
@@ -57527,7 +57688,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
57527
57688
|
for (const file of files) {
|
|
57528
57689
|
let code;
|
|
57529
57690
|
try {
|
|
57530
|
-
code = stripComments(
|
|
57691
|
+
code = stripComments(readFileSync20(file, "utf8"));
|
|
57531
57692
|
} catch {
|
|
57532
57693
|
continue;
|
|
57533
57694
|
}
|
|
@@ -57642,7 +57803,7 @@ function variantScan(root, opts = {}) {
|
|
|
57642
57803
|
for (const file of files) {
|
|
57643
57804
|
let code;
|
|
57644
57805
|
try {
|
|
57645
|
-
code =
|
|
57806
|
+
code = readFileSync21(file, "utf8");
|
|
57646
57807
|
} catch {
|
|
57647
57808
|
continue;
|
|
57648
57809
|
}
|
|
@@ -58302,16 +58463,16 @@ function analyzeJwt(token, opts = {}) {
|
|
|
58302
58463
|
}
|
|
58303
58464
|
|
|
58304
58465
|
// src/enterprise-benchmark.ts
|
|
58305
|
-
import { existsSync as existsSync11, readFileSync as
|
|
58306
|
-
import { join as
|
|
58466
|
+
import { existsSync as existsSync11, readFileSync as readFileSync22, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
58467
|
+
import { join as join19, resolve } from "node:path";
|
|
58307
58468
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
58308
58469
|
var ENTERPRISE_FIXTURES = [
|
|
58309
|
-
{ id: "php-laravel", dir:
|
|
58310
|
-
{ id: "js-express", dir:
|
|
58311
|
-
{ id: "py-django", dir:
|
|
58312
|
-
{ id: "java-spring", dir:
|
|
58313
|
-
{ id: "wp-batch-route", dir:
|
|
58314
|
-
{ id: "php-complex", dir:
|
|
58470
|
+
{ id: "php-laravel", dir: join19(ROOT6, "php-laravel"), expected_vulns: 4 },
|
|
58471
|
+
{ id: "js-express", dir: join19(ROOT6, "js-express"), expected_vulns: 2 },
|
|
58472
|
+
{ id: "py-django", dir: join19(ROOT6, "py-django"), expected_vulns: 2 },
|
|
58473
|
+
{ id: "java-spring", dir: join19(ROOT6, "java-spring"), expected_vulns: 2 },
|
|
58474
|
+
{ id: "wp-batch-route", dir: join19(ROOT6, "wp-batch-route"), expected_vulns: 3 },
|
|
58475
|
+
{ id: "php-complex", dir: join19(ROOT6, "php-complex"), expected_vulns: 4 }
|
|
58315
58476
|
];
|
|
58316
58477
|
function sourceFiles(root) {
|
|
58317
58478
|
const out = [];
|
|
@@ -58323,7 +58484,7 @@ function sourceFiles(root) {
|
|
|
58323
58484
|
return;
|
|
58324
58485
|
}
|
|
58325
58486
|
for (const e of entries) {
|
|
58326
|
-
const p =
|
|
58487
|
+
const p = join19(dir, e);
|
|
58327
58488
|
let st;
|
|
58328
58489
|
try {
|
|
58329
58490
|
st = statSync3(p);
|
|
@@ -58344,7 +58505,7 @@ function detectFixture(dir) {
|
|
|
58344
58505
|
for (const f of sourceFiles(dir)) {
|
|
58345
58506
|
let code;
|
|
58346
58507
|
try {
|
|
58347
|
-
code =
|
|
58508
|
+
code = readFileSync22(f, "utf8");
|
|
58348
58509
|
} catch {
|
|
58349
58510
|
continue;
|
|
58350
58511
|
}
|
|
@@ -58539,8 +58700,8 @@ function createServer() {
|
|
|
58539
58700
|
` + `- 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).
|
|
58540
58701
|
` + `- 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.
|
|
58541
58702
|
|
|
58542
|
-
` + "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). 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.
|
|
58543
|
-
` + "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) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). 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 (a secret field
|
|
58703
|
+
` + "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, and citable references, so a raw hit becomes a class-aware finding. 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. 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.
|
|
58704
|
+
` + "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) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). 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:
|
|
58544
58705
|
` + `- 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.
|
|
58545
58706
|
` + `- 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.
|
|
58546
58707
|
` + "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."
|
|
@@ -58895,6 +59056,37 @@ ${statusLine}` : statusLine },
|
|
|
58895
59056
|
content: [{ type: "text", text: JSON.stringify(listChains()) }]
|
|
58896
59057
|
};
|
|
58897
59058
|
});
|
|
59059
|
+
server.registerTool("pattern_lookup", {
|
|
59060
|
+
title: "Empirical pattern lookup",
|
|
59061
|
+
description: "GROUNDING: return the empirical grounding for a detector type / vuln class — its CWE, its real-world signature, the escalation chains it composes into (cross-ref chains.json), and citable references. Use to turn a raw detector hit into a grounded, class-aware finding. Pass 'list' to enumerate all grounded classes.",
|
|
59062
|
+
inputSchema: {
|
|
59063
|
+
id: string2().describe("Detector/vuln class id (sql_injection, ssrf, idor-like missing_authz, …), or 'list'")
|
|
59064
|
+
}
|
|
59065
|
+
}, async ({ id }) => {
|
|
59066
|
+
if (id === "list")
|
|
59067
|
+
return { content: [{ type: "text", text: JSON.stringify(listPatterns()) }] };
|
|
59068
|
+
const p = patternLookup(id);
|
|
59069
|
+
if (!p)
|
|
59070
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `no pattern for '${id}'`, known: listPatterns().map((x) => x.id) }) }] };
|
|
59071
|
+
return { content: [{ type: "text", text: JSON.stringify(p) }] };
|
|
59072
|
+
});
|
|
59073
|
+
server.registerTool("submission_gate", {
|
|
59074
|
+
title: "Submission gate (7 questions)",
|
|
59075
|
+
description: "GATE: run the deterministic 7-question checklist on a finding before it ships. Questions: reproducible? in-scope? real impact? no privileged assumption? verified live? evidence redacted? severity derived? One false = fail (kill the finding). Pass booleans you can honestly assert true; omit/leave false any you cannot.",
|
|
59076
|
+
inputSchema: {
|
|
59077
|
+
type: string2().optional().describe("Detector/vuln class id"),
|
|
59078
|
+
severity: string2().optional().describe("Finding severity"),
|
|
59079
|
+
reproducible: boolean2().optional().describe("Exact request/step list is copy-paste ready"),
|
|
59080
|
+
in_scope: boolean2().optional().describe("Vulnerable asset is in engagement scope"),
|
|
59081
|
+
real_impact: boolean2().optional().describe("Maps to an accepted-impact class"),
|
|
59082
|
+
no_privileged_assumption: boolean2().optional().describe("Works without unrealistic prerequisites"),
|
|
59083
|
+
verified_live: boolean2().optional().describe("Confirmed with marker + negative control"),
|
|
59084
|
+
evidence_redacted: boolean2().optional().describe("Evidence attached, secrets/PII redacted"),
|
|
59085
|
+
severity_derived: boolean2().optional().describe("Severity derived via CVSS/confidence")
|
|
59086
|
+
}
|
|
59087
|
+
}, async (args) => {
|
|
59088
|
+
return { content: [{ type: "text", text: JSON.stringify(submissionGate(args)) }] };
|
|
59089
|
+
});
|
|
58898
59090
|
server.registerTool("fofa_search", {
|
|
58899
59091
|
title: "FOFA asset search",
|
|
58900
59092
|
description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
|
|
@@ -59279,7 +59471,7 @@ ${statusLine}` : statusLine },
|
|
|
59279
59471
|
let src = code;
|
|
59280
59472
|
if (!src && path) {
|
|
59281
59473
|
try {
|
|
59282
|
-
src =
|
|
59474
|
+
src = readFileSync24(path, "utf8");
|
|
59283
59475
|
} catch {
|
|
59284
59476
|
return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
|
|
59285
59477
|
}
|
|
@@ -59897,7 +60089,7 @@ ${statusLine}` : statusLine },
|
|
|
59897
60089
|
description: "Route confusion (separate class from source→sink taint): detect attacker-controlled dispatch (call_user_func/forward/dispatch), dynamic method calls ($obj->$m()), dynamic include (LFI), batch/proxy forwarding without per-route auth re-check, and route matching without path normalization.",
|
|
59898
60090
|
inputSchema: { path: string2().describe("Source file path (PHP)") }
|
|
59899
60091
|
}, async ({ path }) => {
|
|
59900
|
-
const code =
|
|
60092
|
+
const code = readFileSync24(path, "utf8");
|
|
59901
60093
|
const findings = detectRouteConfusion(code, path);
|
|
59902
60094
|
return {
|
|
59903
60095
|
content: [{
|
|
@@ -59911,7 +60103,7 @@ ${statusLine}` : statusLine },
|
|
|
59911
60103
|
description: "Complex-bug detection beyond taint: deserialization→POP gadget chain (unserialize + magic method), type juggling (loose ==/!= vs hash/secret), mass assignment (extract/parse_str without a safe flag), prototype pollution (unsafe merge of request data), CRLF/header injection (user input into a header), path confusion (user input into a file path without canonicalization), SSRF (user-controlled URL with no host allowlist), XXE (user XML parsed without disabling entities), SSTI (user input into a template render with no sandbox), wrong-context sanitization (sanitize_text_field() into a SQL query), and missing authorization/nonce on WordPress entry points (wp_ajax_* / admin_post_* / register_rest_route without current_user_can, nonce verification, or permission_callback).",
|
|
59912
60104
|
inputSchema: { path: string2().describe("Source file path") }
|
|
59913
60105
|
}, async ({ path }) => {
|
|
59914
|
-
const code =
|
|
60106
|
+
const code = readFileSync24(path, "utf8");
|
|
59915
60107
|
const findings = detectComplexBugs(code, path);
|
|
59916
60108
|
return {
|
|
59917
60109
|
content: [{
|
|
@@ -59997,7 +60189,7 @@ ${statusLine}` : statusLine },
|
|
|
59997
60189
|
let lang = language;
|
|
59998
60190
|
if (path && !code) {
|
|
59999
60191
|
try {
|
|
60000
|
-
src =
|
|
60192
|
+
src = readFileSync24(path, "utf8");
|
|
60001
60193
|
} catch {
|
|
60002
60194
|
return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
|
|
60003
60195
|
}
|
|
@@ -60238,15 +60430,15 @@ async function serve() {
|
|
|
60238
60430
|
|
|
60239
60431
|
// src/index.ts
|
|
60240
60432
|
init_cli();
|
|
60241
|
-
import { readFileSync as
|
|
60242
|
-
import { join as
|
|
60243
|
-
import { fileURLToPath as
|
|
60244
|
-
var ROOT11 =
|
|
60433
|
+
import { readFileSync as readFileSync28, existsSync as existsSync14 } from "node:fs";
|
|
60434
|
+
import { join as join24 } from "node:path";
|
|
60435
|
+
import { fileURLToPath as fileURLToPath14 } from "node:url";
|
|
60436
|
+
var ROOT11 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
|
|
60245
60437
|
function readVersion() {
|
|
60246
60438
|
try {
|
|
60247
|
-
const p =
|
|
60439
|
+
const p = join24(ROOT11, "package.json");
|
|
60248
60440
|
if (existsSync14(p))
|
|
60249
|
-
return JSON.parse(
|
|
60441
|
+
return JSON.parse(readFileSync28(p, "utf8")).version ?? "1.0.0";
|
|
60250
60442
|
} catch {}
|
|
60251
60443
|
return "1.0.0";
|
|
60252
60444
|
}
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
{"id": "missing_authz", "cwe": "CWE-862", "name": "Missing Authorization"},
|
|
56
56
|
{"id": "missing_nonce", "cwe": "CWE-352", "name": "Cross-Site Request Forgery (Missing Nonce)"},
|
|
57
57
|
{"id": "priv_esc", "cwe": "CWE-269", "name": "Improper Privilege Management"},
|
|
58
|
-
{"id": "hardcoded_secret", "cwe": "CWE-798", "name": "Use of Hard-coded Credentials"}
|
|
58
|
+
{"id": "hardcoded_secret", "cwe": "CWE-798", "name": "Use of Hard-coded Credentials"},
|
|
59
|
+
{"id": "type_juggling", "cwe": "CWE-697", "name": "Incorrect Comparison"},
|
|
60
|
+
{"id": "path_confusion", "cwe": "CWE-22", "name": "Improper Limitation of a Pathname to a Restricted Directory (Path Traversal)"},
|
|
61
|
+
{"id": "wrong_sanitizer", "cwe": "CWE-20", "name": "Improper Input Validation"}
|
|
59
62
|
]
|
|
60
63
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "blitzstrike-patterns-v1",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"_comment": "Empirical grounding layer. Every detector type is traced to its real-world bug class (CWE), its real-world signature, the chains it composes into (cross-ref chains.json), and a citable reference. Detection is derived from this data, never hardcoded. References are public standards (CWE/OWASP) and disclosed-report classes — no vendor/product framing.",
|
|
5
|
+
"patterns": [
|
|
6
|
+
{
|
|
7
|
+
"id": "sql_injection",
|
|
8
|
+
"name": "SQL Injection",
|
|
9
|
+
"cwe": "CWE-89",
|
|
10
|
+
"real_world": "User input is interpolated into a SQL query by string concatenation instead of parameterization. The single most-reported web class; real disclosures range from boolean/UNION extraction to stacked queries for admin takeover.",
|
|
11
|
+
"detection_signatures": ["raw string-interpolated query into a SQL sink", "no prepare()/parameterization", "cross-file: concatenated variable with no visible source"],
|
|
12
|
+
"chain_templates": ["sqli_to_rce", "sqli_to_hash_dump_and_crack", "sql_injection_to_rce", "second_order_sqli"],
|
|
13
|
+
"references": ["CWE-89", "OWASP A03:2021 Injection"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "ssrf",
|
|
17
|
+
"name": "Server-Side Request Forgery",
|
|
18
|
+
"cwe": "CWE-918",
|
|
19
|
+
"real_world": "A server fetches an attacker-supplied URL with no host allowlist, letting it reach cloud metadata endpoints, internal services, or the loopback interface.",
|
|
20
|
+
"detection_signatures": ["server-side fetch of a request-controlled URL", "no host/allowlist validation"],
|
|
21
|
+
"chain_templates": ["ssrf_cloud_metadata", "ssrf_to_redis_rce", "blind_ssrf_to_oob_exfil"],
|
|
22
|
+
"references": ["CWE-918", "OWASP A10:2021 SSRF"]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "ssti",
|
|
26
|
+
"name": "Server-Side Template Injection",
|
|
27
|
+
"cwe": "CWE-1336",
|
|
28
|
+
"real_world": "User input reaches a template engine (Jinja2, Twig, Freemarker, ERB, …) that evaluates expressions, yielding code execution via template syntax like {{7*7}}.",
|
|
29
|
+
"detection_signatures": ["template render of request input", "expression evaluation in a template engine"],
|
|
30
|
+
"chain_templates": ["ssti_template_injection_rce", "ssti_to_full_rce"],
|
|
31
|
+
"references": ["CWE-1336", "OWASP A03:2021 Injection"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "xxe",
|
|
35
|
+
"name": "XML External Entity Injection",
|
|
36
|
+
"cwe": "CWE-611",
|
|
37
|
+
"real_world": "An XML parser resolves external entities, letting an attacker read local files or hit internal URLs via DOCTYPE entity definitions.",
|
|
38
|
+
"detection_signatures": ["XML parse without disabling external entities", "DOCTYPE/ENTITY processing"],
|
|
39
|
+
"chain_templates": ["xxe_to_ssrf_and_file_read"],
|
|
40
|
+
"references": ["CWE-611", "OWASP A03:2021 Injection"]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "deserialization",
|
|
44
|
+
"name": "Insecure Deserialization",
|
|
45
|
+
"cwe": "CWE-502",
|
|
46
|
+
"real_world": "Attacker-controlled serialized data is deserialized and escalated through a magic-method gadget chain (__destruct/__wakeup/__toString) into code execution or object injection.",
|
|
47
|
+
"detection_signatures": ["unserialize()/pickle.loads/yaml.load of request data", "a magic-method gadget in scope"],
|
|
48
|
+
"chain_templates": ["deserialization_pop_chain", "deserialization_to_rce", "appkey_leak_deserialization_rce"],
|
|
49
|
+
"references": ["CWE-502", "OWASP A08:2021 Software and Data Integrity Failures"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "type_juggling",
|
|
53
|
+
"name": "PHP Type Juggling (Loose Comparison)",
|
|
54
|
+
"cwe": "CWE-697",
|
|
55
|
+
"real_world": "A loose comparison (==) between user input and a hash/secret collapses via the 0e magic-hash or numeric-string coercion, bypassing authentication.",
|
|
56
|
+
"detection_signatures": ["loose comparison == of user input against a secret/hash", "0e-prefixed or numeric-string coercion"],
|
|
57
|
+
"chain_templates": ["php_type_juggling_auth_bypass"],
|
|
58
|
+
"references": ["CWE-697", "OWASP A07:2021 Identification and Authentication Failures"]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "mass_assignment",
|
|
62
|
+
"name": "Mass Assignment",
|
|
63
|
+
"cwe": "CWE-915",
|
|
64
|
+
"real_world": "Request data is bound to model fields without an allowlist (extract()/parse_str/framework mass-assign), letting an attacker set privileged fields like role or is_admin.",
|
|
65
|
+
"detection_signatures": ["extract()/parse_str of request data without a safe flag", "model binding without an allowlist"],
|
|
66
|
+
"chain_templates": ["mass_assignment_to_admin_takeover", "extract_variable_injection_lfi"],
|
|
67
|
+
"references": ["CWE-915", "OWASP API6:2023 Unrestricted Access to Sensitive Business Flows"]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "prototype_pollution",
|
|
71
|
+
"name": "Prototype Pollution",
|
|
72
|
+
"cwe": "CWE-1321",
|
|
73
|
+
"real_world": "A deep merge of request data into an object pollutes Object.prototype, corrupting logic and escalating to code execution in gadget-heavy runtimes.",
|
|
74
|
+
"detection_signatures": ["deep merge/extend of request data into objects", "__proto__ / constructor.prototype assignment"],
|
|
75
|
+
"chain_templates": ["prototype_pollution_to_rce"],
|
|
76
|
+
"references": ["CWE-1321"]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "crlf_injection",
|
|
80
|
+
"name": "CRLF Injection / Header Injection",
|
|
81
|
+
"cwe": "CWE-93",
|
|
82
|
+
"real_world": "Request-controlled newlines are written into an HTTP header, enabling response splitting, cookie injection, or cache poisoning.",
|
|
83
|
+
"detection_signatures": ["request input reaches a header without CR/LF stripping"],
|
|
84
|
+
"chain_templates": ["cache_poisoning_xss", "cache_poison_to_stored_xss"],
|
|
85
|
+
"references": ["CWE-93", "OWASP A03:2021 Injection"]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "path_confusion",
|
|
89
|
+
"name": "Path Traversal / Path Confusion",
|
|
90
|
+
"cwe": "CWE-22",
|
|
91
|
+
"real_world": "A request-controlled path is normalized or resolved without confinement (../, %2e%2e, absolute paths), reading or writing files outside the intended directory.",
|
|
92
|
+
"detection_signatures": ["request input reaches a file path without confinement"],
|
|
93
|
+
"chain_templates": ["path_traversal_file_write", "path_traversal_to_credential_theft", "lfi_log_poison_rce", "lfi_to_rce_via_log_poisoning"],
|
|
94
|
+
"references": ["CWE-22", "OWASP A01:2021 Broken Access Control"]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "wrong_sanitizer",
|
|
98
|
+
"name": "Wrong-Context Sanitization",
|
|
99
|
+
"cwe": "CWE-20",
|
|
100
|
+
"real_world": "A value is sanitized for one context (e.g. HTML) but reaches a sink that needs a different context (e.g. SQL), so the sanitizer provides no real protection.",
|
|
101
|
+
"detection_signatures": ["a sanitizer/validator for context X feeding a sink that requires context Y"],
|
|
102
|
+
"chain_templates": ["second_order_sqli"],
|
|
103
|
+
"references": ["CWE-20", "OWASP A03:2021 Injection"]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "missing_authz",
|
|
107
|
+
"name": "Missing Authorization (Broken Access Control)",
|
|
108
|
+
"cwe": "CWE-862",
|
|
109
|
+
"real_world": "A state-changing handler lacks a capability/ownership check, letting an unauthenticated or low-privilege caller perform an admin action.",
|
|
110
|
+
"detection_signatures": ["state-changing operation with no current_user_can/is_user_logged_in check"],
|
|
111
|
+
"chain_templates": ["idor_to_admin_takeover", "mass_assignment_to_admin_takeover", "idor_object_reference"],
|
|
112
|
+
"references": ["CWE-862", "OWASP A01:2021 Broken Access Control"]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "missing_nonce",
|
|
116
|
+
"name": "Missing Nonce (CSRF)",
|
|
117
|
+
"cwe": "CWE-352",
|
|
118
|
+
"real_world": "A state-changing action lacks an anti-CSRF token, letting a victim's browser perform the action on their behalf via a forged request.",
|
|
119
|
+
"detection_signatures": ["state-changing operation with no nonce/referer verification"],
|
|
120
|
+
"chain_templates": ["nonce_harvest_auth_bypass"],
|
|
121
|
+
"references": ["CWE-352", "OWASP A01:2021 Broken Access Control"]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "file_upload",
|
|
125
|
+
"name": "Unrestricted File Upload",
|
|
126
|
+
"cwe": "CWE-434",
|
|
127
|
+
"real_world": "A file is uploaded with an attacker-controlled name/extension and no server-side filetype whitelist, landing web-reachable for code execution.",
|
|
128
|
+
"detection_signatures": ["move_uploaded_file/wp_upload_bits with attacker-controlled name", "no extension/mime whitelist", "uploadAllow=all connector config"],
|
|
129
|
+
"chain_templates": ["upload_polyglot_rce", "file_upload_to_rce", "split_controller_upload_bypass"],
|
|
130
|
+
"references": ["CWE-434", "OWASP A04:2021 Insecure Design"]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "priv_esc",
|
|
134
|
+
"name": "Privilege Escalation",
|
|
135
|
+
"cwe": "CWE-269",
|
|
136
|
+
"real_world": "A role/capability/user change is fed by request input and reachable from a public entry point with no capability check, letting a user self-assign admin.",
|
|
137
|
+
"detection_signatures": ["set_role/add_role/add_cap of request input", "cross-file: sink reachable from public hook with no capability check"],
|
|
138
|
+
"chain_templates": ["mass_assignment_to_admin_takeover", "idor_to_admin_takeover"],
|
|
139
|
+
"references": ["CWE-269", "OWASP A01:2021 Broken Access Control"]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"id": "hardcoded_secret",
|
|
143
|
+
"name": "Hardcoded Credentials",
|
|
144
|
+
"cwe": "CWE-798",
|
|
145
|
+
"real_world": "A secret (API key, database password, access token, private key) is committed in source, exposing it to anyone with repo access. Real disclosures regularly turn a leaked key into account takeover or cloud compromise.",
|
|
146
|
+
"detection_signatures": ["secret field assigned a literal", "known secret format (JWT/AWS/GitHub/Slack/…) in source", "high-entropy token under a non-obvious name"],
|
|
147
|
+
"chain_templates": ["api_key_leak_to_account_takeover", "appkey_leak_deserialization_rce", "default_creds_to_internal_pivot"],
|
|
148
|
+
"references": ["CWE-798", "OWASP A02:2021 Cryptographic Failures"]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.58",
|
|
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": {
|