blitzstrike 2.4.58 → 2.4.60
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 +30 -0
- package/dist/index.js +355 -108
- package/intelligence/cwe_map.json +5 -1
- package/intelligence/patterns.json +36 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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.60] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added — Modern attack-surface detectors + verification corpus
|
|
11
|
+
|
|
12
|
+
- Four new detector classes (the "next-gen" 2026 surface beyond classic web):
|
|
13
|
+
`llm_injection` (request input reaches an LLM call — OWASP LLM01), `graphql_exposure`
|
|
14
|
+
(introspection/playground left on), `oauth_misconfig` (redirect_uri fed by request
|
|
15
|
+
input with no allowlist), and `grpc_reflection` (reflection enabled). Each carries
|
|
16
|
+
CWE mapping + empirical pattern grounding.
|
|
17
|
+
- New `bench/verification/` — an auditable verification corpus: every detector that
|
|
18
|
+
was verified against a real vulnerable target gets a writeup (detector → ground
|
|
19
|
+
truth → result → evidence). "Battle-tested" is now a claim with an audit trail,
|
|
20
|
+
not an assertion.
|
|
21
|
+
|
|
22
|
+
## [2.4.59] — 2026-09-14
|
|
23
|
+
|
|
24
|
+
### Added — Severity calibration + engagement scaffold + data health
|
|
25
|
+
|
|
26
|
+
- New `severity_calibrate` MCP tool (src/severity.ts): maps a detector type to a
|
|
27
|
+
platform-neutral priority tier (P1–P5) + a VRT-style category + rationale, with
|
|
28
|
+
an optional CVSS cross-reference — a finding is reported at the severity a triager
|
|
29
|
+
would assign, not the detector's raw default.
|
|
30
|
+
- New `engagement_scaffold` MCP tool: opens an engagement and emits a deterministic
|
|
31
|
+
folder layout (scope.md, findings/, evidence/, reports/, notes/) + a pre-filled
|
|
32
|
+
scope.md template, so a hunt starts with structure instead of improvisation.
|
|
33
|
+
- New `data_refresh` MCP tool (src/datarefresh.ts): re-loads every data layer
|
|
34
|
+
(chains, patterns, CWE map) and verifies cross-references, surfacing stale/broken
|
|
35
|
+
data instead of silently producing bad grounding.
|
|
36
|
+
- System prompt updated to drive the full grounding→gate→calibrate→scaffold flow.
|
|
37
|
+
|
|
8
38
|
## [2.4.58] — 2026-09-14
|
|
9
39
|
|
|
10
40
|
### Added — Empirical grounding v1 (pattern library + submission gate)
|
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 readFileSync24, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
|
|
15494
|
+
import { join as join21 } from "node:path";
|
|
15495
15495
|
import { homedir as homedir10 } from "node:os";
|
|
15496
|
-
import { fileURLToPath as
|
|
15496
|
+
import { fileURLToPath as fileURLToPath11 } 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(join21(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 = join21(DATA_ROOT2, "payloads");
|
|
15526
|
+
const templatesPath = join21(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 = join21(ROOT7, "payloads");
|
|
15537
|
+
const localTemplates = join21(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 = join21(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(join21(tmp, "payloads"), payloadsPath);
|
|
15559
15559
|
payloadsSynced = true;
|
|
15560
15560
|
}
|
|
15561
15561
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
15562
|
-
copyDir(
|
|
15562
|
+
copyDir(join21(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 = join21(src, e.name);
|
|
15585
|
+
const d = join21(dst, e.name);
|
|
15586
15586
|
if (e.isDirectory())
|
|
15587
15587
|
manualCopy(s, d);
|
|
15588
15588
|
else
|
|
15589
|
-
writeFileSync8(d,
|
|
15589
|
+
writeFileSync8(d, readFileSync24(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 = join21(fileURLToPath11(new URL(".", import.meta.url)), "..");
|
|
15595
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join21(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 readFileSync26, existsSync as existsSync12 } from "node:fs";
|
|
15641
|
+
import { join as join22 } from "node:path";
|
|
15642
|
+
import { fileURLToPath as fileURLToPath12 } 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 = readFileSync26(join22(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(join22(ROOT8, "package-lock.json")) || existsSync12(join22(ROOT8, "bun.lock")) || existsSync12(join22(ROOT8, "bun.lockb"));
|
|
15681
15681
|
}
|
|
15682
15682
|
var ROOT8;
|
|
15683
15683
|
var init_release = __esm(() => {
|
|
15684
15684
|
init_version();
|
|
15685
|
-
ROOT8 =
|
|
15685
|
+
ROOT8 = join22(fileURLToPath12(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 readFileSync27 } from "node:fs";
|
|
15690
|
+
import { join as join23 } from "node:path";
|
|
15691
|
+
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
15692
15692
|
function checkDependencies() {
|
|
15693
15693
|
let pkg = {};
|
|
15694
15694
|
try {
|
|
15695
|
-
pkg = JSON.parse(
|
|
15695
|
+
pkg = JSON.parse(readFileSync27(join23(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 = join23(fileURLToPath13(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 readFileSync28, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
|
|
15723
|
+
import { join as join24, dirname as dirname4 } from "node:path";
|
|
15724
15724
|
import { homedir as homedir11 } from "node:os";
|
|
15725
|
-
import { fileURLToPath as
|
|
15725
|
+
import { fileURLToPath as fileURLToPath14 } 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(join24(ROOT10, "chains.json")) && existsSync13(join24(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 = join24(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 = join24(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(readFileSync28(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 = dirname4(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 = dirname4(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 = join24(ROOT10, "opencode-agents");
|
|
15867
|
+
const dstDir = join24(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 = join24(srcDir, f);
|
|
15874
|
+
const dst = join24(dstDir, f);
|
|
15875
15875
|
if (existsSync13(src)) {
|
|
15876
|
-
writeFileSync9(dst,
|
|
15876
|
+
writeFileSync9(dst, readFileSync28(src, "utf8"));
|
|
15877
15877
|
}
|
|
15878
15878
|
}
|
|
15879
15879
|
}
|
|
15880
15880
|
function codexWrite(p) {
|
|
15881
15881
|
return (command, args) => {
|
|
15882
|
-
const dir =
|
|
15882
|
+
const dir = dirname4(p);
|
|
15883
15883
|
if (!existsSync13(dir))
|
|
15884
15884
|
mkdirSync10(dir, { recursive: true });
|
|
15885
|
-
let existing = existsSync13(p) ?
|
|
15885
|
+
let existing = existsSync13(p) ? readFileSync28(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 = dirname4(p);
|
|
15902
15902
|
if (!existsSync13(dir))
|
|
15903
15903
|
mkdirSync10(dir, { recursive: true });
|
|
15904
|
-
let existing = existsSync13(p) ?
|
|
15904
|
+
let existing = existsSync13(p) ? readFileSync28(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 = join24(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 = join24(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 = join24(home, ".cursor", "mcp.json");
|
|
15946
15946
|
agents.push({
|
|
15947
15947
|
name: "Cursor",
|
|
15948
15948
|
detectPath: cursor,
|
|
15949
|
-
installed: () => existsSync13(
|
|
15949
|
+
installed: () => existsSync13(join24(home, ".cursor")) || which("cursor"),
|
|
15950
15950
|
write: jsonMcpServersWrite(cursor)
|
|
15951
15951
|
});
|
|
15952
|
-
const opencode =
|
|
15952
|
+
const opencode = join24(home, ".config", "opencode", "opencode.jsonc");
|
|
15953
15953
|
agents.push({
|
|
15954
15954
|
name: "OpenCode",
|
|
15955
15955
|
detectPath: opencode,
|
|
15956
|
-
installed: () => existsSync13(
|
|
15956
|
+
installed: () => existsSync13(join24(home, ".config", "opencode")) || which("opencode"),
|
|
15957
15957
|
write: opencodeWrite(opencode)
|
|
15958
15958
|
});
|
|
15959
|
-
const codex =
|
|
15959
|
+
const codex = join24(home, ".codex", "config.toml");
|
|
15960
15960
|
agents.push({
|
|
15961
15961
|
name: "Codex",
|
|
15962
15962
|
detectPath: codex,
|
|
15963
|
-
installed: () => existsSync13(
|
|
15963
|
+
installed: () => existsSync13(join24(home, ".codex")) || which("codex"),
|
|
15964
15964
|
write: codexWrite(codex)
|
|
15965
15965
|
});
|
|
15966
|
-
const hermes =
|
|
15966
|
+
const hermes = join24(home, ".hermes", "config.yaml");
|
|
15967
15967
|
agents.push({
|
|
15968
15968
|
name: "Hermes",
|
|
15969
15969
|
detectPath: hermes,
|
|
15970
|
-
installed: () => existsSync13(
|
|
15970
|
+
installed: () => existsSync13(join24(home, ".hermes")) || which("hermes"),
|
|
15971
15971
|
write: hermesWrite(hermes)
|
|
15972
15972
|
});
|
|
15973
|
-
const gemini =
|
|
15973
|
+
const gemini = join24(home, ".gemini", "settings.json");
|
|
15974
15974
|
agents.push({
|
|
15975
15975
|
name: "Gemini CLI",
|
|
15976
15976
|
detectPath: gemini,
|
|
15977
|
-
installed: () => existsSync13(
|
|
15977
|
+
installed: () => existsSync13(join24(home, ".gemini")) || which("gemini"),
|
|
15978
15978
|
write: jsonMcpServersWrite(gemini)
|
|
15979
15979
|
});
|
|
15980
|
-
const windsurf =
|
|
15980
|
+
const windsurf = join24(home, ".codeium", "windsurf", "mcp_config.json");
|
|
15981
15981
|
agents.push({
|
|
15982
15982
|
name: "Windsurf",
|
|
15983
15983
|
detectPath: windsurf,
|
|
15984
|
-
installed: () => existsSync13(
|
|
15984
|
+
installed: () => existsSync13(join24(home, ".codeium")) || which("windsurf"),
|
|
15985
15985
|
write: jsonMcpServersWrite(windsurf)
|
|
15986
15986
|
});
|
|
15987
|
-
const copilot =
|
|
15987
|
+
const copilot = join24(home, ".copilot", "mcp.json");
|
|
15988
15988
|
agents.push({
|
|
15989
15989
|
name: "Copilot",
|
|
15990
15990
|
detectPath: copilot,
|
|
15991
|
-
installed: () => existsSync13(
|
|
15991
|
+
installed: () => existsSync13(join24(home, ".copilot")),
|
|
15992
15992
|
write: jsonMcpServersWrite(copilot)
|
|
15993
15993
|
});
|
|
15994
|
-
const cline =
|
|
15994
|
+
const cline = join24(home, ".cline", "mcp_settings.json");
|
|
15995
15995
|
agents.push({
|
|
15996
15996
|
name: "Cline",
|
|
15997
15997
|
detectPath: cline,
|
|
15998
|
-
installed: () => existsSync13(
|
|
15998
|
+
installed: () => existsSync13(join24(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 = join24(fileURLToPath14(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 readFileSync25 } from "node:fs";
|
|
30755
30755
|
|
|
30756
30756
|
// src/recon.ts
|
|
30757
30757
|
var TIMEOUT_MS = 25000;
|
|
@@ -35526,6 +35526,41 @@ function engagementStatus() {
|
|
|
35526
35526
|
next_phase: next
|
|
35527
35527
|
};
|
|
35528
35528
|
}
|
|
35529
|
+
function scaffoldEngagement(target, scope = [], out_of_scope = []) {
|
|
35530
|
+
const state = startEngagement(target, "live");
|
|
35531
|
+
const scopeLines = (scope.length ? scope : [target]).map((s) => `- ${s}`).join(`
|
|
35532
|
+
`);
|
|
35533
|
+
const oosLines = (out_of_scope.length ? out_of_scope : ["(none declared)"]).map((s) => `- ${s}`).join(`
|
|
35534
|
+
`);
|
|
35535
|
+
const scopeMd = [
|
|
35536
|
+
`# Engagement scope`,
|
|
35537
|
+
``,
|
|
35538
|
+
`**Target:** ${target}`,
|
|
35539
|
+
``,
|
|
35540
|
+
`## In scope`,
|
|
35541
|
+
scopeLines,
|
|
35542
|
+
``,
|
|
35543
|
+
`## Out of scope`,
|
|
35544
|
+
oosLines,
|
|
35545
|
+
``,
|
|
35546
|
+
`## Accepted impact classes`,
|
|
35547
|
+
`- (paste the program's vulnerability-type / accepted-impact list here)`,
|
|
35548
|
+
``,
|
|
35549
|
+
`## Testing rules`,
|
|
35550
|
+
`- (paste rate limits, prohibited areas, and any special rules here)`,
|
|
35551
|
+
``
|
|
35552
|
+
].join(`
|
|
35553
|
+
`);
|
|
35554
|
+
return {
|
|
35555
|
+
id: state.id,
|
|
35556
|
+
target,
|
|
35557
|
+
scope,
|
|
35558
|
+
out_of_scope,
|
|
35559
|
+
structure: ["scope.md", "findings/", "evidence/", "reports/", "notes/"],
|
|
35560
|
+
scope_md: scopeMd,
|
|
35561
|
+
state
|
|
35562
|
+
};
|
|
35563
|
+
}
|
|
35529
35564
|
|
|
35530
35565
|
// src/incremental.ts
|
|
35531
35566
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
@@ -56817,6 +56852,10 @@ function detectComplexBugs(code, file) {
|
|
|
56817
56852
|
detectUnrestrictedUpload(code, file, out);
|
|
56818
56853
|
detectSqlInjection(code, file, out);
|
|
56819
56854
|
detectHardcodedSecret(code, file, out);
|
|
56855
|
+
detectLlmInjection(code, file, out);
|
|
56856
|
+
detectGraphqlExposure(code, file, out);
|
|
56857
|
+
detectOauthMisconfig(code, file, out);
|
|
56858
|
+
detectGrpcReflection(code, file, out);
|
|
56820
56859
|
out.push(...detectWrongSanitizer(code, file));
|
|
56821
56860
|
out.push(...detectMissingAuthz(code, file));
|
|
56822
56861
|
const seen = new Set;
|
|
@@ -57139,6 +57178,91 @@ function detectHardcodedSecret(code, file, out) {
|
|
|
57139
57178
|
}
|
|
57140
57179
|
}
|
|
57141
57180
|
}
|
|
57181
|
+
function detectLlmInjection(code, file, out) {
|
|
57182
|
+
const ls = lines2(code);
|
|
57183
|
+
const clean = stripCommentsOnly(code);
|
|
57184
|
+
const llmCallRe = /\b(?:openai|anthropic|langchain|llama_index)\.[A-Za-z_.]+\s*\(|\b(?:ChatCompletion|completion|messages)\.create\s*\(|\bllm\.invoke\s*\(|\.chat\.completions\.create\s*\(/g;
|
|
57185
|
+
let m;
|
|
57186
|
+
while ((m = llmCallRe.exec(clean)) !== null) {
|
|
57187
|
+
const ln = lineNo2(code, m.index);
|
|
57188
|
+
const scope = ls.slice(Math.max(0, ln - 6), ln + 3).join(`
|
|
57189
|
+
`);
|
|
57190
|
+
if (!SOURCE_TOKENS3.test(scope))
|
|
57191
|
+
continue;
|
|
57192
|
+
out.push({
|
|
57193
|
+
file,
|
|
57194
|
+
line: ln,
|
|
57195
|
+
type: "llm_injection",
|
|
57196
|
+
category: "LLM prompt injection surface",
|
|
57197
|
+
severity: "medium",
|
|
57198
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
57199
|
+
detail: "Request input reaches an LLM call (chat/completion/invoke) with no visible prompt-injection boundary — untrusted text may override instructions or exfiltrate context (OWASP LLM01 Prompt Injection). Verify the prompt is sandboxed and output is not used for privileged actions."
|
|
57200
|
+
});
|
|
57201
|
+
}
|
|
57202
|
+
}
|
|
57203
|
+
function detectGraphqlExposure(code, file, out) {
|
|
57204
|
+
const ls = lines2(code);
|
|
57205
|
+
const clean = stripCommentsOnly(code);
|
|
57206
|
+
const re = /\b(introspection|playground|graphiql)\s*[:=]\s*(true|1)\b/gi;
|
|
57207
|
+
let m;
|
|
57208
|
+
while ((m = re.exec(clean)) !== null) {
|
|
57209
|
+
const ln = lineNo2(code, m.index);
|
|
57210
|
+
out.push({
|
|
57211
|
+
file,
|
|
57212
|
+
line: ln,
|
|
57213
|
+
type: "graphql_exposure",
|
|
57214
|
+
category: "GraphQL introspection/playground exposed",
|
|
57215
|
+
severity: "medium",
|
|
57216
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
57217
|
+
detail: `GraphQL ${m[1].toLowerCase()} is enabled (${m[2]}) in a configuration that appears production-reachable — introspection exposes the full schema/fields, and a playground enables interactive querying (CWE-200 Information Exposure). Disable in production.`
|
|
57218
|
+
});
|
|
57219
|
+
}
|
|
57220
|
+
}
|
|
57221
|
+
function detectOauthMisconfig(code, file, out) {
|
|
57222
|
+
const ls = lines2(code);
|
|
57223
|
+
const clean = stripCommentsOnly(code);
|
|
57224
|
+
const re = /\b(redirect_uri|redirectUrl|return_url|returnUrl|callback_url)\s*[:=]\s*/gi;
|
|
57225
|
+
let m;
|
|
57226
|
+
while ((m = re.exec(clean)) !== null) {
|
|
57227
|
+
const ln = lineNo2(code, m.index);
|
|
57228
|
+
const rest = clean.slice(m.index + m[0].length);
|
|
57229
|
+
const lineEnd = rest.search(/\r?\n/);
|
|
57230
|
+
const line = lineEnd === -1 ? rest : rest.slice(0, lineEnd);
|
|
57231
|
+
if (!/(request|params|query|\$_(?:GET|POST|REQUEST)|req\.)/i.test(line))
|
|
57232
|
+
continue;
|
|
57233
|
+
const scope = ls.slice(Math.max(0, ln - 6), ln + 3).join(`
|
|
57234
|
+
`);
|
|
57235
|
+
if (/(allowlist|whitelist|allowed_|in_array|startsWith|===\s*['"])/.test(scope))
|
|
57236
|
+
continue;
|
|
57237
|
+
out.push({
|
|
57238
|
+
file,
|
|
57239
|
+
line: ln,
|
|
57240
|
+
type: "oauth_misconfig",
|
|
57241
|
+
category: "OAuth redirect_uri misconfiguration",
|
|
57242
|
+
severity: "high",
|
|
57243
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
57244
|
+
detail: "An OAuth redirect_uri/callback is fed by request input with no allowlist validation — an attacker can redirect the authorization code/token to an attacker-controlled endpoint (CWE-601 Open Redirect → token theft). Validate the redirect target against a static allowlist."
|
|
57245
|
+
});
|
|
57246
|
+
}
|
|
57247
|
+
}
|
|
57248
|
+
function detectGrpcReflection(code, file, out) {
|
|
57249
|
+
const ls = lines2(code);
|
|
57250
|
+
const clean = stripCommentsOnly(code);
|
|
57251
|
+
const re = /\b(enable_server_reflection|RegisterReflectionService|reflection\.enable|grpc_reflection|reflection\.Register)\b/gi;
|
|
57252
|
+
let m;
|
|
57253
|
+
while ((m = re.exec(clean)) !== null) {
|
|
57254
|
+
const ln = lineNo2(code, m.index);
|
|
57255
|
+
out.push({
|
|
57256
|
+
file,
|
|
57257
|
+
line: ln,
|
|
57258
|
+
type: "grpc_reflection",
|
|
57259
|
+
category: "gRPC reflection exposed",
|
|
57260
|
+
severity: "low",
|
|
57261
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
57262
|
+
detail: "gRPC server reflection is enabled — it enumerates every service/method to any caller, expanding the attack surface and leaking the API contract (CWE-200 Information Exposure). Disable reflection in production."
|
|
57263
|
+
});
|
|
57264
|
+
}
|
|
57265
|
+
}
|
|
57142
57266
|
|
|
57143
57267
|
// src/guidance.ts
|
|
57144
57268
|
var SINK_DIRECTIVES = {
|
|
@@ -57266,18 +57390,110 @@ function submissionGate(input) {
|
|
|
57266
57390
|
};
|
|
57267
57391
|
}
|
|
57268
57392
|
|
|
57269
|
-
// src/
|
|
57270
|
-
|
|
57271
|
-
|
|
57393
|
+
// src/severity.ts
|
|
57394
|
+
var VRT_MAP = {
|
|
57395
|
+
sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
|
|
57396
|
+
ssti: { vrt: "injection > server_side_template_injection", priority: "P1", severity: "critical", rationale: "SSTI evaluates expressions server-side → code execution." },
|
|
57397
|
+
deserialization: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Deserialization escalated via a gadget chain → code execution." },
|
|
57398
|
+
xxe: { vrt: "injection > xml_external_entity", priority: "P1", severity: "critical", rationale: "XXE reads local files or reaches internal services." },
|
|
57399
|
+
file_upload: { vrt: "insecure_design > unrestricted_file_upload", priority: "P1", severity: "critical", rationale: "A web-reachable uploaded file of an executable type → code execution." },
|
|
57400
|
+
priv_esc: { vrt: "broken_access_control > privilege_escalation", priority: "P1", severity: "critical", rationale: "A user self-assigns an elevated role → full account/tenant takeover." },
|
|
57401
|
+
path_confusion: { vrt: "broken_access_control > path_traversal", priority: "P2", severity: "high", rationale: "Path traversal reads files or writes outside the intended directory; P1 if it reaches an RCE chain." },
|
|
57402
|
+
ssrf: { vrt: "insecure_design > server_side_request_forgery", priority: "P2", severity: "high", rationale: "SSRF reaches internal services; P1 when it hits cloud metadata and exfiltrates credentials." },
|
|
57403
|
+
mass_assignment: { vrt: "broken_access_control > mass_assignment", priority: "P2", severity: "high", rationale: "Attacker sets a privileged field (role/is_admin) → privilege escalation." },
|
|
57404
|
+
missing_authz: { vrt: "broken_access_control > missing_authorization", priority: "P2", severity: "high", rationale: "A state-changing action lacks a capability/ownership check." },
|
|
57405
|
+
type_juggling: { vrt: "authentication > authentication_bypass", priority: "P2", severity: "high", rationale: "Loose comparison collapses a hash/secret → authentication bypass." },
|
|
57406
|
+
hardcoded_secret: { vrt: "sensitive_data_exposure > hardcoded_credentials", priority: "P2", severity: "high", rationale: "A committed credential is usable by anyone with repo access → account/cloud takeover." },
|
|
57407
|
+
prototype_pollution: { vrt: "injection > prototype_pollution", priority: "P2", severity: "high", rationale: "Object prototype corruption can escalate to code execution in gadget-heavy runtimes." },
|
|
57408
|
+
crlf_injection: { vrt: "injection > crlf_injection", priority: "P3", severity: "medium", rationale: "Header injection enables cookie poisoning or cache poisoning." },
|
|
57409
|
+
missing_nonce: { vrt: "broken_access_control > cross_site_request_forgery", priority: "P3", severity: "medium", rationale: "A state-changing action lacks an anti-CSRF token." },
|
|
57410
|
+
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." }
|
|
57411
|
+
};
|
|
57412
|
+
function severityCalibrate(input) {
|
|
57413
|
+
const type = input.type?.toLowerCase() ?? "";
|
|
57414
|
+
const entry = VRT_MAP[type];
|
|
57415
|
+
if (input.informational) {
|
|
57416
|
+
return {
|
|
57417
|
+
type,
|
|
57418
|
+
vrt_category: "informational",
|
|
57419
|
+
priority: "P5",
|
|
57420
|
+
severity: "informational",
|
|
57421
|
+
rationale: "Marked informational — excluded from the validity ratio.",
|
|
57422
|
+
...input.cvss != null ? { cvss_severity: cvssSeverity(input.cvss) } : {}
|
|
57423
|
+
};
|
|
57424
|
+
}
|
|
57425
|
+
if (!entry) {
|
|
57426
|
+
return {
|
|
57427
|
+
type,
|
|
57428
|
+
vrt_category: "unclassified",
|
|
57429
|
+
priority: "P4",
|
|
57430
|
+
severity: "low",
|
|
57431
|
+
rationale: `No VRT mapping for '${type}'; defaulting to low until classified.`,
|
|
57432
|
+
...input.cvss != null ? { cvss_severity: cvssSeverity(input.cvss) } : {}
|
|
57433
|
+
};
|
|
57434
|
+
}
|
|
57435
|
+
return {
|
|
57436
|
+
type,
|
|
57437
|
+
vrt_category: entry.vrt,
|
|
57438
|
+
priority: entry.priority,
|
|
57439
|
+
severity: entry.severity,
|
|
57440
|
+
rationale: entry.rationale,
|
|
57441
|
+
...input.cvss != null ? { cvss_severity: cvssSeverity(input.cvss) } : {}
|
|
57442
|
+
};
|
|
57443
|
+
}
|
|
57444
|
+
function listSeverityMap() {
|
|
57445
|
+
return Object.entries(VRT_MAP).map(([type, e]) => ({ type, priority: e.priority, vrt: e.vrt }));
|
|
57446
|
+
}
|
|
57447
|
+
|
|
57448
|
+
// src/datarefresh.ts
|
|
57449
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
57450
|
+
import { join as join17, dirname as dirname3 } from "node:path";
|
|
57272
57451
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
57452
|
+
var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
|
|
57453
|
+
function loadJson2(rel) {
|
|
57454
|
+
try {
|
|
57455
|
+
return JSON.parse(readFileSync18(join17(__dirname3, "..", rel), "utf8"));
|
|
57456
|
+
} catch {
|
|
57457
|
+
return null;
|
|
57458
|
+
}
|
|
57459
|
+
}
|
|
57460
|
+
function dataRefresh() {
|
|
57461
|
+
const chains = loadJson2("chains.json");
|
|
57462
|
+
const patterns = loadJson2("intelligence/patterns.json");
|
|
57463
|
+
const cwe = loadJson2("intelligence/cwe_map.json");
|
|
57464
|
+
const chainList = chains?.chains ?? [];
|
|
57465
|
+
const patternList = patterns?.patterns ?? [];
|
|
57466
|
+
const cweList = cwe?.mapping ?? [];
|
|
57467
|
+
const chainIds = new Set(chainList.map((c) => c.id));
|
|
57468
|
+
const cweIds = new Set(cweList.map((m) => m.id));
|
|
57469
|
+
const brokenChainRefs = patternList.flatMap((p) => (p.chain_templates ?? []).filter((c) => !chainIds.has(c)).map((c) => `${p.id} -> ${c}`));
|
|
57470
|
+
const missingCwe = patternList.map((p) => p.id).filter((id) => !cweIds.has(id));
|
|
57471
|
+
return {
|
|
57472
|
+
layers: {
|
|
57473
|
+
chains: chainIds.size,
|
|
57474
|
+
patterns: patternList.length,
|
|
57475
|
+
cwe: cweIds.size
|
|
57476
|
+
},
|
|
57477
|
+
consistency: {
|
|
57478
|
+
ok: brokenChainRefs.length === 0 && missingCwe.length === 0,
|
|
57479
|
+
broken_chain_refs: brokenChainRefs,
|
|
57480
|
+
missing_cwe: missingCwe
|
|
57481
|
+
}
|
|
57482
|
+
};
|
|
57483
|
+
}
|
|
57484
|
+
|
|
57485
|
+
// src/benchmark.ts
|
|
57486
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
57487
|
+
import { join as join18 } from "node:path";
|
|
57488
|
+
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
57273
57489
|
import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
|
|
57274
|
-
var ROOT5 =
|
|
57275
|
-
var CORPUS =
|
|
57276
|
-
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ??
|
|
57490
|
+
var ROOT5 = join18(fileURLToPath10(new URL(".", import.meta.url)), "..");
|
|
57491
|
+
var CORPUS = join18(ROOT5, "bench", "corpus");
|
|
57492
|
+
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join18(homedir8(), ".blitzstrike", "corpus");
|
|
57277
57493
|
function parseCorpusFile(path) {
|
|
57278
57494
|
let text;
|
|
57279
57495
|
try {
|
|
57280
|
-
text =
|
|
57496
|
+
text = readFileSync19(path, "utf8");
|
|
57281
57497
|
} catch {
|
|
57282
57498
|
return null;
|
|
57283
57499
|
}
|
|
@@ -57333,7 +57549,7 @@ function loadDir(dir) {
|
|
|
57333
57549
|
for (const f of readdirSync5(dir)) {
|
|
57334
57550
|
if (!f.endsWith(".md"))
|
|
57335
57551
|
continue;
|
|
57336
|
-
const e = parseCorpusFile(
|
|
57552
|
+
const e = parseCorpusFile(join18(dir, f));
|
|
57337
57553
|
if (e)
|
|
57338
57554
|
entries.push(e);
|
|
57339
57555
|
}
|
|
@@ -57360,7 +57576,7 @@ function captureFalsePositive(input) {
|
|
|
57360
57576
|
input.code,
|
|
57361
57577
|
"```"
|
|
57362
57578
|
].filter((l) => l !== null);
|
|
57363
|
-
const path =
|
|
57579
|
+
const path = join18(CAPTURED_CORPUS_DIR, `${id}.md`);
|
|
57364
57580
|
writeFileSync6(path, lines.join(`
|
|
57365
57581
|
`) + `
|
|
57366
57582
|
`, "utf8");
|
|
@@ -57382,7 +57598,7 @@ function detect(entry) {
|
|
|
57382
57598
|
}
|
|
57383
57599
|
const lang = entry.language;
|
|
57384
57600
|
if (lang === "php") {
|
|
57385
|
-
const tmp =
|
|
57601
|
+
const tmp = join18(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
57386
57602
|
writeFileSync6(tmp, entry.code);
|
|
57387
57603
|
const r = analyzeDataFlow2(tmp);
|
|
57388
57604
|
rmSync3(tmp, { force: true });
|
|
@@ -57451,12 +57667,12 @@ function runBenchmark() {
|
|
|
57451
57667
|
}
|
|
57452
57668
|
|
|
57453
57669
|
// src/obligations.ts
|
|
57454
|
-
import { readFileSync as
|
|
57455
|
-
import { join as
|
|
57670
|
+
import { readFileSync as readFileSync20, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
57671
|
+
import { join as join19 } from "node:path";
|
|
57456
57672
|
import { createHash as createHash6 } from "node:crypto";
|
|
57457
57673
|
import { homedir as homedir9 } from "node:os";
|
|
57458
|
-
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ??
|
|
57459
|
-
var OBLIGATION_PATH =
|
|
57674
|
+
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join19(homedir9(), ".blitzstrike");
|
|
57675
|
+
var OBLIGATION_PATH = join19(HOME_DIR4, "obligations.jsonl");
|
|
57460
57676
|
function ensureDir3() {
|
|
57461
57677
|
if (!existsSync10(HOME_DIR4))
|
|
57462
57678
|
mkdirSync8(HOME_DIR4, { recursive: true });
|
|
@@ -57469,7 +57685,7 @@ function loadObligations() {
|
|
|
57469
57685
|
return [];
|
|
57470
57686
|
try {
|
|
57471
57687
|
const out = [];
|
|
57472
|
-
for (const line of
|
|
57688
|
+
for (const line of readFileSync20(OBLIGATION_PATH, "utf8").split(`
|
|
57473
57689
|
`)) {
|
|
57474
57690
|
const t = line.trim();
|
|
57475
57691
|
if (!t)
|
|
@@ -57568,11 +57784,11 @@ function openCount() {
|
|
|
57568
57784
|
|
|
57569
57785
|
// src/variant-scan.ts
|
|
57570
57786
|
init_scanner();
|
|
57571
|
-
import { readFileSync as
|
|
57787
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
57572
57788
|
|
|
57573
57789
|
// src/cross-file.ts
|
|
57574
57790
|
init_scanner();
|
|
57575
|
-
import { readFileSync as
|
|
57791
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
57576
57792
|
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)/;
|
|
57577
57793
|
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*\(/;
|
|
57578
57794
|
var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
|
|
@@ -57624,7 +57840,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
57624
57840
|
for (const file of files) {
|
|
57625
57841
|
let code;
|
|
57626
57842
|
try {
|
|
57627
|
-
code = stripComments(
|
|
57843
|
+
code = stripComments(readFileSync21(file, "utf8"));
|
|
57628
57844
|
} catch {
|
|
57629
57845
|
continue;
|
|
57630
57846
|
}
|
|
@@ -57665,7 +57881,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
57665
57881
|
for (const file of files) {
|
|
57666
57882
|
let code;
|
|
57667
57883
|
try {
|
|
57668
|
-
code = stripComments(
|
|
57884
|
+
code = stripComments(readFileSync21(file, "utf8"));
|
|
57669
57885
|
} catch {
|
|
57670
57886
|
continue;
|
|
57671
57887
|
}
|
|
@@ -57688,7 +57904,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
57688
57904
|
for (const file of files) {
|
|
57689
57905
|
let code;
|
|
57690
57906
|
try {
|
|
57691
|
-
code = stripComments(
|
|
57907
|
+
code = stripComments(readFileSync21(file, "utf8"));
|
|
57692
57908
|
} catch {
|
|
57693
57909
|
continue;
|
|
57694
57910
|
}
|
|
@@ -57803,7 +58019,7 @@ function variantScan(root, opts = {}) {
|
|
|
57803
58019
|
for (const file of files) {
|
|
57804
58020
|
let code;
|
|
57805
58021
|
try {
|
|
57806
|
-
code =
|
|
58022
|
+
code = readFileSync22(file, "utf8");
|
|
57807
58023
|
} catch {
|
|
57808
58024
|
continue;
|
|
57809
58025
|
}
|
|
@@ -58463,16 +58679,16 @@ function analyzeJwt(token, opts = {}) {
|
|
|
58463
58679
|
}
|
|
58464
58680
|
|
|
58465
58681
|
// src/enterprise-benchmark.ts
|
|
58466
|
-
import { existsSync as existsSync11, readFileSync as
|
|
58467
|
-
import { join as
|
|
58682
|
+
import { existsSync as existsSync11, readFileSync as readFileSync23, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
58683
|
+
import { join as join20, resolve } from "node:path";
|
|
58468
58684
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
58469
58685
|
var ENTERPRISE_FIXTURES = [
|
|
58470
|
-
{ id: "php-laravel", dir:
|
|
58471
|
-
{ id: "js-express", dir:
|
|
58472
|
-
{ id: "py-django", dir:
|
|
58473
|
-
{ id: "java-spring", dir:
|
|
58474
|
-
{ id: "wp-batch-route", dir:
|
|
58475
|
-
{ id: "php-complex", dir:
|
|
58686
|
+
{ id: "php-laravel", dir: join20(ROOT6, "php-laravel"), expected_vulns: 4 },
|
|
58687
|
+
{ id: "js-express", dir: join20(ROOT6, "js-express"), expected_vulns: 2 },
|
|
58688
|
+
{ id: "py-django", dir: join20(ROOT6, "py-django"), expected_vulns: 2 },
|
|
58689
|
+
{ id: "java-spring", dir: join20(ROOT6, "java-spring"), expected_vulns: 2 },
|
|
58690
|
+
{ id: "wp-batch-route", dir: join20(ROOT6, "wp-batch-route"), expected_vulns: 3 },
|
|
58691
|
+
{ id: "php-complex", dir: join20(ROOT6, "php-complex"), expected_vulns: 4 }
|
|
58476
58692
|
];
|
|
58477
58693
|
function sourceFiles(root) {
|
|
58478
58694
|
const out = [];
|
|
@@ -58484,7 +58700,7 @@ function sourceFiles(root) {
|
|
|
58484
58700
|
return;
|
|
58485
58701
|
}
|
|
58486
58702
|
for (const e of entries) {
|
|
58487
|
-
const p =
|
|
58703
|
+
const p = join20(dir, e);
|
|
58488
58704
|
let st;
|
|
58489
58705
|
try {
|
|
58490
58706
|
st = statSync3(p);
|
|
@@ -58505,7 +58721,7 @@ function detectFixture(dir) {
|
|
|
58505
58721
|
for (const f of sourceFiles(dir)) {
|
|
58506
58722
|
let code;
|
|
58507
58723
|
try {
|
|
58508
|
-
code =
|
|
58724
|
+
code = readFileSync23(f, "utf8");
|
|
58509
58725
|
} catch {
|
|
58510
58726
|
continue;
|
|
58511
58727
|
}
|
|
@@ -58700,8 +58916,7 @@ function createServer() {
|
|
|
58700
58916
|
` + `- 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).
|
|
58701
58917
|
` + `- 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.
|
|
58702
58918
|
|
|
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" +
|
|
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:
|
|
58919
|
+
` + "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. 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) 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:
|
|
58705
58920
|
` + `- 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.
|
|
58706
58921
|
` + `- 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.
|
|
58707
58922
|
` + "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."
|
|
@@ -59087,6 +59302,38 @@ ${statusLine}` : statusLine },
|
|
|
59087
59302
|
}, async (args) => {
|
|
59088
59303
|
return { content: [{ type: "text", text: JSON.stringify(submissionGate(args)) }] };
|
|
59089
59304
|
});
|
|
59305
|
+
server.registerTool("severity_calibrate", {
|
|
59306
|
+
title: "Severity calibration (VRT-style)",
|
|
59307
|
+
description: "SEVERITY: calibrate a finding to a platform-neutral priority tier (P1–P5) + a VRT-style category, so it is reported at the severity a triager would assign. Maps a detector type (sql_injection, ssrf, ssti, …) to its category + rationale; pass a CVSS score to cross-reference, or 'list' to enumerate every calibrated class.",
|
|
59308
|
+
inputSchema: {
|
|
59309
|
+
type: string2().optional().describe("Detector/vuln class id, or 'list'"),
|
|
59310
|
+
severity: string2().optional().describe("Detector-reported severity (informational for reference)"),
|
|
59311
|
+
cvss: number2().min(0).max(10).optional().describe("CVSS base score (0–10) to cross-reference"),
|
|
59312
|
+
informational: boolean2().optional().describe("True if the finding is informational-only")
|
|
59313
|
+
}
|
|
59314
|
+
}, async ({ type, severity, cvss, informational }) => {
|
|
59315
|
+
if (type === "list")
|
|
59316
|
+
return { content: [{ type: "text", text: JSON.stringify(listSeverityMap()) }] };
|
|
59317
|
+
return { content: [{ type: "text", text: JSON.stringify(severityCalibrate({ type, severity, cvss, informational })) }] };
|
|
59318
|
+
});
|
|
59319
|
+
server.registerTool("engagement_scaffold", {
|
|
59320
|
+
title: "Engagement scaffold",
|
|
59321
|
+
description: "SCAFFOLD: open an engagement and emit a deterministic folder layout (scope.md, findings/, evidence/, reports/, notes/) + a pre-filled scope.md template. Returns the engagement id + initial state so the hunt starts with structure, not improvisation.",
|
|
59322
|
+
inputSchema: {
|
|
59323
|
+
target: string2().describe("The in-scope target (domain/URL/program)"),
|
|
59324
|
+
scope: array(string2()).optional().describe("In-scope assets/domains"),
|
|
59325
|
+
out_of_scope: array(string2()).optional().describe("Out-of-scope assets")
|
|
59326
|
+
}
|
|
59327
|
+
}, async ({ target, scope, out_of_scope }) => {
|
|
59328
|
+
return { content: [{ type: "text", text: JSON.stringify(scaffoldEngagement(target, scope ?? [], out_of_scope ?? [])) }] };
|
|
59329
|
+
});
|
|
59330
|
+
server.registerTool("data_refresh", {
|
|
59331
|
+
title: "Data-layer health check",
|
|
59332
|
+
description: "HEALTH: re-load every data layer (chains, patterns, CWE map) and verify cross-references — a pattern pointing at a missing chain, or a detector without a CWE mapping. Returns counts + any broken reference, so stale data is surfaced instead of silently producing bad grounding.",
|
|
59333
|
+
inputSchema: {}
|
|
59334
|
+
}, async () => {
|
|
59335
|
+
return { content: [{ type: "text", text: JSON.stringify(dataRefresh()) }] };
|
|
59336
|
+
});
|
|
59090
59337
|
server.registerTool("fofa_search", {
|
|
59091
59338
|
title: "FOFA asset search",
|
|
59092
59339
|
description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
|
|
@@ -59471,7 +59718,7 @@ ${statusLine}` : statusLine },
|
|
|
59471
59718
|
let src = code;
|
|
59472
59719
|
if (!src && path) {
|
|
59473
59720
|
try {
|
|
59474
|
-
src =
|
|
59721
|
+
src = readFileSync25(path, "utf8");
|
|
59475
59722
|
} catch {
|
|
59476
59723
|
return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
|
|
59477
59724
|
}
|
|
@@ -60089,7 +60336,7 @@ ${statusLine}` : statusLine },
|
|
|
60089
60336
|
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.",
|
|
60090
60337
|
inputSchema: { path: string2().describe("Source file path (PHP)") }
|
|
60091
60338
|
}, async ({ path }) => {
|
|
60092
|
-
const code =
|
|
60339
|
+
const code = readFileSync25(path, "utf8");
|
|
60093
60340
|
const findings = detectRouteConfusion(code, path);
|
|
60094
60341
|
return {
|
|
60095
60342
|
content: [{
|
|
@@ -60103,7 +60350,7 @@ ${statusLine}` : statusLine },
|
|
|
60103
60350
|
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).",
|
|
60104
60351
|
inputSchema: { path: string2().describe("Source file path") }
|
|
60105
60352
|
}, async ({ path }) => {
|
|
60106
|
-
const code =
|
|
60353
|
+
const code = readFileSync25(path, "utf8");
|
|
60107
60354
|
const findings = detectComplexBugs(code, path);
|
|
60108
60355
|
return {
|
|
60109
60356
|
content: [{
|
|
@@ -60189,7 +60436,7 @@ ${statusLine}` : statusLine },
|
|
|
60189
60436
|
let lang = language;
|
|
60190
60437
|
if (path && !code) {
|
|
60191
60438
|
try {
|
|
60192
|
-
src =
|
|
60439
|
+
src = readFileSync25(path, "utf8");
|
|
60193
60440
|
} catch {
|
|
60194
60441
|
return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
|
|
60195
60442
|
}
|
|
@@ -60430,15 +60677,15 @@ async function serve() {
|
|
|
60430
60677
|
|
|
60431
60678
|
// src/index.ts
|
|
60432
60679
|
init_cli();
|
|
60433
|
-
import { readFileSync as
|
|
60434
|
-
import { join as
|
|
60435
|
-
import { fileURLToPath as
|
|
60436
|
-
var ROOT11 =
|
|
60680
|
+
import { readFileSync as readFileSync29, existsSync as existsSync14 } from "node:fs";
|
|
60681
|
+
import { join as join25 } from "node:path";
|
|
60682
|
+
import { fileURLToPath as fileURLToPath15 } from "node:url";
|
|
60683
|
+
var ROOT11 = join25(fileURLToPath15(new URL(".", import.meta.url)), "..");
|
|
60437
60684
|
function readVersion() {
|
|
60438
60685
|
try {
|
|
60439
|
-
const p =
|
|
60686
|
+
const p = join25(ROOT11, "package.json");
|
|
60440
60687
|
if (existsSync14(p))
|
|
60441
|
-
return JSON.parse(
|
|
60688
|
+
return JSON.parse(readFileSync29(p, "utf8")).version ?? "1.0.0";
|
|
60442
60689
|
} catch {}
|
|
60443
60690
|
return "1.0.0";
|
|
60444
60691
|
}
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
{"id": "hardcoded_secret", "cwe": "CWE-798", "name": "Use of Hard-coded Credentials"},
|
|
59
59
|
{"id": "type_juggling", "cwe": "CWE-697", "name": "Incorrect Comparison"},
|
|
60
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"}
|
|
61
|
+
{"id": "wrong_sanitizer", "cwe": "CWE-20", "name": "Improper Input Validation"},
|
|
62
|
+
{"id": "llm_injection", "cwe": "CWE-77", "name": "Improper Neutralization of Special Elements used in a Command (LLM Prompt Injection)"},
|
|
63
|
+
{"id": "graphql_exposure", "cwe": "CWE-200", "name": "Exposure of Sensitive Information"},
|
|
64
|
+
{"id": "oauth_misconfig", "cwe": "CWE-601", "name": "URL Redirection to Untrusted Site (Open Redirect)"},
|
|
65
|
+
{"id": "grpc_reflection", "cwe": "CWE-200", "name": "Exposure of Sensitive Information"}
|
|
62
66
|
]
|
|
63
67
|
}
|
|
@@ -146,6 +146,42 @@
|
|
|
146
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
147
|
"chain_templates": ["api_key_leak_to_account_takeover", "appkey_leak_deserialization_rce", "default_creds_to_internal_pivot"],
|
|
148
148
|
"references": ["CWE-798", "OWASP A02:2021 Cryptographic Failures"]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "llm_injection",
|
|
152
|
+
"name": "LLM Prompt Injection",
|
|
153
|
+
"cwe": "CWE-77",
|
|
154
|
+
"real_world": "Untrusted text reaches an LLM call (chat/completion/invoke) without a prompt-injection boundary, letting the input override instructions, leak the system prompt, or steer the model into a privileged action.",
|
|
155
|
+
"detection_signatures": ["an LLM provider call (openai/anthropic/langchain/llm) with request input in scope", "no visible system-prompt boundary or output filtering"],
|
|
156
|
+
"chain_templates": [],
|
|
157
|
+
"references": ["CWE-77", "OWASP LLM01:2025 Prompt Injection"]
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"id": "graphql_exposure",
|
|
161
|
+
"name": "GraphQL Introspection / Playground Exposure",
|
|
162
|
+
"cwe": "CWE-200",
|
|
163
|
+
"real_world": "GraphQL introspection or a playground is left enabled in production, exposing the full schema (every query/mutation/field) and enabling interactive querying for recon.",
|
|
164
|
+
"detection_signatures": ["introspection/playground/graphiql set true in a production-reachable config"],
|
|
165
|
+
"chain_templates": ["graphql_idor_to_data_theft", "graphql_batching_to_credential_stuffing"],
|
|
166
|
+
"references": ["CWE-200", "OWASP API2:2023 Broken Authentication"]
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"id": "oauth_misconfig",
|
|
170
|
+
"name": "OAuth Redirect URI Misconfiguration",
|
|
171
|
+
"cwe": "CWE-601",
|
|
172
|
+
"real_world": "An OAuth redirect_uri/callback is fed by request input with no static allowlist, letting an attacker redirect the authorization code or token to an attacker-controlled endpoint.",
|
|
173
|
+
"detection_signatures": ["redirect_uri/callback fed by request input", "no allowlist/whitelist validation nearby"],
|
|
174
|
+
"chain_templates": ["oauth_redirect_bypass", "open_redirect_to_oauth_theft"],
|
|
175
|
+
"references": ["CWE-601", "OWASP A07:2021 Identification and Authentication Failures"]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"id": "grpc_reflection",
|
|
179
|
+
"name": "gRPC Reflection Exposed",
|
|
180
|
+
"cwe": "CWE-200",
|
|
181
|
+
"real_world": "gRPC server reflection is enabled, enumerating every service and method to any caller and leaking the API contract.",
|
|
182
|
+
"detection_signatures": ["enable_server_reflection / RegisterReflectionService in production code"],
|
|
183
|
+
"chain_templates": [],
|
|
184
|
+
"references": ["CWE-200"]
|
|
149
185
|
}
|
|
150
186
|
]
|
|
151
187
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.60",
|
|
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": {
|