blitzstrike 1.0.21 → 1.0.23
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 +338 -73
- package/package.json +1 -1
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) {
|
|
@@ -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)) {
|
|
@@ -30017,6 +30037,59 @@ function confidenceFactorsFor(_input) {
|
|
|
30017
30037
|
negative_control: false
|
|
30018
30038
|
};
|
|
30019
30039
|
}
|
|
30040
|
+
function transition(finding, to) {
|
|
30041
|
+
if (!canTransition(finding.status, to)) {
|
|
30042
|
+
throw new Error(`illegal transition ${finding.status} -> ${to}`);
|
|
30043
|
+
}
|
|
30044
|
+
const updated = { ...finding, status: to, timestamps: { ...finding.timestamps, updated: new Date().toISOString() } };
|
|
30045
|
+
return updated;
|
|
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
|
+
}
|
|
30054
|
+
function confirmFinding(finding, opts) {
|
|
30055
|
+
const evidence = opts.evidence.map((e) => makeEvidence(e));
|
|
30056
|
+
const negativeControl = opts.negative_control ?? true;
|
|
30057
|
+
const confidence = computeConfidence({
|
|
30058
|
+
static_analysis: true,
|
|
30059
|
+
data_flow: true,
|
|
30060
|
+
reachability: true,
|
|
30061
|
+
preconditions: true,
|
|
30062
|
+
runtime_validation: true,
|
|
30063
|
+
negative_control: negativeControl
|
|
30064
|
+
});
|
|
30065
|
+
const level = confidenceLevel(confidence);
|
|
30066
|
+
const status = negativeControl ? "confirmed" : "hypothesis";
|
|
30067
|
+
return {
|
|
30068
|
+
...finding,
|
|
30069
|
+
status,
|
|
30070
|
+
confidence,
|
|
30071
|
+
confidence_level: level,
|
|
30072
|
+
validation: {
|
|
30073
|
+
performed: true,
|
|
30074
|
+
status: negativeControl ? "confirmed" : "unconfirmed",
|
|
30075
|
+
baseline: opts.baseline ?? true,
|
|
30076
|
+
negative_control: negativeControl
|
|
30077
|
+
},
|
|
30078
|
+
evidence: [...finding.evidence, ...evidence],
|
|
30079
|
+
timestamps: { ...finding.timestamps, updated: new Date().toISOString() }
|
|
30080
|
+
};
|
|
30081
|
+
}
|
|
30082
|
+
function rejectFinding(finding, status, reason, evidence) {
|
|
30083
|
+
const ev = evidence?.map((e) => makeEvidence(e)) ?? [];
|
|
30084
|
+
const updated = transition(finding, status);
|
|
30085
|
+
return {
|
|
30086
|
+
...updated,
|
|
30087
|
+
validation: { ...updated.validation, performed: false },
|
|
30088
|
+
remediation: { ...updated.remediation, note: reason },
|
|
30089
|
+
evidence: [...updated.evidence, ...ev],
|
|
30090
|
+
timestamps: { ...updated.timestamps, updated: new Date().toISOString() }
|
|
30091
|
+
};
|
|
30092
|
+
}
|
|
30020
30093
|
|
|
30021
30094
|
// src/orchestrator.ts
|
|
30022
30095
|
var _chainsCache = null;
|
|
@@ -30383,7 +30456,7 @@ function ensureTool(name) {
|
|
|
30383
30456
|
}
|
|
30384
30457
|
|
|
30385
30458
|
// src/intel.ts
|
|
30386
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
30459
|
+
import { readFileSync as readFileSync6, existsSync as existsSync4 } from "node:fs";
|
|
30387
30460
|
import { join as join6 } from "node:path";
|
|
30388
30461
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
30389
30462
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
@@ -30392,7 +30465,7 @@ var ROOT3 = join6(fileURLToPath4(new URL(".", import.meta.url)), "..");
|
|
|
30392
30465
|
var INTEL = join6(ROOT3, "intelligence");
|
|
30393
30466
|
function loadJson(name) {
|
|
30394
30467
|
const p = join6(INTEL, `${name}.json`);
|
|
30395
|
-
if (!
|
|
30468
|
+
if (!existsSync4(p))
|
|
30396
30469
|
return null;
|
|
30397
30470
|
try {
|
|
30398
30471
|
return JSON.parse(readFileSync6(p, "utf8"));
|
|
@@ -30408,7 +30481,6 @@ function detectWaf(headers, body = "") {
|
|
|
30408
30481
|
const bodySigs = sigs.body_signatures ?? [];
|
|
30409
30482
|
const matches = [];
|
|
30410
30483
|
const headerKeys = Object.keys(headers).map((k) => k.toLowerCase());
|
|
30411
|
-
const headerVals = Object.values(headers).join(" ").toLowerCase();
|
|
30412
30484
|
for (const s of headerSigs) {
|
|
30413
30485
|
const h = (s.header ?? "").toLowerCase();
|
|
30414
30486
|
const pattern = (s.pattern ?? "").toLowerCase();
|
|
@@ -30507,12 +30579,12 @@ var PACKAGE_PAYLOADS = join6(ROOT3, "payloads");
|
|
|
30507
30579
|
var PACKAGE_TEMPLATES = join6(ROOT3, "templates");
|
|
30508
30580
|
var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join6(homedir2(), ".blitzstrike", "data");
|
|
30509
30581
|
function payloadsDir() {
|
|
30510
|
-
if (
|
|
30582
|
+
if (existsSync4(PACKAGE_PAYLOADS))
|
|
30511
30583
|
return PACKAGE_PAYLOADS;
|
|
30512
30584
|
return join6(DATA_ROOT, "payloads");
|
|
30513
30585
|
}
|
|
30514
30586
|
function templatesDir() {
|
|
30515
|
-
if (
|
|
30587
|
+
if (existsSync4(PACKAGE_TEMPLATES))
|
|
30516
30588
|
return PACKAGE_TEMPLATES;
|
|
30517
30589
|
return join6(DATA_ROOT, "templates");
|
|
30518
30590
|
}
|
|
@@ -31086,7 +31158,6 @@ function walkExpression(node, env, summaries, findings, suppressed, file) {
|
|
|
31086
31158
|
const sanitizers = tainted.flatMap((t) => t.sanitizers);
|
|
31087
31159
|
const inlineSan = findSanitizers(JSON.stringify(node));
|
|
31088
31160
|
const san = [...sanitizers, ...inlineSan].filter((s, i, arr) => arr.indexOf(s) === i);
|
|
31089
|
-
const authGated = false;
|
|
31090
31161
|
const cls = sink.cls;
|
|
31091
31162
|
if (!isSanitized(san, cls.id)) {
|
|
31092
31163
|
findings.push({
|
|
@@ -49357,7 +49428,6 @@ var phpAdapter = {
|
|
|
49357
49428
|
}
|
|
49358
49429
|
}
|
|
49359
49430
|
}
|
|
49360
|
-
const superSources = new Map;
|
|
49361
49431
|
for (const n of nodes) {
|
|
49362
49432
|
if (n.kind === "assign") {
|
|
49363
49433
|
const target = phpVar(n.left);
|
|
@@ -49430,7 +49500,7 @@ function jsWalk(node, out) {
|
|
|
49430
49500
|
var jsAdapter = {
|
|
49431
49501
|
language: "javascript",
|
|
49432
49502
|
extensions: [".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx"],
|
|
49433
|
-
parse(code,
|
|
49503
|
+
parse(code, _file) {
|
|
49434
49504
|
let ast;
|
|
49435
49505
|
try {
|
|
49436
49506
|
ast = parse6(code, {
|
|
@@ -49539,7 +49609,7 @@ var PY_SANITIZERS = [
|
|
|
49539
49609
|
var pythonAdapter = {
|
|
49540
49610
|
language: "python",
|
|
49541
49611
|
extensions: [".py", ".pyw"],
|
|
49542
|
-
parse(code,
|
|
49612
|
+
parse(code, _file) {
|
|
49543
49613
|
const tree = parser2.parse(code);
|
|
49544
49614
|
const sources = [];
|
|
49545
49615
|
const sinks = [];
|
|
@@ -49657,7 +49727,7 @@ var JAVA_SANITIZERS = [
|
|
|
49657
49727
|
var javaAdapter = {
|
|
49658
49728
|
language: "java",
|
|
49659
49729
|
extensions: [".java"],
|
|
49660
|
-
parse(code,
|
|
49730
|
+
parse(code, _file) {
|
|
49661
49731
|
const sources = [];
|
|
49662
49732
|
const sinks = [];
|
|
49663
49733
|
const sanitizers = [];
|
|
@@ -50184,13 +50254,151 @@ async function liveRecon(target, includeActive = false) {
|
|
|
50184
50254
|
};
|
|
50185
50255
|
}
|
|
50186
50256
|
|
|
50257
|
+
// src/strike.ts
|
|
50258
|
+
var DEFAULT_TIMEOUT_MS = 15000;
|
|
50259
|
+
async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
50260
|
+
const controller = new AbortController;
|
|
50261
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
50262
|
+
try {
|
|
50263
|
+
const res = await fetch(url, {
|
|
50264
|
+
method,
|
|
50265
|
+
body: method === "POST" ? data : undefined,
|
|
50266
|
+
headers: { "User-Agent": "blitzstrike/1.0", ...headers ?? {} },
|
|
50267
|
+
signal: controller.signal,
|
|
50268
|
+
redirect: "follow"
|
|
50269
|
+
});
|
|
50270
|
+
const body = await res.text();
|
|
50271
|
+
const hdrs = {};
|
|
50272
|
+
res.headers.forEach((v, k) => {
|
|
50273
|
+
hdrs[k] = v;
|
|
50274
|
+
});
|
|
50275
|
+
return { status: res.status, body: body.slice(0, 20000), headers: hdrs };
|
|
50276
|
+
} catch (e) {
|
|
50277
|
+
return { status: 0, body: String(e), headers: {} };
|
|
50278
|
+
} finally {
|
|
50279
|
+
clearTimeout(t);
|
|
50280
|
+
}
|
|
50281
|
+
}
|
|
50282
|
+
function defaultMarker() {
|
|
50283
|
+
return `BS${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
|
|
50284
|
+
}
|
|
50285
|
+
function inject(target, value, param) {
|
|
50286
|
+
const placeholder = "{{MARKER}}";
|
|
50287
|
+
if (target.includes(placeholder))
|
|
50288
|
+
return target.split(placeholder).join(value);
|
|
50289
|
+
if (target.includes("?"))
|
|
50290
|
+
return `${target}&${param}=${encodeURIComponent(value)}`;
|
|
50291
|
+
return `${target}?${param}=${encodeURIComponent(value)}`;
|
|
50292
|
+
}
|
|
50293
|
+
async function strikeVerify(input) {
|
|
50294
|
+
const method = input.method ?? "GET";
|
|
50295
|
+
const param = input.param ?? "q";
|
|
50296
|
+
const marker = input.marker ?? defaultMarker();
|
|
50297
|
+
const control = input.control ?? defaultMarker();
|
|
50298
|
+
const baseUrl = input.url;
|
|
50299
|
+
const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
50300
|
+
const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
|
|
50301
|
+
const markerTarget = inject(baseUrl, marker, param);
|
|
50302
|
+
const controlTarget = inject(baseUrl, control, param);
|
|
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);
|
|
50306
|
+
if (baseline.status === 0 || markerResp.status === 0) {
|
|
50307
|
+
return {
|
|
50308
|
+
status: "blocked",
|
|
50309
|
+
marker_reflected: false,
|
|
50310
|
+
control_reflected: false,
|
|
50311
|
+
baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
|
|
50312
|
+
marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
|
|
50313
|
+
control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
|
|
50314
|
+
reason: "request failed or target unreachable — cannot validate",
|
|
50315
|
+
evidence: []
|
|
50316
|
+
};
|
|
50317
|
+
}
|
|
50318
|
+
const markerReflected = markerResp.body.includes(marker);
|
|
50319
|
+
const controlReflected = controlResp.body.includes(control);
|
|
50320
|
+
let status;
|
|
50321
|
+
let reason;
|
|
50322
|
+
if (markerReflected && !controlReflected) {
|
|
50323
|
+
status = "confirmed";
|
|
50324
|
+
reason = "marker reflected; negative control did not — the sink reflects attacker-controlled input (real finding)";
|
|
50325
|
+
} else if (markerReflected && controlReflected) {
|
|
50326
|
+
status = "false_positive";
|
|
50327
|
+
reason = "marker AND negative control both reflected — behaviour is indistinguishable from benign reflection (likely false positive)";
|
|
50328
|
+
} else if (!markerReflected && !controlReflected) {
|
|
50329
|
+
status = "unconfirmed";
|
|
50330
|
+
reason = "marker not reflected — reachability/exploitability could not be demonstrated live";
|
|
50331
|
+
} else {
|
|
50332
|
+
status = "unconfirmed";
|
|
50333
|
+
reason = "negative control reflected but marker did not — inconsistent, requires deeper analysis";
|
|
50334
|
+
}
|
|
50335
|
+
const evidence = [
|
|
50336
|
+
{
|
|
50337
|
+
type: "baseline_comparison",
|
|
50338
|
+
description: `Baseline response (status ${baseline.status}) for ${redactSecrets(baselineTarget)}`,
|
|
50339
|
+
artifacts: [{ name: "baseline", kind: "http_response", content: `${baseline.status}
|
|
50340
|
+
${baseline.body}` }]
|
|
50341
|
+
},
|
|
50342
|
+
{
|
|
50343
|
+
type: "validation_result",
|
|
50344
|
+
description: `Marker '${redactSecrets(marker)}' reflected=${markerReflected}; control reflected=${controlReflected}`,
|
|
50345
|
+
artifacts: [{ name: "marker_response", kind: "http_response", content: `${markerResp.status}
|
|
50346
|
+
${markerResp.body}` }]
|
|
50347
|
+
},
|
|
50348
|
+
{
|
|
50349
|
+
type: "negative_control",
|
|
50350
|
+
description: `Negative control '${redactSecrets(control)}' reflected=${controlReflected}`,
|
|
50351
|
+
artifacts: [{ name: "control_response", kind: "http_response", content: `${controlResp.status}
|
|
50352
|
+
${controlResp.body}` }]
|
|
50353
|
+
}
|
|
50354
|
+
];
|
|
50355
|
+
return {
|
|
50356
|
+
status,
|
|
50357
|
+
marker_reflected: markerReflected,
|
|
50358
|
+
control_reflected: controlReflected,
|
|
50359
|
+
baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
|
|
50360
|
+
marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
|
|
50361
|
+
control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
|
|
50362
|
+
reason,
|
|
50363
|
+
evidence
|
|
50364
|
+
};
|
|
50365
|
+
}
|
|
50366
|
+
function resolveFinding(finding, verdict) {
|
|
50367
|
+
let f = finding;
|
|
50368
|
+
if (f.status === "hypothesis") {
|
|
50369
|
+
f = transition(f, "validating");
|
|
50370
|
+
}
|
|
50371
|
+
switch (verdict.status) {
|
|
50372
|
+
case "confirmed":
|
|
50373
|
+
return confirmFinding(f, {
|
|
50374
|
+
evidence: verdict.evidence,
|
|
50375
|
+
negative_control: true,
|
|
50376
|
+
baseline: true
|
|
50377
|
+
});
|
|
50378
|
+
case "false_positive":
|
|
50379
|
+
return rejectFinding(f, "false_positive", verdict.reason, verdict.evidence);
|
|
50380
|
+
case "blocked":
|
|
50381
|
+
return rejectFinding(f, "blocked", verdict.reason, verdict.evidence);
|
|
50382
|
+
case "unconfirmed":
|
|
50383
|
+
case "likely":
|
|
50384
|
+
default: {
|
|
50385
|
+
return {
|
|
50386
|
+
...f,
|
|
50387
|
+
validation: { performed: true, status: verdict.status === "likely" ? "likely" : "unconfirmed", negative_control: verdict.control_reflected === false, baseline: true },
|
|
50388
|
+
remediation: { ...f.remediation, note: verdict.reason },
|
|
50389
|
+
timestamps: { ...f.timestamps, updated: new Date().toISOString() }
|
|
50390
|
+
};
|
|
50391
|
+
}
|
|
50392
|
+
}
|
|
50393
|
+
}
|
|
50394
|
+
|
|
50187
50395
|
// src/server.ts
|
|
50188
50396
|
function createServer() {
|
|
50189
50397
|
const server = new McpServer({
|
|
50190
50398
|
name: "blitzstrike",
|
|
50191
50399
|
version: "1.0.0"
|
|
50192
50400
|
}, {
|
|
50193
|
-
instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter gated alternative. Both re-check scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates).\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL
|
|
50401
|
+
instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter gated alternative. Both re-check scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates).\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL plus a BASELINE. The verdict is deterministic: marker " + " reflected AND control inert = confirmed; both reflected = false_positive; " + " marker not reflected = unconfirmed. Feed the verdict to strike_resolve to " + " advance the finding lifecycle (hypothesis->validating->confirmed). Use " + " read_tool_manual for the exploit tool's manual, payload_lookup for payloads, " + " detect_waf/tech_correlation for context.\\n" + "5. RECORD + REPORT. Create a canonical finding with finding_create, advance it " + " through the lifecycle with finding_transition, attach confidence_score, and " + " redact any secrets before persisting. Only report findings that survived " + " verification.\\n\\n" + "SUPPORTING LAYERS:\\n" + "- run_engagement: full one-call audit (scope gate -> triage -> chain enrichment " + " -> findings with tool manuals attached).\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- skill_lookup / read_skill / list_skills: 85 playbooks (WP/PHP 0-day, AD, " + " malware, mobile, cloud, etc.).\\n" + "- read_playbook / read_tool_manual / list_manuals: 270 tool manuals + engagement " + " playbooks.\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "IRON RULES:\\n" + "- Scope first; authorized targets only. Refuse out-of-scope or destructive/DoS work.\\n" + "- A hit is a hypothesis; a verified exploit (or proven data-flow) is the finding.\\n" + "- Prefer the one-call run_engagement path; fall back to granular tools when you " + " need finer control."
|
|
50194
50402
|
});
|
|
50195
50403
|
server.registerTool("blitz_scan", {
|
|
50196
50404
|
title: "BLITZ scan",
|
|
@@ -50272,55 +50480,67 @@ function createServer() {
|
|
|
50272
50480
|
});
|
|
50273
50481
|
server.registerTool("strike_verify", {
|
|
50274
50482
|
title: "STRIKE live verification",
|
|
50275
|
-
description: "STRIKE: live HTTP verification with marker + negative control.
|
|
50483
|
+
description: "STRIKE: live HTTP verification with marker + negative control + baseline. Verdict: confirmed (marker reflected, control NOT) / false_positive (both reflected) / unconfirmed (marker not reflected) / blocked. Returns redacted SHA-256-tagged evidence. " + "USE WHEN: you have a hypothesis and need to prove (or refute) it live. " + "NEXT: feed the verdict to strike_resolve to advance the finding's lifecycle.",
|
|
50276
50484
|
inputSchema: {
|
|
50277
50485
|
url: string2().describe("Target URL"),
|
|
50278
50486
|
method: _enum(["GET", "POST"]).optional().describe("HTTP method"),
|
|
50279
|
-
data: string2().optional().describe("POST body
|
|
50487
|
+
data: string2().optional().describe("POST body template; a literal {{MARKER}} is replaced"),
|
|
50280
50488
|
headers: string2().optional().describe("JSON object of extra headers"),
|
|
50281
|
-
marker: string2().optional().describe("
|
|
50489
|
+
marker: string2().optional().describe("Unique string the payload should reflect (defaults to a random token)"),
|
|
50490
|
+
control: string2().optional().describe("Benign lookalike for the negative control (defaults to a random token)"),
|
|
50491
|
+
param: string2().optional().describe("Query/body param name to inject into (default 'q')"),
|
|
50282
50492
|
timeout: number2().int().optional().describe("Timeout seconds (default 15)")
|
|
50283
50493
|
}
|
|
50284
|
-
}, async ({ url, method, data, headers, marker, timeout }) => {
|
|
50285
|
-
const controller = new AbortController;
|
|
50286
|
-
const t = setTimeout(() => controller.abort(), (timeout ?? 15) * 1000);
|
|
50494
|
+
}, async ({ url, method, data, headers, marker, control, param, timeout }) => {
|
|
50287
50495
|
let hdrs = {};
|
|
50288
50496
|
try {
|
|
50289
50497
|
hdrs = headers ? JSON.parse(headers) : {};
|
|
50290
50498
|
} catch {}
|
|
50499
|
+
const verdict = await strikeVerify({
|
|
50500
|
+
url,
|
|
50501
|
+
method: method ?? "GET",
|
|
50502
|
+
data,
|
|
50503
|
+
headers: hdrs,
|
|
50504
|
+
marker,
|
|
50505
|
+
control,
|
|
50506
|
+
param,
|
|
50507
|
+
timeout: timeout ? timeout * 1000 : undefined
|
|
50508
|
+
});
|
|
50509
|
+
return {
|
|
50510
|
+
content: [{
|
|
50511
|
+
type: "text",
|
|
50512
|
+
text: JSON.stringify({
|
|
50513
|
+
...verdict,
|
|
50514
|
+
next_steps: [
|
|
50515
|
+
`Verdict: ${verdict.status} — ${verdict.reason}`,
|
|
50516
|
+
verdict.status === "confirmed" ? "Confirmed. Attach to the finding with strike_resolve, then record evidence (finding_create already carried SHA-256 artifacts)." : verdict.status === "false_positive" ? "False positive. Reject the finding via strike_resolve to close it out." : "Not confirmed. Refine the payload or trace reachability (taint_file / eagle_eye) before re-validating."
|
|
50517
|
+
]
|
|
50518
|
+
})
|
|
50519
|
+
}]
|
|
50520
|
+
};
|
|
50521
|
+
});
|
|
50522
|
+
server.registerTool("strike_resolve", {
|
|
50523
|
+
title: "Resolve a finding from a STRIKE verdict",
|
|
50524
|
+
description: "STRIKE+FINDINGS: take a canonical Finding and a STRIKE verdict, and advance the lifecycle end-to-end — hypothesis->validating->confirmed (marker reflected, control inert) or ->false_positive/blocked/unconfirmed. Returns the updated finding with evidence + recomputed confidence.",
|
|
50525
|
+
inputSchema: {
|
|
50526
|
+
finding: string2().describe("JSON of the canonical Finding object (from finding_create)"),
|
|
50527
|
+
verdict: string2().describe("JSON of the STRIKE verdict (from strike_verify)")
|
|
50528
|
+
}
|
|
50529
|
+
}, async ({ finding, verdict }) => {
|
|
50530
|
+
let f;
|
|
50531
|
+
let v;
|
|
50291
50532
|
try {
|
|
50292
|
-
|
|
50293
|
-
|
|
50294
|
-
|
|
50295
|
-
|
|
50296
|
-
|
|
50297
|
-
|
|
50298
|
-
|
|
50299
|
-
|
|
50300
|
-
const reflected = marker ? body.includes(marker) : true;
|
|
50301
|
-
return {
|
|
50302
|
-
content: [
|
|
50303
|
-
{
|
|
50304
|
-
type: "text",
|
|
50305
|
-
text: JSON.stringify({
|
|
50306
|
-
url,
|
|
50307
|
-
status: res.status,
|
|
50308
|
-
marker,
|
|
50309
|
-
marker_reflected: reflected,
|
|
50310
|
-
body_preview: body.slice(0, 800)
|
|
50311
|
-
})
|
|
50312
|
-
}
|
|
50313
|
-
]
|
|
50314
|
-
};
|
|
50315
|
-
} catch (e) {
|
|
50316
|
-
return {
|
|
50317
|
-
content: [
|
|
50318
|
-
{ type: "text", text: JSON.stringify({ url, error: String(e) }) }
|
|
50319
|
-
]
|
|
50320
|
-
};
|
|
50321
|
-
} finally {
|
|
50322
|
-
clearTimeout(t);
|
|
50533
|
+
f = JSON.parse(finding);
|
|
50534
|
+
} catch {
|
|
50535
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON" }) }] };
|
|
50536
|
+
}
|
|
50537
|
+
try {
|
|
50538
|
+
v = JSON.parse(verdict);
|
|
50539
|
+
} catch {
|
|
50540
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid verdict JSON" }) }] };
|
|
50323
50541
|
}
|
|
50542
|
+
const updated = resolveFinding(f, v);
|
|
50543
|
+
return { content: [{ type: "text", text: JSON.stringify(updated) }] };
|
|
50324
50544
|
});
|
|
50325
50545
|
server.registerTool("scope_check", {
|
|
50326
50546
|
title: "STRIKE scope enforcement",
|
|
@@ -50509,6 +50729,13 @@ function createServer() {
|
|
|
50509
50729
|
}, async () => {
|
|
50510
50730
|
return { content: [{ type: "text", text: JSON.stringify(listMemory()) }] };
|
|
50511
50731
|
});
|
|
50732
|
+
server.registerTool("memory_forget", {
|
|
50733
|
+
title: "Forget a memory entry",
|
|
50734
|
+
description: "MEMORY: remove a memory entry by id (append-only store; 'forget' = tombstone).",
|
|
50735
|
+
inputSchema: { id: string2().describe("Memory entry id (from memory_list)") }
|
|
50736
|
+
}, async ({ id }) => {
|
|
50737
|
+
return { content: [{ type: "text", text: JSON.stringify(forget(id)) }] };
|
|
50738
|
+
});
|
|
50512
50739
|
server.registerTool("read_tool_manual", {
|
|
50513
50740
|
title: "Read a tool manual",
|
|
50514
50741
|
description: "Read the full deep reference manual for a security tool (270+ manuals from kali-pentest, Apache-2.0).",
|
|
@@ -50692,6 +50919,44 @@ function createServer() {
|
|
|
50692
50919
|
const score = computeConfidence(factors);
|
|
50693
50920
|
return { content: [{ type: "text", text: JSON.stringify({ score, level: confidenceLevel(score) }) }] };
|
|
50694
50921
|
});
|
|
50922
|
+
server.registerTool("confidence_weights", {
|
|
50923
|
+
title: "Get/set confidence weights",
|
|
50924
|
+
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.",
|
|
50925
|
+
inputSchema: {
|
|
50926
|
+
set: string2().optional().describe('JSON object of weight overrides (e.g. {"data_flow":0.3})')
|
|
50927
|
+
}
|
|
50928
|
+
}, async ({ set }) => {
|
|
50929
|
+
if (set) {
|
|
50930
|
+
let w;
|
|
50931
|
+
try {
|
|
50932
|
+
w = JSON.parse(set);
|
|
50933
|
+
} catch {
|
|
50934
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
|
|
50935
|
+
}
|
|
50936
|
+
setConfidenceWeights(w);
|
|
50937
|
+
}
|
|
50938
|
+
return { content: [{ type: "text", text: JSON.stringify({ weights: getConfidenceWeights() }) }] };
|
|
50939
|
+
});
|
|
50940
|
+
server.registerTool("finding_attach_evidence", {
|
|
50941
|
+
title: "Attach evidence to a finding",
|
|
50942
|
+
description: "FINDINGS: attach a redacted, SHA-256-tagged evidence record to an existing canonical finding (without advancing its lifecycle).",
|
|
50943
|
+
inputSchema: {
|
|
50944
|
+
finding: string2().describe("JSON of the Finding object"),
|
|
50945
|
+
type: string2().describe("Evidence type (source_location/response/validation_result/negative_control/...)"),
|
|
50946
|
+
description: string2().describe("Evidence description"),
|
|
50947
|
+
content: string2().optional().describe("Evidence artifact content")
|
|
50948
|
+
}
|
|
50949
|
+
}, async ({ finding, type, description, content }) => {
|
|
50950
|
+
let f;
|
|
50951
|
+
try {
|
|
50952
|
+
f = JSON.parse(finding);
|
|
50953
|
+
} catch {
|
|
50954
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON" }) }] };
|
|
50955
|
+
}
|
|
50956
|
+
const ev = makeEvidence({ type, description, artifacts: content ? [{ name: "artifact", kind: "evidence", content }] : [] });
|
|
50957
|
+
const updated = attachEvidence(f, ev);
|
|
50958
|
+
return { content: [{ type: "text", text: JSON.stringify(updated) }] };
|
|
50959
|
+
});
|
|
50695
50960
|
server.registerTool("redact", {
|
|
50696
50961
|
title: "Redact secrets from text",
|
|
50697
50962
|
description: "EVIDENCE: redact passwords/API keys/tokens/cookies/private keys from text before persisting evidence or reporting.",
|
|
@@ -50828,7 +51093,7 @@ async function serve() {
|
|
|
50828
51093
|
}
|
|
50829
51094
|
|
|
50830
51095
|
// src/cli.ts
|
|
50831
|
-
import { readFileSync as readFileSync9, existsSync as
|
|
51096
|
+
import { readFileSync as readFileSync9, existsSync as existsSync5, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
50832
51097
|
import { join as join7, dirname } from "node:path";
|
|
50833
51098
|
import { homedir as homedir3 } from "node:os";
|
|
50834
51099
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -50868,7 +51133,7 @@ function runDoctor() {
|
|
|
50868
51133
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
50869
51134
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
50870
51135
|
});
|
|
50871
|
-
const dataOk =
|
|
51136
|
+
const dataOk = existsSync5(join7(ROOT4, "chains.json")) && existsSync5(join7(ROOT4, "tools-catalog.json"));
|
|
50872
51137
|
issues.push({
|
|
50873
51138
|
name: "Data layers (chains + tools-catalog)",
|
|
50874
51139
|
status: dataOk ? "ok" : "fail",
|
|
@@ -50897,14 +51162,14 @@ function resolveCommand() {
|
|
|
50897
51162
|
if (which("blitzstrike"))
|
|
50898
51163
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
50899
51164
|
const src = join7(ROOT4, "src", "index.ts");
|
|
50900
|
-
if (which("bun") &&
|
|
51165
|
+
if (which("bun") && existsSync5(src))
|
|
50901
51166
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
50902
51167
|
if (which("npx"))
|
|
50903
51168
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
50904
51169
|
if (which("bunx"))
|
|
50905
51170
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
50906
51171
|
const dist = join7(ROOT4, "dist", "index.js");
|
|
50907
|
-
if (
|
|
51172
|
+
if (existsSync5(dist))
|
|
50908
51173
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
50909
51174
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
50910
51175
|
}
|
|
@@ -50918,7 +51183,7 @@ function readJson(p) {
|
|
|
50918
51183
|
function jsonMcpServersWrite(p) {
|
|
50919
51184
|
return (command, args) => {
|
|
50920
51185
|
const dir = dirname(p);
|
|
50921
|
-
if (!
|
|
51186
|
+
if (!existsSync5(dir))
|
|
50922
51187
|
mkdirSync2(dir, { recursive: true });
|
|
50923
51188
|
const existing = readJson(p) ?? {};
|
|
50924
51189
|
existing.mcpServers = {
|
|
@@ -50931,7 +51196,7 @@ function jsonMcpServersWrite(p) {
|
|
|
50931
51196
|
function opencodeWrite(p) {
|
|
50932
51197
|
return (_command, _args) => {
|
|
50933
51198
|
const dir = dirname(p);
|
|
50934
|
-
if (!
|
|
51199
|
+
if (!existsSync5(dir))
|
|
50935
51200
|
mkdirSync2(dir, { recursive: true });
|
|
50936
51201
|
const existing = readJson(p) ?? {};
|
|
50937
51202
|
existing.mcp = {
|
|
@@ -50945,14 +51210,14 @@ function opencodeWrite(p) {
|
|
|
50945
51210
|
function copyOpenCodeAgents() {
|
|
50946
51211
|
const srcDir = join7(ROOT4, "opencode-agents");
|
|
50947
51212
|
const dstDir = join7(homedir3(), ".config", "opencode", "agents");
|
|
50948
|
-
if (!
|
|
51213
|
+
if (!existsSync5(srcDir))
|
|
50949
51214
|
return;
|
|
50950
|
-
if (!
|
|
51215
|
+
if (!existsSync5(dstDir))
|
|
50951
51216
|
mkdirSync2(dstDir, { recursive: true });
|
|
50952
51217
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
50953
51218
|
const src = join7(srcDir, f);
|
|
50954
51219
|
const dst = join7(dstDir, f);
|
|
50955
|
-
if (
|
|
51220
|
+
if (existsSync5(src)) {
|
|
50956
51221
|
writeFileSync(dst, readFileSync9(src, "utf8"));
|
|
50957
51222
|
}
|
|
50958
51223
|
}
|
|
@@ -50960,9 +51225,9 @@ function copyOpenCodeAgents() {
|
|
|
50960
51225
|
function codexWrite(p) {
|
|
50961
51226
|
return (command, args) => {
|
|
50962
51227
|
const dir = dirname(p);
|
|
50963
|
-
if (!
|
|
51228
|
+
if (!existsSync5(dir))
|
|
50964
51229
|
mkdirSync2(dir, { recursive: true });
|
|
50965
|
-
let existing =
|
|
51230
|
+
let existing = existsSync5(p) ? readFileSync9(p, "utf8") : "";
|
|
50966
51231
|
if (!existing.trimEnd().endsWith(`
|
|
50967
51232
|
`))
|
|
50968
51233
|
existing += `
|
|
@@ -50979,9 +51244,9 @@ args = ${JSON.stringify(args)}
|
|
|
50979
51244
|
function hermesWrite(p) {
|
|
50980
51245
|
return (command, args) => {
|
|
50981
51246
|
const dir = dirname(p);
|
|
50982
|
-
if (!
|
|
51247
|
+
if (!existsSync5(dir))
|
|
50983
51248
|
mkdirSync2(dir, { recursive: true });
|
|
50984
|
-
let existing =
|
|
51249
|
+
let existing = existsSync5(p) ? readFileSync9(p, "utf8") : "";
|
|
50985
51250
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
50986
51251
|
if (!existing.trimEnd().endsWith(`
|
|
50987
51252
|
`))
|
|
@@ -51012,70 +51277,70 @@ function detectAgents() {
|
|
|
51012
51277
|
agents.push({
|
|
51013
51278
|
name: "Claude Code",
|
|
51014
51279
|
detectPath: claudeJson,
|
|
51015
|
-
installed: () =>
|
|
51280
|
+
installed: () => existsSync5(claudeJson) || which("claude"),
|
|
51016
51281
|
write: jsonMcpServersWrite(claudeJson)
|
|
51017
51282
|
});
|
|
51018
51283
|
const claudeDesktop = join7(home, ".config", "Claude", "claude_desktop_config.json");
|
|
51019
51284
|
agents.push({
|
|
51020
51285
|
name: "Claude Desktop",
|
|
51021
51286
|
detectPath: claudeDesktop,
|
|
51022
|
-
installed: () =>
|
|
51287
|
+
installed: () => existsSync5(claudeDesktop),
|
|
51023
51288
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
51024
51289
|
});
|
|
51025
51290
|
const cursor = join7(home, ".cursor", "mcp.json");
|
|
51026
51291
|
agents.push({
|
|
51027
51292
|
name: "Cursor",
|
|
51028
51293
|
detectPath: cursor,
|
|
51029
|
-
installed: () =>
|
|
51294
|
+
installed: () => existsSync5(join7(home, ".cursor")) || which("cursor"),
|
|
51030
51295
|
write: jsonMcpServersWrite(cursor)
|
|
51031
51296
|
});
|
|
51032
51297
|
const opencode = join7(home, ".config", "opencode", "opencode.jsonc");
|
|
51033
51298
|
agents.push({
|
|
51034
51299
|
name: "OpenCode",
|
|
51035
51300
|
detectPath: opencode,
|
|
51036
|
-
installed: () =>
|
|
51301
|
+
installed: () => existsSync5(join7(home, ".config", "opencode")) || which("opencode"),
|
|
51037
51302
|
write: opencodeWrite(opencode)
|
|
51038
51303
|
});
|
|
51039
51304
|
const codex = join7(home, ".codex", "config.toml");
|
|
51040
51305
|
agents.push({
|
|
51041
51306
|
name: "Codex",
|
|
51042
51307
|
detectPath: codex,
|
|
51043
|
-
installed: () =>
|
|
51308
|
+
installed: () => existsSync5(join7(home, ".codex")) || which("codex"),
|
|
51044
51309
|
write: codexWrite(codex)
|
|
51045
51310
|
});
|
|
51046
51311
|
const hermes = join7(home, ".hermes", "config.yaml");
|
|
51047
51312
|
agents.push({
|
|
51048
51313
|
name: "Hermes",
|
|
51049
51314
|
detectPath: hermes,
|
|
51050
|
-
installed: () =>
|
|
51315
|
+
installed: () => existsSync5(join7(home, ".hermes")) || which("hermes"),
|
|
51051
51316
|
write: hermesWrite(hermes)
|
|
51052
51317
|
});
|
|
51053
51318
|
const gemini = join7(home, ".gemini", "settings.json");
|
|
51054
51319
|
agents.push({
|
|
51055
51320
|
name: "Gemini CLI",
|
|
51056
51321
|
detectPath: gemini,
|
|
51057
|
-
installed: () =>
|
|
51322
|
+
installed: () => existsSync5(join7(home, ".gemini")) || which("gemini"),
|
|
51058
51323
|
write: jsonMcpServersWrite(gemini)
|
|
51059
51324
|
});
|
|
51060
51325
|
const windsurf = join7(home, ".codeium", "windsurf", "mcp_config.json");
|
|
51061
51326
|
agents.push({
|
|
51062
51327
|
name: "Windsurf",
|
|
51063
51328
|
detectPath: windsurf,
|
|
51064
|
-
installed: () =>
|
|
51329
|
+
installed: () => existsSync5(join7(home, ".codeium")) || which("windsurf"),
|
|
51065
51330
|
write: jsonMcpServersWrite(windsurf)
|
|
51066
51331
|
});
|
|
51067
51332
|
const copilot = join7(home, ".copilot", "mcp.json");
|
|
51068
51333
|
agents.push({
|
|
51069
51334
|
name: "Copilot",
|
|
51070
51335
|
detectPath: copilot,
|
|
51071
|
-
installed: () =>
|
|
51336
|
+
installed: () => existsSync5(join7(home, ".copilot")),
|
|
51072
51337
|
write: jsonMcpServersWrite(copilot)
|
|
51073
51338
|
});
|
|
51074
51339
|
const cline = join7(home, ".cline", "mcp_settings.json");
|
|
51075
51340
|
agents.push({
|
|
51076
51341
|
name: "Cline",
|
|
51077
51342
|
detectPath: cline,
|
|
51078
|
-
installed: () =>
|
|
51343
|
+
installed: () => existsSync5(join7(home, ".cline")),
|
|
51079
51344
|
write: jsonMcpServersWrite(cline)
|
|
51080
51345
|
});
|
|
51081
51346
|
return agents;
|
|
@@ -51119,14 +51384,14 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
51119
51384
|
}
|
|
51120
51385
|
|
|
51121
51386
|
// src/index.ts
|
|
51122
|
-
import { readFileSync as readFileSync11, existsSync as
|
|
51387
|
+
import { readFileSync as readFileSync11, existsSync as existsSync6 } from "node:fs";
|
|
51123
51388
|
import { join as join9 } from "node:path";
|
|
51124
51389
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
51125
51390
|
var ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
51126
51391
|
function readVersion() {
|
|
51127
51392
|
try {
|
|
51128
51393
|
const p = join9(ROOT6, "package.json");
|
|
51129
|
-
if (
|
|
51394
|
+
if (existsSync6(p))
|
|
51130
51395
|
return JSON.parse(readFileSync11(p, "utf8")).version ?? "1.0.0";
|
|
51131
51396
|
} catch {}
|
|
51132
51397
|
return "1.0.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
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": {
|