blitzstrike 1.0.22 → 1.0.24
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/dist/index.js +639 -87
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
@@ -43,6 +44,7 @@ var __esm = (fn, res, err) => () => {
|
|
|
43
44
|
throw err[0];
|
|
44
45
|
return res;
|
|
45
46
|
};
|
|
47
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
46
48
|
|
|
47
49
|
// node_modules/ajv/dist/compile/codegen/code.js
|
|
48
50
|
var require_code = __commonJS(function(exports) {
|
|
@@ -14820,10 +14822,10 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14820
14822
|
});
|
|
14821
14823
|
|
|
14822
14824
|
// src/sync.ts
|
|
14823
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as
|
|
14824
|
-
import { join as
|
|
14825
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync3, rmSync } from "node:fs";
|
|
14826
|
+
import { join as join9 } from "node:path";
|
|
14825
14827
|
import { homedir as homedir4 } from "node:os";
|
|
14826
|
-
import { fileURLToPath as
|
|
14828
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
14827
14829
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
14828
14830
|
function dirExists(p) {
|
|
14829
14831
|
try {
|
|
@@ -14836,9 +14838,9 @@ function countFiles(dir) {
|
|
|
14836
14838
|
try {
|
|
14837
14839
|
let n = 0;
|
|
14838
14840
|
const walk = (d) => {
|
|
14839
|
-
for (const e of
|
|
14841
|
+
for (const e of readdirSync5(d, { withFileTypes: true })) {
|
|
14840
14842
|
if (e.isDirectory())
|
|
14841
|
-
walk(
|
|
14843
|
+
walk(join9(d, e.name));
|
|
14842
14844
|
else
|
|
14843
14845
|
n++;
|
|
14844
14846
|
}
|
|
@@ -14852,8 +14854,8 @@ function countFiles(dir) {
|
|
|
14852
14854
|
function syncData() {
|
|
14853
14855
|
if (!dirExists(DATA_ROOT2))
|
|
14854
14856
|
mkdirSync3(DATA_ROOT2, { recursive: true });
|
|
14855
|
-
const payloadsPath =
|
|
14856
|
-
const templatesPath =
|
|
14857
|
+
const payloadsPath = join9(DATA_ROOT2, "payloads");
|
|
14858
|
+
const templatesPath = join9(DATA_ROOT2, "templates");
|
|
14857
14859
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
14858
14860
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
14859
14861
|
if (hasPayloads && hasTemplates) {
|
|
@@ -14863,8 +14865,8 @@ function syncData() {
|
|
|
14863
14865
|
data_root: DATA_ROOT2
|
|
14864
14866
|
};
|
|
14865
14867
|
}
|
|
14866
|
-
const localPayloads =
|
|
14867
|
-
const localTemplates =
|
|
14868
|
+
const localPayloads = join9(ROOT6, "payloads");
|
|
14869
|
+
const localTemplates = join9(ROOT6, "templates");
|
|
14868
14870
|
let payloadsSynced = false;
|
|
14869
14871
|
let templatesSynced = false;
|
|
14870
14872
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -14877,7 +14879,7 @@ function syncData() {
|
|
|
14877
14879
|
}
|
|
14878
14880
|
const gitOk = spawnSync3("git", ["--version"]).status === 0;
|
|
14879
14881
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
14880
|
-
const tmp =
|
|
14882
|
+
const tmp = join9(DATA_ROOT2, ".tmp-clone");
|
|
14881
14883
|
if (dirExists(tmp))
|
|
14882
14884
|
rmSync(tmp, { recursive: true, force: true });
|
|
14883
14885
|
const r = spawnSync3("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -14885,11 +14887,11 @@ function syncData() {
|
|
|
14885
14887
|
const sparse = spawnSync3("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
14886
14888
|
if (sparse.status === 0) {
|
|
14887
14889
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
14888
|
-
copyDir(
|
|
14890
|
+
copyDir(join9(tmp, "payloads"), payloadsPath);
|
|
14889
14891
|
payloadsSynced = true;
|
|
14890
14892
|
}
|
|
14891
14893
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
14892
|
-
copyDir(
|
|
14894
|
+
copyDir(join9(tmp, "templates"), templatesPath);
|
|
14893
14895
|
templatesSynced = true;
|
|
14894
14896
|
}
|
|
14895
14897
|
}
|
|
@@ -14910,19 +14912,19 @@ function copyDir(src, dst) {
|
|
|
14910
14912
|
}
|
|
14911
14913
|
function manualCopy(src, dst) {
|
|
14912
14914
|
mkdirSync3(dst, { recursive: true });
|
|
14913
|
-
for (const e of
|
|
14914
|
-
const s =
|
|
14915
|
-
const d =
|
|
14915
|
+
for (const e of readdirSync5(src, { withFileTypes: true })) {
|
|
14916
|
+
const s = join9(src, e.name);
|
|
14917
|
+
const d = join9(dst, e.name);
|
|
14916
14918
|
if (e.isDirectory())
|
|
14917
14919
|
manualCopy(s, d);
|
|
14918
14920
|
else
|
|
14919
|
-
writeFileSync2(d,
|
|
14921
|
+
writeFileSync2(d, readFileSync11(s));
|
|
14920
14922
|
}
|
|
14921
14923
|
}
|
|
14922
|
-
var
|
|
14924
|
+
var ROOT6, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
14923
14925
|
var init_sync = __esm(() => {
|
|
14924
|
-
|
|
14925
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
14926
|
+
ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
14927
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join9(homedir4(), ".blitzstrike", "data");
|
|
14926
14928
|
});
|
|
14927
14929
|
|
|
14928
14930
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -29759,6 +29761,18 @@ function listMemory() {
|
|
|
29759
29761
|
entries: entries.sort((a, b) => a.created < b.created ? 1 : -1).map((e) => ({ id: e.id, topic: e.topic, type: e.type, verified: e.verified, created: e.created }))
|
|
29760
29762
|
};
|
|
29761
29763
|
}
|
|
29764
|
+
function forget(id) {
|
|
29765
|
+
const { writeFileSync } = __require("node:fs");
|
|
29766
|
+
const entries = loadMemory();
|
|
29767
|
+
const kept = entries.filter((e) => e.id !== id);
|
|
29768
|
+
if (kept.length === entries.length) {
|
|
29769
|
+
return { removed: false, id, note: "not found" };
|
|
29770
|
+
}
|
|
29771
|
+
writeFileSync(MEMORY_PATH, kept.map((e) => JSON.stringify(e)).join(`
|
|
29772
|
+
`) + (kept.length ? `
|
|
29773
|
+
` : ""));
|
|
29774
|
+
return { removed: true, id };
|
|
29775
|
+
}
|
|
29762
29776
|
function rememberIfAbsent(topic, content, type, tags, source, verified) {
|
|
29763
29777
|
const r = remember(topic, content, type, tags, source, verified);
|
|
29764
29778
|
return r.saved === true;
|
|
@@ -29954,6 +29968,12 @@ var DEFAULT_WEIGHTS = {
|
|
|
29954
29968
|
negative_control: 0.1
|
|
29955
29969
|
};
|
|
29956
29970
|
var confidenceWeights = { ...DEFAULT_WEIGHTS };
|
|
29971
|
+
function setConfidenceWeights(weights) {
|
|
29972
|
+
confidenceWeights = { ...DEFAULT_WEIGHTS, ...weights };
|
|
29973
|
+
}
|
|
29974
|
+
function getConfidenceWeights() {
|
|
29975
|
+
return { ...confidenceWeights };
|
|
29976
|
+
}
|
|
29957
29977
|
function computeConfidence(factors) {
|
|
29958
29978
|
let score = 0;
|
|
29959
29979
|
for (const [key, weight] of Object.entries(confidenceWeights)) {
|
|
@@ -30024,6 +30044,13 @@ function transition(finding, to) {
|
|
|
30024
30044
|
const updated = { ...finding, status: to, timestamps: { ...finding.timestamps, updated: new Date().toISOString() } };
|
|
30025
30045
|
return updated;
|
|
30026
30046
|
}
|
|
30047
|
+
function attachEvidence(finding, evidence) {
|
|
30048
|
+
return {
|
|
30049
|
+
...finding,
|
|
30050
|
+
evidence: [...finding.evidence, evidence],
|
|
30051
|
+
timestamps: { ...finding.timestamps, updated: new Date().toISOString() }
|
|
30052
|
+
};
|
|
30053
|
+
}
|
|
30027
30054
|
function confirmFinding(finding, opts) {
|
|
30028
30055
|
const evidence = opts.evidence.map((e) => makeEvidence(e));
|
|
30029
30056
|
const negativeControl = opts.negative_control ?? true;
|
|
@@ -30429,7 +30456,7 @@ function ensureTool(name) {
|
|
|
30429
30456
|
}
|
|
30430
30457
|
|
|
30431
30458
|
// src/intel.ts
|
|
30432
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
30459
|
+
import { readFileSync as readFileSync6, existsSync as existsSync4 } from "node:fs";
|
|
30433
30460
|
import { join as join6 } from "node:path";
|
|
30434
30461
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
30435
30462
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
@@ -30438,7 +30465,7 @@ var ROOT3 = join6(fileURLToPath4(new URL(".", import.meta.url)), "..");
|
|
|
30438
30465
|
var INTEL = join6(ROOT3, "intelligence");
|
|
30439
30466
|
function loadJson(name) {
|
|
30440
30467
|
const p = join6(INTEL, `${name}.json`);
|
|
30441
|
-
if (!
|
|
30468
|
+
if (!existsSync4(p))
|
|
30442
30469
|
return null;
|
|
30443
30470
|
try {
|
|
30444
30471
|
return JSON.parse(readFileSync6(p, "utf8"));
|
|
@@ -30454,7 +30481,6 @@ function detectWaf(headers, body = "") {
|
|
|
30454
30481
|
const bodySigs = sigs.body_signatures ?? [];
|
|
30455
30482
|
const matches = [];
|
|
30456
30483
|
const headerKeys = Object.keys(headers).map((k) => k.toLowerCase());
|
|
30457
|
-
const headerVals = Object.values(headers).join(" ").toLowerCase();
|
|
30458
30484
|
for (const s of headerSigs) {
|
|
30459
30485
|
const h = (s.header ?? "").toLowerCase();
|
|
30460
30486
|
const pattern = (s.pattern ?? "").toLowerCase();
|
|
@@ -30553,12 +30579,12 @@ var PACKAGE_PAYLOADS = join6(ROOT3, "payloads");
|
|
|
30553
30579
|
var PACKAGE_TEMPLATES = join6(ROOT3, "templates");
|
|
30554
30580
|
var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join6(homedir2(), ".blitzstrike", "data");
|
|
30555
30581
|
function payloadsDir() {
|
|
30556
|
-
if (
|
|
30582
|
+
if (existsSync4(PACKAGE_PAYLOADS))
|
|
30557
30583
|
return PACKAGE_PAYLOADS;
|
|
30558
30584
|
return join6(DATA_ROOT, "payloads");
|
|
30559
30585
|
}
|
|
30560
30586
|
function templatesDir() {
|
|
30561
|
-
if (
|
|
30587
|
+
if (existsSync4(PACKAGE_TEMPLATES))
|
|
30562
30588
|
return PACKAGE_TEMPLATES;
|
|
30563
30589
|
return join6(DATA_ROOT, "templates");
|
|
30564
30590
|
}
|
|
@@ -31132,7 +31158,6 @@ function walkExpression(node, env, summaries, findings, suppressed, file) {
|
|
|
31132
31158
|
const sanitizers = tainted.flatMap((t) => t.sanitizers);
|
|
31133
31159
|
const inlineSan = findSanitizers(JSON.stringify(node));
|
|
31134
31160
|
const san = [...sanitizers, ...inlineSan].filter((s, i, arr) => arr.indexOf(s) === i);
|
|
31135
|
-
const authGated = false;
|
|
31136
31161
|
const cls = sink.cls;
|
|
31137
31162
|
if (!isSanitized(san, cls.id)) {
|
|
31138
31163
|
findings.push({
|
|
@@ -49403,7 +49428,6 @@ var phpAdapter = {
|
|
|
49403
49428
|
}
|
|
49404
49429
|
}
|
|
49405
49430
|
}
|
|
49406
|
-
const superSources = new Map;
|
|
49407
49431
|
for (const n of nodes) {
|
|
49408
49432
|
if (n.kind === "assign") {
|
|
49409
49433
|
const target = phpVar(n.left);
|
|
@@ -49476,7 +49500,7 @@ function jsWalk(node, out) {
|
|
|
49476
49500
|
var jsAdapter = {
|
|
49477
49501
|
language: "javascript",
|
|
49478
49502
|
extensions: [".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx"],
|
|
49479
|
-
parse(code,
|
|
49503
|
+
parse(code, _file) {
|
|
49480
49504
|
let ast;
|
|
49481
49505
|
try {
|
|
49482
49506
|
ast = parse6(code, {
|
|
@@ -49585,7 +49609,7 @@ var PY_SANITIZERS = [
|
|
|
49585
49609
|
var pythonAdapter = {
|
|
49586
49610
|
language: "python",
|
|
49587
49611
|
extensions: [".py", ".pyw"],
|
|
49588
|
-
parse(code,
|
|
49612
|
+
parse(code, _file) {
|
|
49589
49613
|
const tree = parser2.parse(code);
|
|
49590
49614
|
const sources = [];
|
|
49591
49615
|
const sinks = [];
|
|
@@ -49703,7 +49727,7 @@ var JAVA_SANITIZERS = [
|
|
|
49703
49727
|
var javaAdapter = {
|
|
49704
49728
|
language: "java",
|
|
49705
49729
|
extensions: [".java"],
|
|
49706
|
-
parse(code,
|
|
49730
|
+
parse(code, _file) {
|
|
49707
49731
|
const sources = [];
|
|
49708
49732
|
const sinks = [];
|
|
49709
49733
|
const sanitizers = [];
|
|
@@ -50231,10 +50255,10 @@ async function liveRecon(target, includeActive = false) {
|
|
|
50231
50255
|
}
|
|
50232
50256
|
|
|
50233
50257
|
// src/strike.ts
|
|
50234
|
-
var
|
|
50235
|
-
async function httpRequest(url, method, data, headers) {
|
|
50258
|
+
var DEFAULT_TIMEOUT_MS = 15000;
|
|
50259
|
+
async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
50236
50260
|
const controller = new AbortController;
|
|
50237
|
-
const t = setTimeout(() => controller.abort(),
|
|
50261
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
50238
50262
|
try {
|
|
50239
50263
|
const res = await fetch(url, {
|
|
50240
50264
|
method,
|
|
@@ -50272,12 +50296,13 @@ async function strikeVerify(input) {
|
|
|
50272
50296
|
const marker = input.marker ?? defaultMarker();
|
|
50273
50297
|
const control = input.control ?? defaultMarker();
|
|
50274
50298
|
const baseUrl = input.url;
|
|
50299
|
+
const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
50275
50300
|
const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
|
|
50276
50301
|
const markerTarget = inject(baseUrl, marker, param);
|
|
50277
50302
|
const controlTarget = inject(baseUrl, control, param);
|
|
50278
|
-
const baseline = await httpRequest(baselineTarget, method, input.data, input.headers);
|
|
50279
|
-
const markerResp = await httpRequest(markerTarget, method, input.data, input.headers);
|
|
50280
|
-
const controlResp = await httpRequest(controlTarget, method, input.data, input.headers);
|
|
50303
|
+
const baseline = await httpRequest(baselineTarget, method, input.data, input.headers, timeoutMs);
|
|
50304
|
+
const markerResp = await httpRequest(markerTarget, method, input.data, input.headers, timeoutMs);
|
|
50305
|
+
const controlResp = await httpRequest(controlTarget, method, input.data, input.headers, timeoutMs);
|
|
50281
50306
|
if (baseline.status === 0 || markerResp.status === 0) {
|
|
50282
50307
|
return {
|
|
50283
50308
|
status: "blocked",
|
|
@@ -50367,6 +50392,421 @@ function resolveFinding(finding, verdict) {
|
|
|
50367
50392
|
}
|
|
50368
50393
|
}
|
|
50369
50394
|
|
|
50395
|
+
// src/cvss.ts
|
|
50396
|
+
var AV_WEIGHT = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
|
|
50397
|
+
var AC_WEIGHT = { L: 0.77, H: 0.44 };
|
|
50398
|
+
var PR_WEIGHT = {
|
|
50399
|
+
U: { N: 0.85, L: 0.62, H: 0.27 },
|
|
50400
|
+
C: { N: 0.85, L: 0.68, H: 0.5 }
|
|
50401
|
+
};
|
|
50402
|
+
var UI_WEIGHT = { N: 0.85, R: 0.62 };
|
|
50403
|
+
var CIA_WEIGHT = { H: 0.56, L: 0.22, N: 0 };
|
|
50404
|
+
function roundup(value) {
|
|
50405
|
+
return Math.ceil(value * 10) / 10;
|
|
50406
|
+
}
|
|
50407
|
+
function cvssBaseScore(input) {
|
|
50408
|
+
const AV = input.AV ?? "N";
|
|
50409
|
+
const AC = input.AC ?? "L";
|
|
50410
|
+
const PR = input.PR ?? "N";
|
|
50411
|
+
const UI = input.UI ?? "N";
|
|
50412
|
+
const S = input.S ?? "U";
|
|
50413
|
+
const C = input.C ?? "N";
|
|
50414
|
+
const I = input.I ?? "N";
|
|
50415
|
+
const A = input.A ?? "N";
|
|
50416
|
+
const iscBase = 1 - (1 - CIA_WEIGHT[C]) * (1 - CIA_WEIGHT[I]) * (1 - CIA_WEIGHT[A]);
|
|
50417
|
+
let impact;
|
|
50418
|
+
if (S === "U") {
|
|
50419
|
+
impact = 6.42 * iscBase;
|
|
50420
|
+
} else {
|
|
50421
|
+
impact = 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
|
|
50422
|
+
}
|
|
50423
|
+
const exploitability = 8.22 * AV_WEIGHT[AV] * AC_WEIGHT[AC] * PR_WEIGHT[S][PR] * UI_WEIGHT[UI];
|
|
50424
|
+
if (impact <= 0)
|
|
50425
|
+
return 0;
|
|
50426
|
+
if (S === "U") {
|
|
50427
|
+
return roundup(Math.min(impact + exploitability, 10));
|
|
50428
|
+
}
|
|
50429
|
+
return roundup(Math.min(1.08 * (impact + exploitability), 10));
|
|
50430
|
+
}
|
|
50431
|
+
function cvssVector(input) {
|
|
50432
|
+
const order = [
|
|
50433
|
+
["AV", input.AV ?? "N"],
|
|
50434
|
+
["AC", input.AC ?? "L"],
|
|
50435
|
+
["PR", input.PR ?? "N"],
|
|
50436
|
+
["UI", input.UI ?? "N"],
|
|
50437
|
+
["S", input.S ?? "U"],
|
|
50438
|
+
["C", input.C ?? "N"],
|
|
50439
|
+
["I", input.I ?? "N"],
|
|
50440
|
+
["A", input.A ?? "N"]
|
|
50441
|
+
];
|
|
50442
|
+
return "CVSS:3.1/" + order.map(([k, v]) => `${k}:${v}`).join("/");
|
|
50443
|
+
}
|
|
50444
|
+
function cvssSeverity(score) {
|
|
50445
|
+
if (score === 0)
|
|
50446
|
+
return "none";
|
|
50447
|
+
if (score < 4)
|
|
50448
|
+
return "low";
|
|
50449
|
+
if (score < 7)
|
|
50450
|
+
return "medium";
|
|
50451
|
+
if (score < 9)
|
|
50452
|
+
return "high";
|
|
50453
|
+
return "critical";
|
|
50454
|
+
}
|
|
50455
|
+
function cvssAssess(input) {
|
|
50456
|
+
const score = cvssBaseScore(input);
|
|
50457
|
+
return { score, vector: cvssVector(input), severity: cvssSeverity(score) };
|
|
50458
|
+
}
|
|
50459
|
+
|
|
50460
|
+
// src/dedup.ts
|
|
50461
|
+
function rootCauseSignature(finding) {
|
|
50462
|
+
const sinkType = (finding.sink?.type ?? "unknown").toLowerCase();
|
|
50463
|
+
const sourceType = (finding.source?.type ?? "unknown").toLowerCase();
|
|
50464
|
+
const cwe = (finding.classification?.cwe ?? "").toUpperCase();
|
|
50465
|
+
return [sinkType, sourceType, cwe].filter(Boolean).join("|");
|
|
50466
|
+
}
|
|
50467
|
+
function dedupFindings(findings) {
|
|
50468
|
+
const groups = new Map;
|
|
50469
|
+
for (const f of findings) {
|
|
50470
|
+
const sig = rootCauseSignature(f);
|
|
50471
|
+
const existing = groups.get(sig);
|
|
50472
|
+
if (existing) {
|
|
50473
|
+
existing.count += 1;
|
|
50474
|
+
existing.findings.push(f);
|
|
50475
|
+
} else {
|
|
50476
|
+
groups.set(sig, { signature: sig, count: 1, representative: f, findings: [f] });
|
|
50477
|
+
}
|
|
50478
|
+
}
|
|
50479
|
+
return [...groups.values()];
|
|
50480
|
+
}
|
|
50481
|
+
|
|
50482
|
+
// src/report.ts
|
|
50483
|
+
function summarize(findings) {
|
|
50484
|
+
const byStatus = {};
|
|
50485
|
+
const bySeverity = {};
|
|
50486
|
+
let evidenceArtifacts = 0;
|
|
50487
|
+
for (const f of findings) {
|
|
50488
|
+
byStatus[f.status] = (byStatus[f.status] ?? 0) + 1;
|
|
50489
|
+
const sev = f.classification?.severity ?? "informational";
|
|
50490
|
+
bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
|
|
50491
|
+
evidenceArtifacts += f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
|
|
50492
|
+
}
|
|
50493
|
+
const groups = dedupFindings(findings);
|
|
50494
|
+
return {
|
|
50495
|
+
total_findings: findings.length,
|
|
50496
|
+
unique_root_causes: groups.length,
|
|
50497
|
+
by_status: byStatus,
|
|
50498
|
+
by_severity: bySeverity,
|
|
50499
|
+
confirmed: byStatus["confirmed"] ?? 0,
|
|
50500
|
+
false_positives: byStatus["false_positive"] ?? 0,
|
|
50501
|
+
evidence_artifacts: evidenceArtifacts
|
|
50502
|
+
};
|
|
50503
|
+
}
|
|
50504
|
+
function severityLabel(sev) {
|
|
50505
|
+
const s = sev.toUpperCase();
|
|
50506
|
+
if (s === "CRITICAL")
|
|
50507
|
+
return "\uD83D\uDD34 CRITICAL";
|
|
50508
|
+
if (s === "HIGH")
|
|
50509
|
+
return "\uD83D\uDFE0 HIGH";
|
|
50510
|
+
if (s === "MEDIUM")
|
|
50511
|
+
return "\uD83D\uDFE1 MEDIUM";
|
|
50512
|
+
if (s === "LOW")
|
|
50513
|
+
return "\uD83D\uDD35 LOW";
|
|
50514
|
+
return "⚪ INFORMATIONAL";
|
|
50515
|
+
}
|
|
50516
|
+
function reportMarkdown(findings, opts = {}) {
|
|
50517
|
+
const summary = summarize(findings);
|
|
50518
|
+
const lines = [];
|
|
50519
|
+
lines.push(`# ${opts.title ?? "Blitz Strike — Security Assessment Report"}`);
|
|
50520
|
+
lines.push("");
|
|
50521
|
+
lines.push(`- Scope: ${opts.scope ?? "(unset)"}`);
|
|
50522
|
+
lines.push(`- Blitz Strike version: ${opts.version ?? "unknown"}`);
|
|
50523
|
+
if (opts.include_timestamp)
|
|
50524
|
+
lines.push(`- Generated: ${new Date().toISOString()}`);
|
|
50525
|
+
lines.push("");
|
|
50526
|
+
lines.push("## Summary");
|
|
50527
|
+
lines.push("");
|
|
50528
|
+
lines.push(`| Metric | Value |`);
|
|
50529
|
+
lines.push(`|--------|-------|`);
|
|
50530
|
+
lines.push(`| Total findings | ${summary.total_findings} |`);
|
|
50531
|
+
lines.push(`| Unique root causes | ${summary.unique_root_causes} |`);
|
|
50532
|
+
lines.push(`| Confirmed | ${summary.confirmed} |`);
|
|
50533
|
+
lines.push(`| False positives | ${summary.false_positives} |`);
|
|
50534
|
+
lines.push(`| Evidence artifacts | ${summary.evidence_artifacts} |`);
|
|
50535
|
+
lines.push("");
|
|
50536
|
+
lines.push("### By status");
|
|
50537
|
+
lines.push("");
|
|
50538
|
+
for (const [status, n] of Object.entries(summary.by_status).sort()) {
|
|
50539
|
+
lines.push(`- ${status}: ${n}`);
|
|
50540
|
+
}
|
|
50541
|
+
lines.push("");
|
|
50542
|
+
lines.push("### By severity");
|
|
50543
|
+
lines.push("");
|
|
50544
|
+
for (const [sev, n] of Object.entries(summary.by_severity).sort()) {
|
|
50545
|
+
lines.push(`- ${sev}: ${n}`);
|
|
50546
|
+
}
|
|
50547
|
+
lines.push("");
|
|
50548
|
+
lines.push("## Findings");
|
|
50549
|
+
lines.push("");
|
|
50550
|
+
if (findings.length === 0) {
|
|
50551
|
+
lines.push("_No findings._");
|
|
50552
|
+
return lines.join(`
|
|
50553
|
+
`) + `
|
|
50554
|
+
`;
|
|
50555
|
+
}
|
|
50556
|
+
for (const f of findings) {
|
|
50557
|
+
lines.push(`### ${f.id} — ${f.title} [${severityLabel(f.classification?.severity ?? "informational")}]`);
|
|
50558
|
+
lines.push("");
|
|
50559
|
+
lines.push(`- **Status**: \`${f.status}\``);
|
|
50560
|
+
lines.push(`- **Confidence**: ${f.confidence} (${f.confidence_level})`);
|
|
50561
|
+
if (f.classification?.cwe)
|
|
50562
|
+
lines.push(`- **CWE**: ${f.classification.cwe}`);
|
|
50563
|
+
if (f.classification?.cvss_score != null)
|
|
50564
|
+
lines.push(`- **CVSS**: ${f.classification.cvss_score} (${f.classification.cvss_vector ?? ""}) — ${cvssSeverity(f.classification.cvss_score)}`);
|
|
50565
|
+
lines.push(`- **Source**: ${f.source?.type} \`${f.source?.name ?? ""}\``);
|
|
50566
|
+
lines.push(`- **Sink**: ${f.sink?.type}${f.sink?.symbol ? ` \`${f.sink.symbol}\`` : ""}`);
|
|
50567
|
+
if (f.target?.host || f.target?.endpoint)
|
|
50568
|
+
lines.push(`- **Target**: ${[f.target.host, f.target.endpoint].filter(Boolean).join("")}`);
|
|
50569
|
+
lines.push(`- **Evidence**: ${f.evidence?.length ?? 0} record(s)`);
|
|
50570
|
+
if (f.remediation && Object.keys(f.remediation).length) {
|
|
50571
|
+
lines.push(`- **Note**: ${JSON.stringify(f.remediation)}`);
|
|
50572
|
+
}
|
|
50573
|
+
lines.push("");
|
|
50574
|
+
}
|
|
50575
|
+
return lines.join(`
|
|
50576
|
+
`) + `
|
|
50577
|
+
`;
|
|
50578
|
+
}
|
|
50579
|
+
function reportJson(findings, opts = {}) {
|
|
50580
|
+
const summary = summarize(findings);
|
|
50581
|
+
const payload = {
|
|
50582
|
+
title: opts.title ?? "Blitz Strike — Security Assessment Report",
|
|
50583
|
+
scope: opts.scope ?? null,
|
|
50584
|
+
version: opts.version ?? null,
|
|
50585
|
+
summary,
|
|
50586
|
+
findings,
|
|
50587
|
+
integrity: sha256(JSON.stringify({ summary, findings }))
|
|
50588
|
+
};
|
|
50589
|
+
return JSON.stringify(payload, null, 2);
|
|
50590
|
+
}
|
|
50591
|
+
|
|
50592
|
+
// src/benchmark.ts
|
|
50593
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync4 } from "node:fs";
|
|
50594
|
+
import { join as join7 } from "node:path";
|
|
50595
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
50596
|
+
var ROOT4 = join7(fileURLToPath5(new URL(".", import.meta.url)), "..");
|
|
50597
|
+
var CORPUS = join7(ROOT4, "bench", "corpus");
|
|
50598
|
+
function parseCorpusFile(path) {
|
|
50599
|
+
let text;
|
|
50600
|
+
try {
|
|
50601
|
+
text = readFileSync9(path, "utf8");
|
|
50602
|
+
} catch {
|
|
50603
|
+
return null;
|
|
50604
|
+
}
|
|
50605
|
+
const lines = text.split(`
|
|
50606
|
+
`);
|
|
50607
|
+
let id = "";
|
|
50608
|
+
let language = "php";
|
|
50609
|
+
let vulnerable = true;
|
|
50610
|
+
let sinkType;
|
|
50611
|
+
let code = "";
|
|
50612
|
+
let inCode = false;
|
|
50613
|
+
let codeLang = "";
|
|
50614
|
+
for (const line of lines) {
|
|
50615
|
+
const m = line.match(/^\s*-\s*(id|language|vulnerable|sink_type)\s*:\s*(.+)$/i);
|
|
50616
|
+
if (!inCode && m) {
|
|
50617
|
+
const key = m[1].toLowerCase();
|
|
50618
|
+
const val = m[2].trim();
|
|
50619
|
+
if (key === "id")
|
|
50620
|
+
id = val;
|
|
50621
|
+
else if (key === "language")
|
|
50622
|
+
language = val.toLowerCase();
|
|
50623
|
+
else if (key === "vulnerable")
|
|
50624
|
+
vulnerable = val === "true";
|
|
50625
|
+
else if (key === "sink_type")
|
|
50626
|
+
sinkType = val;
|
|
50627
|
+
continue;
|
|
50628
|
+
}
|
|
50629
|
+
const start = line.match(/^```(\w*)/);
|
|
50630
|
+
if (start && !inCode) {
|
|
50631
|
+
inCode = true;
|
|
50632
|
+
codeLang = start[1] || "php";
|
|
50633
|
+
continue;
|
|
50634
|
+
}
|
|
50635
|
+
if (inCode && line.trim() === "```")
|
|
50636
|
+
break;
|
|
50637
|
+
if (inCode)
|
|
50638
|
+
code += line + `
|
|
50639
|
+
`;
|
|
50640
|
+
}
|
|
50641
|
+
if (!id)
|
|
50642
|
+
id = path.split("/").pop().replace(/\.md$/, "");
|
|
50643
|
+
return { id, language: language || codeLang || "php", vulnerable, sink_type: sinkType, code: code.trim() };
|
|
50644
|
+
}
|
|
50645
|
+
function loadCorpus() {
|
|
50646
|
+
let entries = [];
|
|
50647
|
+
try {
|
|
50648
|
+
for (const f of readdirSync4(CORPUS)) {
|
|
50649
|
+
if (!f.endsWith(".md"))
|
|
50650
|
+
continue;
|
|
50651
|
+
const e = parseCorpusFile(join7(CORPUS, f));
|
|
50652
|
+
if (e)
|
|
50653
|
+
entries.push(e);
|
|
50654
|
+
}
|
|
50655
|
+
} catch {
|
|
50656
|
+
entries = [];
|
|
50657
|
+
}
|
|
50658
|
+
return entries;
|
|
50659
|
+
}
|
|
50660
|
+
function detect(entry) {
|
|
50661
|
+
const lang = entry.language;
|
|
50662
|
+
if (lang === "php") {
|
|
50663
|
+
const tmp = join7(ROOT4, "bench", ".tmp.php");
|
|
50664
|
+
const { writeFileSync, rmSync } = __require("node:fs");
|
|
50665
|
+
writeFileSync(tmp, entry.code);
|
|
50666
|
+
const r = analyzeTaint(tmp);
|
|
50667
|
+
rmSync(tmp, { force: true });
|
|
50668
|
+
if (r.findings.length > 0)
|
|
50669
|
+
return { detected: true, sinkKind: r.findings[0].sink };
|
|
50670
|
+
return { detected: false };
|
|
50671
|
+
}
|
|
50672
|
+
const ext = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" }[lang] ?? ".php";
|
|
50673
|
+
const r = analyzeTaintUniversal(entry.code, "bench." + ext);
|
|
50674
|
+
if (r.findings.length > 0)
|
|
50675
|
+
return { detected: true, sinkKind: r.findings[0].sink };
|
|
50676
|
+
return { detected: false };
|
|
50677
|
+
}
|
|
50678
|
+
function runBenchmark() {
|
|
50679
|
+
const entries = loadCorpus();
|
|
50680
|
+
const results = entries.map((entry) => {
|
|
50681
|
+
const { detected, sinkKind } = detect(entry);
|
|
50682
|
+
const outcome = entry.vulnerable ? detected ? "tp" : "fn" : detected ? "fp" : "tn";
|
|
50683
|
+
return { entry, detected, sink_kind: sinkKind, outcome };
|
|
50684
|
+
});
|
|
50685
|
+
const tp = results.filter((r) => r.outcome === "tp").length;
|
|
50686
|
+
const tn = results.filter((r) => r.outcome === "tn").length;
|
|
50687
|
+
const fp = results.filter((r) => r.outcome === "fp").length;
|
|
50688
|
+
const fn = results.filter((r) => r.outcome === "fn").length;
|
|
50689
|
+
const total = results.length;
|
|
50690
|
+
const detectionRate = tp + fn > 0 ? tp / (tp + fn) : 0;
|
|
50691
|
+
const falsePositiveRate = fp + tn > 0 ? fp / (fp + tn) : 0;
|
|
50692
|
+
const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
|
|
50693
|
+
return {
|
|
50694
|
+
total_cases: total,
|
|
50695
|
+
tp,
|
|
50696
|
+
tn,
|
|
50697
|
+
fp,
|
|
50698
|
+
fn,
|
|
50699
|
+
detection_rate: Number(detectionRate.toFixed(3)),
|
|
50700
|
+
false_positive_rate: Number(falsePositiveRate.toFixed(3)),
|
|
50701
|
+
precision: Number(precision.toFixed(3)),
|
|
50702
|
+
results: results.map((r) => ({ id: r.entry.id, language: r.entry.language, outcome: r.outcome, sink: r.sink_kind }))
|
|
50703
|
+
};
|
|
50704
|
+
}
|
|
50705
|
+
|
|
50706
|
+
// src/coverage.ts
|
|
50707
|
+
var SINK_CLASSES = [
|
|
50708
|
+
"sql_execution",
|
|
50709
|
+
"code_execution",
|
|
50710
|
+
"command_execution",
|
|
50711
|
+
"file_operations",
|
|
50712
|
+
"file_inclusion",
|
|
50713
|
+
"deserialization",
|
|
50714
|
+
"http_request",
|
|
50715
|
+
"redirect",
|
|
50716
|
+
"html_render",
|
|
50717
|
+
"xml_processing",
|
|
50718
|
+
"archive_extraction",
|
|
50719
|
+
"path_traversal"
|
|
50720
|
+
];
|
|
50721
|
+
var PHP_SINKS2 = {
|
|
50722
|
+
sql_execution: true,
|
|
50723
|
+
code_execution: true,
|
|
50724
|
+
command_execution: true,
|
|
50725
|
+
file_operations: true,
|
|
50726
|
+
file_inclusion: true,
|
|
50727
|
+
deserialization: true,
|
|
50728
|
+
http_request: true,
|
|
50729
|
+
redirect: true,
|
|
50730
|
+
html_render: true,
|
|
50731
|
+
xml_processing: false,
|
|
50732
|
+
archive_extraction: false,
|
|
50733
|
+
path_traversal: true
|
|
50734
|
+
};
|
|
50735
|
+
var JS_SINKS2 = {
|
|
50736
|
+
sql_execution: true,
|
|
50737
|
+
code_execution: true,
|
|
50738
|
+
command_execution: true,
|
|
50739
|
+
file_operations: true,
|
|
50740
|
+
file_inclusion: true,
|
|
50741
|
+
deserialization: false,
|
|
50742
|
+
http_request: true,
|
|
50743
|
+
redirect: true,
|
|
50744
|
+
html_render: true,
|
|
50745
|
+
xml_processing: false,
|
|
50746
|
+
archive_extraction: false,
|
|
50747
|
+
path_traversal: true
|
|
50748
|
+
};
|
|
50749
|
+
var PY_SINKS2 = {
|
|
50750
|
+
sql_execution: true,
|
|
50751
|
+
code_execution: true,
|
|
50752
|
+
command_execution: true,
|
|
50753
|
+
file_operations: true,
|
|
50754
|
+
file_inclusion: false,
|
|
50755
|
+
deserialization: true,
|
|
50756
|
+
http_request: false,
|
|
50757
|
+
redirect: true,
|
|
50758
|
+
html_render: true,
|
|
50759
|
+
xml_processing: false,
|
|
50760
|
+
archive_extraction: false,
|
|
50761
|
+
path_traversal: true
|
|
50762
|
+
};
|
|
50763
|
+
var JAVA_SINKS2 = {
|
|
50764
|
+
sql_execution: true,
|
|
50765
|
+
code_execution: false,
|
|
50766
|
+
command_execution: true,
|
|
50767
|
+
file_operations: true,
|
|
50768
|
+
file_inclusion: false,
|
|
50769
|
+
deserialization: true,
|
|
50770
|
+
http_request: true,
|
|
50771
|
+
redirect: true,
|
|
50772
|
+
html_render: true,
|
|
50773
|
+
xml_processing: false,
|
|
50774
|
+
archive_extraction: false,
|
|
50775
|
+
path_traversal: true
|
|
50776
|
+
};
|
|
50777
|
+
var SINK_BY_LANG = {
|
|
50778
|
+
php: PHP_SINKS2,
|
|
50779
|
+
javascript: JS_SINKS2,
|
|
50780
|
+
python: PY_SINKS2,
|
|
50781
|
+
java: JAVA_SINKS2
|
|
50782
|
+
};
|
|
50783
|
+
function coverageMatrix() {
|
|
50784
|
+
const langs = listLanguages();
|
|
50785
|
+
const sinkCoverage = SINK_CLASSES.map((sink) => {
|
|
50786
|
+
const covered = langs.map((l) => l.language).filter((lang) => SINK_BY_LANG[lang]?.[sink] === true);
|
|
50787
|
+
return { sink, languages: covered };
|
|
50788
|
+
});
|
|
50789
|
+
let coveredPairs = 0;
|
|
50790
|
+
for (const lang of langs) {
|
|
50791
|
+
for (const sink of SINK_CLASSES) {
|
|
50792
|
+
if (SINK_BY_LANG[lang.language]?.[sink])
|
|
50793
|
+
coveredPairs += 1;
|
|
50794
|
+
}
|
|
50795
|
+
}
|
|
50796
|
+
const totalPairs = langs.length * SINK_CLASSES.length;
|
|
50797
|
+
return {
|
|
50798
|
+
languages: langs,
|
|
50799
|
+
sink_coverage: sinkCoverage,
|
|
50800
|
+
counts: {
|
|
50801
|
+
languages: langs.length,
|
|
50802
|
+
sink_classes: SINK_CLASSES.length,
|
|
50803
|
+
covered_pairs: coveredPairs,
|
|
50804
|
+
total_pairs: totalPairs
|
|
50805
|
+
},
|
|
50806
|
+
coverage_ratio: Number((coveredPairs / totalPairs).toFixed(3))
|
|
50807
|
+
};
|
|
50808
|
+
}
|
|
50809
|
+
|
|
50370
50810
|
// src/server.ts
|
|
50371
50811
|
function createServer() {
|
|
50372
50812
|
const server = new McpServer({
|
|
@@ -50478,7 +50918,8 @@ function createServer() {
|
|
|
50478
50918
|
headers: hdrs,
|
|
50479
50919
|
marker,
|
|
50480
50920
|
control,
|
|
50481
|
-
param
|
|
50921
|
+
param,
|
|
50922
|
+
timeout: timeout ? timeout * 1000 : undefined
|
|
50482
50923
|
});
|
|
50483
50924
|
return {
|
|
50484
50925
|
content: [{
|
|
@@ -50703,6 +51144,13 @@ function createServer() {
|
|
|
50703
51144
|
}, async () => {
|
|
50704
51145
|
return { content: [{ type: "text", text: JSON.stringify(listMemory()) }] };
|
|
50705
51146
|
});
|
|
51147
|
+
server.registerTool("memory_forget", {
|
|
51148
|
+
title: "Forget a memory entry",
|
|
51149
|
+
description: "MEMORY: remove a memory entry by id (append-only store; 'forget' = tombstone).",
|
|
51150
|
+
inputSchema: { id: string2().describe("Memory entry id (from memory_list)") }
|
|
51151
|
+
}, async ({ id }) => {
|
|
51152
|
+
return { content: [{ type: "text", text: JSON.stringify(forget(id)) }] };
|
|
51153
|
+
});
|
|
50706
51154
|
server.registerTool("read_tool_manual", {
|
|
50707
51155
|
title: "Read a tool manual",
|
|
50708
51156
|
description: "Read the full deep reference manual for a security tool (270+ manuals from kali-pentest, Apache-2.0).",
|
|
@@ -50886,6 +51334,44 @@ function createServer() {
|
|
|
50886
51334
|
const score = computeConfidence(factors);
|
|
50887
51335
|
return { content: [{ type: "text", text: JSON.stringify({ score, level: confidenceLevel(score) }) }] };
|
|
50888
51336
|
});
|
|
51337
|
+
server.registerTool("confidence_weights", {
|
|
51338
|
+
title: "Get/set confidence weights",
|
|
51339
|
+
description: "FINDINGS: inspect or override the deterministic confidence weights (configurable per the framework spec). Pass `set` (JSON object of weight overrides) to change them; omit to read the current weights.",
|
|
51340
|
+
inputSchema: {
|
|
51341
|
+
set: string2().optional().describe('JSON object of weight overrides (e.g. {"data_flow":0.3})')
|
|
51342
|
+
}
|
|
51343
|
+
}, async ({ set }) => {
|
|
51344
|
+
if (set) {
|
|
51345
|
+
let w;
|
|
51346
|
+
try {
|
|
51347
|
+
w = JSON.parse(set);
|
|
51348
|
+
} catch {
|
|
51349
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
|
|
51350
|
+
}
|
|
51351
|
+
setConfidenceWeights(w);
|
|
51352
|
+
}
|
|
51353
|
+
return { content: [{ type: "text", text: JSON.stringify({ weights: getConfidenceWeights() }) }] };
|
|
51354
|
+
});
|
|
51355
|
+
server.registerTool("finding_attach_evidence", {
|
|
51356
|
+
title: "Attach evidence to a finding",
|
|
51357
|
+
description: "FINDINGS: attach a redacted, SHA-256-tagged evidence record to an existing canonical finding (without advancing its lifecycle).",
|
|
51358
|
+
inputSchema: {
|
|
51359
|
+
finding: string2().describe("JSON of the Finding object"),
|
|
51360
|
+
type: string2().describe("Evidence type (source_location/response/validation_result/negative_control/...)"),
|
|
51361
|
+
description: string2().describe("Evidence description"),
|
|
51362
|
+
content: string2().optional().describe("Evidence artifact content")
|
|
51363
|
+
}
|
|
51364
|
+
}, async ({ finding, type, description, content }) => {
|
|
51365
|
+
let f;
|
|
51366
|
+
try {
|
|
51367
|
+
f = JSON.parse(finding);
|
|
51368
|
+
} catch {
|
|
51369
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON" }) }] };
|
|
51370
|
+
}
|
|
51371
|
+
const ev = makeEvidence({ type, description, artifacts: content ? [{ name: "artifact", kind: "evidence", content }] : [] });
|
|
51372
|
+
const updated = attachEvidence(f, ev);
|
|
51373
|
+
return { content: [{ type: "text", text: JSON.stringify(updated) }] };
|
|
51374
|
+
});
|
|
50889
51375
|
server.registerTool("redact", {
|
|
50890
51376
|
title: "Redact secrets from text",
|
|
50891
51377
|
description: "EVIDENCE: redact passwords/API keys/tokens/cookies/private keys from text before persisting evidence or reporting.",
|
|
@@ -51013,6 +51499,72 @@ function createServer() {
|
|
|
51013
51499
|
});
|
|
51014
51500
|
return { content: [{ type: "text", text: JSON.stringify({ ...r, next_steps: guidance }) }] };
|
|
51015
51501
|
});
|
|
51502
|
+
server.registerTool("cvss_score", {
|
|
51503
|
+
title: "Compute CVSS v3.1 base score",
|
|
51504
|
+
description: "CVSS: compute a deterministic CVSS v3.1 base score + vector + severity from metric values (AV/AC/PR/UI/S/C/I/A). Self-computed, not read from NVD.",
|
|
51505
|
+
inputSchema: {
|
|
51506
|
+
AV: string2().optional().describe("Attack Vector: N/A/L/P"),
|
|
51507
|
+
AC: string2().optional().describe("Attack Complexity: L/H"),
|
|
51508
|
+
PR: string2().optional().describe("Privileges Required: N/L/H"),
|
|
51509
|
+
UI: string2().optional().describe("User Interaction: N/R"),
|
|
51510
|
+
S: string2().optional().describe("Scope: U/C"),
|
|
51511
|
+
C: string2().optional().describe("Confidentiality: H/L/N"),
|
|
51512
|
+
I: string2().optional().describe("Integrity: H/L/N"),
|
|
51513
|
+
A: string2().optional().describe("Availability: H/L/N")
|
|
51514
|
+
}
|
|
51515
|
+
}, async (input) => {
|
|
51516
|
+
return { content: [{ type: "text", text: JSON.stringify(cvssAssess(input)) }] };
|
|
51517
|
+
});
|
|
51518
|
+
server.registerTool("dedup_findings", {
|
|
51519
|
+
title: "Deduplicate findings by root cause",
|
|
51520
|
+
description: "FINDINGS: collapse a list of findings that share a root cause (sink type + source type + CWE) into one group per root cause.",
|
|
51521
|
+
inputSchema: {
|
|
51522
|
+
findings: string2().describe("JSON array of Finding objects")
|
|
51523
|
+
}
|
|
51524
|
+
}, async ({ findings }) => {
|
|
51525
|
+
let list;
|
|
51526
|
+
try {
|
|
51527
|
+
list = JSON.parse(findings);
|
|
51528
|
+
} catch {
|
|
51529
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
|
|
51530
|
+
}
|
|
51531
|
+
const groups = dedupFindings(list);
|
|
51532
|
+
return { content: [{ type: "text", text: JSON.stringify(groups.map((g) => ({ signature: g.signature, count: g.count, representative: g.representative.id }))) }] };
|
|
51533
|
+
});
|
|
51534
|
+
server.registerTool("generate_report", {
|
|
51535
|
+
title: "Generate a reproducible report",
|
|
51536
|
+
description: "REPORT: emit a deterministic markdown (or JSON) report from canonical Findings, with summary + integrity hash. Reproducible: same findings -> byte-identical output.",
|
|
51537
|
+
inputSchema: {
|
|
51538
|
+
findings: string2().describe("JSON array of Finding objects"),
|
|
51539
|
+
format: _enum(["markdown", "json"]).optional().describe("Report format"),
|
|
51540
|
+
title: string2().optional().describe("Report title"),
|
|
51541
|
+
scope: string2().optional().describe("Scope description")
|
|
51542
|
+
}
|
|
51543
|
+
}, async ({ findings, format, title, scope }) => {
|
|
51544
|
+
let list;
|
|
51545
|
+
try {
|
|
51546
|
+
list = JSON.parse(findings);
|
|
51547
|
+
} catch {
|
|
51548
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
|
|
51549
|
+
}
|
|
51550
|
+
const opts = { title, scope, version: "1.0.23" };
|
|
51551
|
+
const out = (format ?? "markdown") === "json" ? reportJson(list, opts) : reportMarkdown(list, opts);
|
|
51552
|
+
return { content: [{ type: "text", text: out }] };
|
|
51553
|
+
});
|
|
51554
|
+
server.registerTool("run_benchmark", {
|
|
51555
|
+
title: "Run the benchmark suite",
|
|
51556
|
+
description: "BENCHMARK: run the labelled corpus and report detection rate, false-positive rate, and precision. The quality metric, not the tool count.",
|
|
51557
|
+
inputSchema: {}
|
|
51558
|
+
}, async () => {
|
|
51559
|
+
return { content: [{ type: "text", text: JSON.stringify(runBenchmark()) }] };
|
|
51560
|
+
});
|
|
51561
|
+
server.registerTool("coverage_matrix", {
|
|
51562
|
+
title: "Report coverage matrix",
|
|
51563
|
+
description: "BENCHMARK: enumerate language × sink-class coverage (which languages detect which sink types), plus the coverage ratio.",
|
|
51564
|
+
inputSchema: {}
|
|
51565
|
+
}, async () => {
|
|
51566
|
+
return { content: [{ type: "text", text: JSON.stringify(coverageMatrix()) }] };
|
|
51567
|
+
});
|
|
51016
51568
|
return server;
|
|
51017
51569
|
}
|
|
51018
51570
|
async function serve() {
|
|
@@ -51022,12 +51574,12 @@ async function serve() {
|
|
|
51022
51574
|
}
|
|
51023
51575
|
|
|
51024
51576
|
// src/cli.ts
|
|
51025
|
-
import { readFileSync as
|
|
51026
|
-
import { join as
|
|
51577
|
+
import { readFileSync as readFileSync10, existsSync as existsSync5, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
51578
|
+
import { join as join8, dirname } from "node:path";
|
|
51027
51579
|
import { homedir as homedir3 } from "node:os";
|
|
51028
|
-
import { fileURLToPath as
|
|
51580
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
51029
51581
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
51030
|
-
var
|
|
51582
|
+
var ROOT5 = join8(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
51031
51583
|
function which(cmd) {
|
|
51032
51584
|
return spawnSync2(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
51033
51585
|
}
|
|
@@ -51062,7 +51614,7 @@ function runDoctor() {
|
|
|
51062
51614
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
51063
51615
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
51064
51616
|
});
|
|
51065
|
-
const dataOk =
|
|
51617
|
+
const dataOk = existsSync5(join8(ROOT5, "chains.json")) && existsSync5(join8(ROOT5, "tools-catalog.json"));
|
|
51066
51618
|
issues.push({
|
|
51067
51619
|
name: "Data layers (chains + tools-catalog)",
|
|
51068
51620
|
status: dataOk ? "ok" : "fail",
|
|
@@ -51090,21 +51642,21 @@ BlitzStrike — ${verdict}`);
|
|
|
51090
51642
|
function resolveCommand() {
|
|
51091
51643
|
if (which("blitzstrike"))
|
|
51092
51644
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
51093
|
-
const src =
|
|
51094
|
-
if (which("bun") &&
|
|
51645
|
+
const src = join8(ROOT5, "src", "index.ts");
|
|
51646
|
+
if (which("bun") && existsSync5(src))
|
|
51095
51647
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
51096
51648
|
if (which("npx"))
|
|
51097
51649
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51098
51650
|
if (which("bunx"))
|
|
51099
51651
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
51100
|
-
const dist =
|
|
51101
|
-
if (
|
|
51652
|
+
const dist = join8(ROOT5, "dist", "index.js");
|
|
51653
|
+
if (existsSync5(dist))
|
|
51102
51654
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
51103
51655
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51104
51656
|
}
|
|
51105
51657
|
function readJson(p) {
|
|
51106
51658
|
try {
|
|
51107
|
-
return JSON.parse(
|
|
51659
|
+
return JSON.parse(readFileSync10(p, "utf8"));
|
|
51108
51660
|
} catch {
|
|
51109
51661
|
return null;
|
|
51110
51662
|
}
|
|
@@ -51112,7 +51664,7 @@ function readJson(p) {
|
|
|
51112
51664
|
function jsonMcpServersWrite(p) {
|
|
51113
51665
|
return (command, args) => {
|
|
51114
51666
|
const dir = dirname(p);
|
|
51115
|
-
if (!
|
|
51667
|
+
if (!existsSync5(dir))
|
|
51116
51668
|
mkdirSync2(dir, { recursive: true });
|
|
51117
51669
|
const existing = readJson(p) ?? {};
|
|
51118
51670
|
existing.mcpServers = {
|
|
@@ -51125,7 +51677,7 @@ function jsonMcpServersWrite(p) {
|
|
|
51125
51677
|
function opencodeWrite(p) {
|
|
51126
51678
|
return (_command, _args) => {
|
|
51127
51679
|
const dir = dirname(p);
|
|
51128
|
-
if (!
|
|
51680
|
+
if (!existsSync5(dir))
|
|
51129
51681
|
mkdirSync2(dir, { recursive: true });
|
|
51130
51682
|
const existing = readJson(p) ?? {};
|
|
51131
51683
|
existing.mcp = {
|
|
@@ -51137,26 +51689,26 @@ function opencodeWrite(p) {
|
|
|
51137
51689
|
};
|
|
51138
51690
|
}
|
|
51139
51691
|
function copyOpenCodeAgents() {
|
|
51140
|
-
const srcDir =
|
|
51141
|
-
const dstDir =
|
|
51142
|
-
if (!
|
|
51692
|
+
const srcDir = join8(ROOT5, "opencode-agents");
|
|
51693
|
+
const dstDir = join8(homedir3(), ".config", "opencode", "agents");
|
|
51694
|
+
if (!existsSync5(srcDir))
|
|
51143
51695
|
return;
|
|
51144
|
-
if (!
|
|
51696
|
+
if (!existsSync5(dstDir))
|
|
51145
51697
|
mkdirSync2(dstDir, { recursive: true });
|
|
51146
51698
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
51147
|
-
const src =
|
|
51148
|
-
const dst =
|
|
51149
|
-
if (
|
|
51150
|
-
writeFileSync(dst,
|
|
51699
|
+
const src = join8(srcDir, f);
|
|
51700
|
+
const dst = join8(dstDir, f);
|
|
51701
|
+
if (existsSync5(src)) {
|
|
51702
|
+
writeFileSync(dst, readFileSync10(src, "utf8"));
|
|
51151
51703
|
}
|
|
51152
51704
|
}
|
|
51153
51705
|
}
|
|
51154
51706
|
function codexWrite(p) {
|
|
51155
51707
|
return (command, args) => {
|
|
51156
51708
|
const dir = dirname(p);
|
|
51157
|
-
if (!
|
|
51709
|
+
if (!existsSync5(dir))
|
|
51158
51710
|
mkdirSync2(dir, { recursive: true });
|
|
51159
|
-
let existing =
|
|
51711
|
+
let existing = existsSync5(p) ? readFileSync10(p, "utf8") : "";
|
|
51160
51712
|
if (!existing.trimEnd().endsWith(`
|
|
51161
51713
|
`))
|
|
51162
51714
|
existing += `
|
|
@@ -51173,9 +51725,9 @@ args = ${JSON.stringify(args)}
|
|
|
51173
51725
|
function hermesWrite(p) {
|
|
51174
51726
|
return (command, args) => {
|
|
51175
51727
|
const dir = dirname(p);
|
|
51176
|
-
if (!
|
|
51728
|
+
if (!existsSync5(dir))
|
|
51177
51729
|
mkdirSync2(dir, { recursive: true });
|
|
51178
|
-
let existing =
|
|
51730
|
+
let existing = existsSync5(p) ? readFileSync10(p, "utf8") : "";
|
|
51179
51731
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
51180
51732
|
if (!existing.trimEnd().endsWith(`
|
|
51181
51733
|
`))
|
|
@@ -51202,74 +51754,74 @@ mcp_servers:
|
|
|
51202
51754
|
function detectAgents() {
|
|
51203
51755
|
const home = homedir3();
|
|
51204
51756
|
const agents = [];
|
|
51205
|
-
const claudeJson =
|
|
51757
|
+
const claudeJson = join8(home, ".claude.json");
|
|
51206
51758
|
agents.push({
|
|
51207
51759
|
name: "Claude Code",
|
|
51208
51760
|
detectPath: claudeJson,
|
|
51209
|
-
installed: () =>
|
|
51761
|
+
installed: () => existsSync5(claudeJson) || which("claude"),
|
|
51210
51762
|
write: jsonMcpServersWrite(claudeJson)
|
|
51211
51763
|
});
|
|
51212
|
-
const claudeDesktop =
|
|
51764
|
+
const claudeDesktop = join8(home, ".config", "Claude", "claude_desktop_config.json");
|
|
51213
51765
|
agents.push({
|
|
51214
51766
|
name: "Claude Desktop",
|
|
51215
51767
|
detectPath: claudeDesktop,
|
|
51216
|
-
installed: () =>
|
|
51768
|
+
installed: () => existsSync5(claudeDesktop),
|
|
51217
51769
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
51218
51770
|
});
|
|
51219
|
-
const cursor =
|
|
51771
|
+
const cursor = join8(home, ".cursor", "mcp.json");
|
|
51220
51772
|
agents.push({
|
|
51221
51773
|
name: "Cursor",
|
|
51222
51774
|
detectPath: cursor,
|
|
51223
|
-
installed: () =>
|
|
51775
|
+
installed: () => existsSync5(join8(home, ".cursor")) || which("cursor"),
|
|
51224
51776
|
write: jsonMcpServersWrite(cursor)
|
|
51225
51777
|
});
|
|
51226
|
-
const opencode =
|
|
51778
|
+
const opencode = join8(home, ".config", "opencode", "opencode.jsonc");
|
|
51227
51779
|
agents.push({
|
|
51228
51780
|
name: "OpenCode",
|
|
51229
51781
|
detectPath: opencode,
|
|
51230
|
-
installed: () =>
|
|
51782
|
+
installed: () => existsSync5(join8(home, ".config", "opencode")) || which("opencode"),
|
|
51231
51783
|
write: opencodeWrite(opencode)
|
|
51232
51784
|
});
|
|
51233
|
-
const codex =
|
|
51785
|
+
const codex = join8(home, ".codex", "config.toml");
|
|
51234
51786
|
agents.push({
|
|
51235
51787
|
name: "Codex",
|
|
51236
51788
|
detectPath: codex,
|
|
51237
|
-
installed: () =>
|
|
51789
|
+
installed: () => existsSync5(join8(home, ".codex")) || which("codex"),
|
|
51238
51790
|
write: codexWrite(codex)
|
|
51239
51791
|
});
|
|
51240
|
-
const hermes =
|
|
51792
|
+
const hermes = join8(home, ".hermes", "config.yaml");
|
|
51241
51793
|
agents.push({
|
|
51242
51794
|
name: "Hermes",
|
|
51243
51795
|
detectPath: hermes,
|
|
51244
|
-
installed: () =>
|
|
51796
|
+
installed: () => existsSync5(join8(home, ".hermes")) || which("hermes"),
|
|
51245
51797
|
write: hermesWrite(hermes)
|
|
51246
51798
|
});
|
|
51247
|
-
const gemini =
|
|
51799
|
+
const gemini = join8(home, ".gemini", "settings.json");
|
|
51248
51800
|
agents.push({
|
|
51249
51801
|
name: "Gemini CLI",
|
|
51250
51802
|
detectPath: gemini,
|
|
51251
|
-
installed: () =>
|
|
51803
|
+
installed: () => existsSync5(join8(home, ".gemini")) || which("gemini"),
|
|
51252
51804
|
write: jsonMcpServersWrite(gemini)
|
|
51253
51805
|
});
|
|
51254
|
-
const windsurf =
|
|
51806
|
+
const windsurf = join8(home, ".codeium", "windsurf", "mcp_config.json");
|
|
51255
51807
|
agents.push({
|
|
51256
51808
|
name: "Windsurf",
|
|
51257
51809
|
detectPath: windsurf,
|
|
51258
|
-
installed: () =>
|
|
51810
|
+
installed: () => existsSync5(join8(home, ".codeium")) || which("windsurf"),
|
|
51259
51811
|
write: jsonMcpServersWrite(windsurf)
|
|
51260
51812
|
});
|
|
51261
|
-
const copilot =
|
|
51813
|
+
const copilot = join8(home, ".copilot", "mcp.json");
|
|
51262
51814
|
agents.push({
|
|
51263
51815
|
name: "Copilot",
|
|
51264
51816
|
detectPath: copilot,
|
|
51265
|
-
installed: () =>
|
|
51817
|
+
installed: () => existsSync5(join8(home, ".copilot")),
|
|
51266
51818
|
write: jsonMcpServersWrite(copilot)
|
|
51267
51819
|
});
|
|
51268
|
-
const cline =
|
|
51820
|
+
const cline = join8(home, ".cline", "mcp_settings.json");
|
|
51269
51821
|
agents.push({
|
|
51270
51822
|
name: "Cline",
|
|
51271
51823
|
detectPath: cline,
|
|
51272
|
-
installed: () =>
|
|
51824
|
+
installed: () => existsSync5(join8(home, ".cline")),
|
|
51273
51825
|
write: jsonMcpServersWrite(cline)
|
|
51274
51826
|
});
|
|
51275
51827
|
return agents;
|
|
@@ -51313,15 +51865,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
51313
51865
|
}
|
|
51314
51866
|
|
|
51315
51867
|
// src/index.ts
|
|
51316
|
-
import { readFileSync as
|
|
51317
|
-
import { join as
|
|
51318
|
-
import { fileURLToPath as
|
|
51319
|
-
var
|
|
51868
|
+
import { readFileSync as readFileSync12, existsSync as existsSync6 } from "node:fs";
|
|
51869
|
+
import { join as join10 } from "node:path";
|
|
51870
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
51871
|
+
var ROOT7 = join10(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
51320
51872
|
function readVersion() {
|
|
51321
51873
|
try {
|
|
51322
|
-
const p =
|
|
51323
|
-
if (
|
|
51324
|
-
return JSON.parse(
|
|
51874
|
+
const p = join10(ROOT7, "package.json");
|
|
51875
|
+
if (existsSync6(p))
|
|
51876
|
+
return JSON.parse(readFileSync12(p, "utf8")).version ?? "1.0.0";
|
|
51325
51877
|
} catch {}
|
|
51326
51878
|
return "1.0.0";
|
|
51327
51879
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
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": {
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
"serve": "bun run src/index.ts serve",
|
|
30
30
|
"doctor": "bun run src/index.ts doctor",
|
|
31
31
|
"compile": "bun build src/index.ts --compile --outfile dist/blitzstrike",
|
|
32
|
-
"test": "bun test",
|
|
33
|
-
"prepublishOnly": "bun run typecheck && bun run build"
|
|
32
|
+
"test": "bun run test/verify.ts",
|
|
33
|
+
"prepublishOnly": "bun run typecheck && bun run build",
|
|
34
|
+
"bench": "bun run test/verify.ts"
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
37
|
"mcp",
|