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.
Files changed (2) hide show
  1. package/dist/index.js +338 -73
  2. 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 existsSync5 } from "node:fs";
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 (!existsSync5(p))
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 (existsSync5(PACKAGE_PAYLOADS))
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 (existsSync5(PACKAGE_TEMPLATES))
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, file) {
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, file) {
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, file) {
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; the finding is confirmed only when your marker reflects AND " + " the negative control stays inert. 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."
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. A hit is a HYPOTHESIS until the marker reflects.",
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 (urlencoded)"),
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("String that must appear in the response to confirm"),
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
- const res = await fetch(url, {
50293
- method: method ?? "GET",
50294
- body: method === "POST" ? data : undefined,
50295
- headers: { "User-Agent": "blitzstrike/1.0", ...hdrs },
50296
- signal: controller.signal,
50297
- redirect: "follow"
50298
- });
50299
- const body = await res.text();
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 existsSync6, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
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 = existsSync6(join7(ROOT4, "chains.json")) && existsSync6(join7(ROOT4, "tools-catalog.json"));
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") && existsSync6(src))
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 (existsSync6(dist))
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 (!existsSync6(dir))
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 (!existsSync6(dir))
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 (!existsSync6(srcDir))
51213
+ if (!existsSync5(srcDir))
50949
51214
  return;
50950
- if (!existsSync6(dstDir))
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 (existsSync6(src)) {
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 (!existsSync6(dir))
51228
+ if (!existsSync5(dir))
50964
51229
  mkdirSync2(dir, { recursive: true });
50965
- let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
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 (!existsSync6(dir))
51247
+ if (!existsSync5(dir))
50983
51248
  mkdirSync2(dir, { recursive: true });
50984
- let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
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: () => existsSync6(claudeJson) || which("claude"),
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: () => existsSync6(claudeDesktop),
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: () => existsSync6(join7(home, ".cursor")) || which("cursor"),
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: () => existsSync6(join7(home, ".config", "opencode")) || which("opencode"),
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: () => existsSync6(join7(home, ".codex")) || which("codex"),
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: () => existsSync6(join7(home, ".hermes")) || which("hermes"),
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: () => existsSync6(join7(home, ".gemini")) || which("gemini"),
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: () => existsSync6(join7(home, ".codeium")) || which("windsurf"),
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: () => existsSync6(join7(home, ".copilot")),
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: () => existsSync6(join7(home, ".cline")),
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 existsSync8 } from "node:fs";
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 (existsSync8(p))
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.21",
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": {