blitzstrike 1.0.21 → 1.0.22

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 +232 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30017,6 +30017,52 @@ function confidenceFactorsFor(_input) {
30017
30017
  negative_control: false
30018
30018
  };
30019
30019
  }
30020
+ function transition(finding, to) {
30021
+ if (!canTransition(finding.status, to)) {
30022
+ throw new Error(`illegal transition ${finding.status} -> ${to}`);
30023
+ }
30024
+ const updated = { ...finding, status: to, timestamps: { ...finding.timestamps, updated: new Date().toISOString() } };
30025
+ return updated;
30026
+ }
30027
+ function confirmFinding(finding, opts) {
30028
+ const evidence = opts.evidence.map((e) => makeEvidence(e));
30029
+ const negativeControl = opts.negative_control ?? true;
30030
+ const confidence = computeConfidence({
30031
+ static_analysis: true,
30032
+ data_flow: true,
30033
+ reachability: true,
30034
+ preconditions: true,
30035
+ runtime_validation: true,
30036
+ negative_control: negativeControl
30037
+ });
30038
+ const level = confidenceLevel(confidence);
30039
+ const status = negativeControl ? "confirmed" : "hypothesis";
30040
+ return {
30041
+ ...finding,
30042
+ status,
30043
+ confidence,
30044
+ confidence_level: level,
30045
+ validation: {
30046
+ performed: true,
30047
+ status: negativeControl ? "confirmed" : "unconfirmed",
30048
+ baseline: opts.baseline ?? true,
30049
+ negative_control: negativeControl
30050
+ },
30051
+ evidence: [...finding.evidence, ...evidence],
30052
+ timestamps: { ...finding.timestamps, updated: new Date().toISOString() }
30053
+ };
30054
+ }
30055
+ function rejectFinding(finding, status, reason, evidence) {
30056
+ const ev = evidence?.map((e) => makeEvidence(e)) ?? [];
30057
+ const updated = transition(finding, status);
30058
+ return {
30059
+ ...updated,
30060
+ validation: { ...updated.validation, performed: false },
30061
+ remediation: { ...updated.remediation, note: reason },
30062
+ evidence: [...updated.evidence, ...ev],
30063
+ timestamps: { ...updated.timestamps, updated: new Date().toISOString() }
30064
+ };
30065
+ }
30020
30066
 
30021
30067
  // src/orchestrator.ts
30022
30068
  var _chainsCache = null;
@@ -50184,13 +50230,150 @@ async function liveRecon(target, includeActive = false) {
50184
50230
  };
50185
50231
  }
50186
50232
 
50233
+ // src/strike.ts
50234
+ var TIMEOUT_MS3 = 15000;
50235
+ async function httpRequest(url, method, data, headers) {
50236
+ const controller = new AbortController;
50237
+ const t = setTimeout(() => controller.abort(), TIMEOUT_MS3);
50238
+ try {
50239
+ const res = await fetch(url, {
50240
+ method,
50241
+ body: method === "POST" ? data : undefined,
50242
+ headers: { "User-Agent": "blitzstrike/1.0", ...headers ?? {} },
50243
+ signal: controller.signal,
50244
+ redirect: "follow"
50245
+ });
50246
+ const body = await res.text();
50247
+ const hdrs = {};
50248
+ res.headers.forEach((v, k) => {
50249
+ hdrs[k] = v;
50250
+ });
50251
+ return { status: res.status, body: body.slice(0, 20000), headers: hdrs };
50252
+ } catch (e) {
50253
+ return { status: 0, body: String(e), headers: {} };
50254
+ } finally {
50255
+ clearTimeout(t);
50256
+ }
50257
+ }
50258
+ function defaultMarker() {
50259
+ return `BS${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
50260
+ }
50261
+ function inject(target, value, param) {
50262
+ const placeholder = "{{MARKER}}";
50263
+ if (target.includes(placeholder))
50264
+ return target.split(placeholder).join(value);
50265
+ if (target.includes("?"))
50266
+ return `${target}&${param}=${encodeURIComponent(value)}`;
50267
+ return `${target}?${param}=${encodeURIComponent(value)}`;
50268
+ }
50269
+ async function strikeVerify(input) {
50270
+ const method = input.method ?? "GET";
50271
+ const param = input.param ?? "q";
50272
+ const marker = input.marker ?? defaultMarker();
50273
+ const control = input.control ?? defaultMarker();
50274
+ const baseUrl = input.url;
50275
+ const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
50276
+ const markerTarget = inject(baseUrl, marker, param);
50277
+ 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);
50281
+ if (baseline.status === 0 || markerResp.status === 0) {
50282
+ return {
50283
+ status: "blocked",
50284
+ marker_reflected: false,
50285
+ control_reflected: false,
50286
+ baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
50287
+ marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
50288
+ control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
50289
+ reason: "request failed or target unreachable — cannot validate",
50290
+ evidence: []
50291
+ };
50292
+ }
50293
+ const markerReflected = markerResp.body.includes(marker);
50294
+ const controlReflected = controlResp.body.includes(control);
50295
+ let status;
50296
+ let reason;
50297
+ if (markerReflected && !controlReflected) {
50298
+ status = "confirmed";
50299
+ reason = "marker reflected; negative control did not — the sink reflects attacker-controlled input (real finding)";
50300
+ } else if (markerReflected && controlReflected) {
50301
+ status = "false_positive";
50302
+ reason = "marker AND negative control both reflected — behaviour is indistinguishable from benign reflection (likely false positive)";
50303
+ } else if (!markerReflected && !controlReflected) {
50304
+ status = "unconfirmed";
50305
+ reason = "marker not reflected — reachability/exploitability could not be demonstrated live";
50306
+ } else {
50307
+ status = "unconfirmed";
50308
+ reason = "negative control reflected but marker did not — inconsistent, requires deeper analysis";
50309
+ }
50310
+ const evidence = [
50311
+ {
50312
+ type: "baseline_comparison",
50313
+ description: `Baseline response (status ${baseline.status}) for ${redactSecrets(baselineTarget)}`,
50314
+ artifacts: [{ name: "baseline", kind: "http_response", content: `${baseline.status}
50315
+ ${baseline.body}` }]
50316
+ },
50317
+ {
50318
+ type: "validation_result",
50319
+ description: `Marker '${redactSecrets(marker)}' reflected=${markerReflected}; control reflected=${controlReflected}`,
50320
+ artifacts: [{ name: "marker_response", kind: "http_response", content: `${markerResp.status}
50321
+ ${markerResp.body}` }]
50322
+ },
50323
+ {
50324
+ type: "negative_control",
50325
+ description: `Negative control '${redactSecrets(control)}' reflected=${controlReflected}`,
50326
+ artifacts: [{ name: "control_response", kind: "http_response", content: `${controlResp.status}
50327
+ ${controlResp.body}` }]
50328
+ }
50329
+ ];
50330
+ return {
50331
+ status,
50332
+ marker_reflected: markerReflected,
50333
+ control_reflected: controlReflected,
50334
+ baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
50335
+ marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
50336
+ control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
50337
+ reason,
50338
+ evidence
50339
+ };
50340
+ }
50341
+ function resolveFinding(finding, verdict) {
50342
+ let f = finding;
50343
+ if (f.status === "hypothesis") {
50344
+ f = transition(f, "validating");
50345
+ }
50346
+ switch (verdict.status) {
50347
+ case "confirmed":
50348
+ return confirmFinding(f, {
50349
+ evidence: verdict.evidence,
50350
+ negative_control: true,
50351
+ baseline: true
50352
+ });
50353
+ case "false_positive":
50354
+ return rejectFinding(f, "false_positive", verdict.reason, verdict.evidence);
50355
+ case "blocked":
50356
+ return rejectFinding(f, "blocked", verdict.reason, verdict.evidence);
50357
+ case "unconfirmed":
50358
+ case "likely":
50359
+ default: {
50360
+ return {
50361
+ ...f,
50362
+ validation: { performed: true, status: verdict.status === "likely" ? "likely" : "unconfirmed", negative_control: verdict.control_reflected === false, baseline: true },
50363
+ remediation: { ...f.remediation, note: verdict.reason },
50364
+ timestamps: { ...f.timestamps, updated: new Date().toISOString() }
50365
+ };
50366
+ }
50367
+ }
50368
+ }
50369
+
50187
50370
  // src/server.ts
50188
50371
  function createServer() {
50189
50372
  const server = new McpServer({
50190
50373
  name: "blitzstrike",
50191
50374
  version: "1.0.0"
50192
50375
  }, {
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."
50376
+ 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
50377
  });
50195
50378
  server.registerTool("blitz_scan", {
50196
50379
  title: "BLITZ scan",
@@ -50272,55 +50455,66 @@ function createServer() {
50272
50455
  });
50273
50456
  server.registerTool("strike_verify", {
50274
50457
  title: "STRIKE live verification",
50275
- description: "STRIKE: live HTTP verification with marker + negative control. A hit is a HYPOTHESIS until the marker reflects.",
50458
+ 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
50459
  inputSchema: {
50277
50460
  url: string2().describe("Target URL"),
50278
50461
  method: _enum(["GET", "POST"]).optional().describe("HTTP method"),
50279
- data: string2().optional().describe("POST body (urlencoded)"),
50462
+ data: string2().optional().describe("POST body template; a literal {{MARKER}} is replaced"),
50280
50463
  headers: string2().optional().describe("JSON object of extra headers"),
50281
- marker: string2().optional().describe("String that must appear in the response to confirm"),
50464
+ marker: string2().optional().describe("Unique string the payload should reflect (defaults to a random token)"),
50465
+ control: string2().optional().describe("Benign lookalike for the negative control (defaults to a random token)"),
50466
+ param: string2().optional().describe("Query/body param name to inject into (default 'q')"),
50282
50467
  timeout: number2().int().optional().describe("Timeout seconds (default 15)")
50283
50468
  }
50284
- }, async ({ url, method, data, headers, marker, timeout }) => {
50285
- const controller = new AbortController;
50286
- const t = setTimeout(() => controller.abort(), (timeout ?? 15) * 1000);
50469
+ }, async ({ url, method, data, headers, marker, control, param, timeout }) => {
50287
50470
  let hdrs = {};
50288
50471
  try {
50289
50472
  hdrs = headers ? JSON.parse(headers) : {};
50290
50473
  } catch {}
50474
+ const verdict = await strikeVerify({
50475
+ url,
50476
+ method: method ?? "GET",
50477
+ data,
50478
+ headers: hdrs,
50479
+ marker,
50480
+ control,
50481
+ param
50482
+ });
50483
+ return {
50484
+ content: [{
50485
+ type: "text",
50486
+ text: JSON.stringify({
50487
+ ...verdict,
50488
+ next_steps: [
50489
+ `Verdict: ${verdict.status} — ${verdict.reason}`,
50490
+ 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."
50491
+ ]
50492
+ })
50493
+ }]
50494
+ };
50495
+ });
50496
+ server.registerTool("strike_resolve", {
50497
+ title: "Resolve a finding from a STRIKE verdict",
50498
+ 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.",
50499
+ inputSchema: {
50500
+ finding: string2().describe("JSON of the canonical Finding object (from finding_create)"),
50501
+ verdict: string2().describe("JSON of the STRIKE verdict (from strike_verify)")
50502
+ }
50503
+ }, async ({ finding, verdict }) => {
50504
+ let f;
50505
+ let v;
50291
50506
  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);
50507
+ f = JSON.parse(finding);
50508
+ } catch {
50509
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON" }) }] };
50510
+ }
50511
+ try {
50512
+ v = JSON.parse(verdict);
50513
+ } catch {
50514
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid verdict JSON" }) }] };
50323
50515
  }
50516
+ const updated = resolveFinding(f, v);
50517
+ return { content: [{ type: "text", text: JSON.stringify(updated) }] };
50324
50518
  });
50325
50519
  server.registerTool("scope_check", {
50326
50520
  title: "STRIKE scope enforcement",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
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": {