blitzstrike 1.0.35 → 1.0.38

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 (72) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/bench/corpus/java-cmd-vuln.md +8 -0
  3. package/bench/corpus/java-deser-vuln.md +8 -0
  4. package/bench/corpus/java-sqli-prep.md +9 -0
  5. package/bench/corpus/java-sqli-vuln.md +9 -0
  6. package/bench/corpus/java-ssrf-vuln.md +9 -0
  7. package/bench/corpus/java-xss-vuln.md +8 -0
  8. package/bench/corpus/java-xxe-vuln.md +10 -0
  9. package/bench/corpus/js-cmd-spawn.md +8 -0
  10. package/bench/corpus/js-cmd-vuln.md +8 -0
  11. package/bench/corpus/js-eval-vuln.md +8 -0
  12. package/bench/corpus/js-pathtraversal-vuln.md +8 -0
  13. package/bench/corpus/js-redirect-vuln.md +8 -0
  14. package/bench/corpus/js-sqli-param.md +8 -0
  15. package/bench/corpus/js-ssrf-vuln.md +8 -0
  16. package/bench/corpus/{js-xss-sanitized.md → js-xss-escape.md} +1 -1
  17. package/bench/corpus/js-xss-vuln.md +8 -0
  18. package/bench/corpus/php-cmd-authgated.md +8 -0
  19. package/bench/corpus/php-cmd-byref.md +11 -0
  20. package/bench/corpus/php-cmd-escaped.md +9 -0
  21. package/bench/corpus/php-cmd-escapeshellcmd.md +9 -0
  22. package/bench/corpus/php-cmd-intval.md +9 -0
  23. package/bench/corpus/php-cmd-passthru.md +9 -0
  24. package/bench/corpus/php-cmd-shell-exec.md +9 -0
  25. package/bench/corpus/php-cmd-transitive.md +10 -0
  26. package/bench/corpus/{php-command-injection-vuln.md → php-cmd-vuln.md} +1 -1
  27. package/bench/corpus/php-codeexec-assert.md +9 -0
  28. package/bench/corpus/php-codeexec-vuln.md +9 -0
  29. package/bench/corpus/php-fileupload-name.md +9 -0
  30. package/bench/corpus/php-fileupload-sanitized.md +9 -0
  31. package/bench/corpus/php-fileupload-vuln.md +9 -0
  32. package/bench/corpus/php-filewrite-vuln.md +9 -0
  33. package/bench/corpus/php-include-intval.md +9 -0
  34. package/bench/corpus/php-include-require.md +9 -0
  35. package/bench/corpus/php-include-vuln.md +9 -0
  36. package/bench/corpus/php-include-whitelist.md +10 -0
  37. package/bench/corpus/php-redirect-safe.md +9 -0
  38. package/bench/corpus/php-redirect-vuln.md +9 -0
  39. package/bench/corpus/php-sqli-authgated.md +9 -0
  40. package/bench/corpus/php-sqli-esc.md +9 -0
  41. package/bench/corpus/php-sqli-filter.md +9 -0
  42. package/bench/corpus/php-sqli-getvar.md +9 -0
  43. package/bench/corpus/php-sqli-interproc.md +10 -0
  44. package/bench/corpus/php-sqli-whereraw.md +9 -0
  45. package/bench/corpus/php-ssrf-curl.md +11 -0
  46. package/bench/corpus/php-ssrf-vuln.md +9 -0
  47. package/bench/corpus/php-ssrf-wpremote.md +9 -0
  48. package/bench/corpus/php-unserialize-vuln.md +9 -0
  49. package/bench/corpus/php-xss-attr.md +9 -0
  50. package/bench/corpus/php-xss-authgated.md +8 -0
  51. package/bench/corpus/php-xss-eschtml.md +9 -0
  52. package/bench/corpus/php-xss-htmlentities.md +9 -0
  53. package/bench/corpus/php-xss-return.md +9 -0
  54. package/bench/corpus/php-xss-ternary.md +9 -0
  55. package/bench/corpus/php-xss-vuln.md +9 -0
  56. package/bench/corpus/php-xxe-vuln.md +9 -0
  57. package/bench/corpus/php-zipslip-vuln.md +10 -0
  58. package/bench/corpus/py-cmd-subprocess.md +9 -0
  59. package/bench/corpus/{py-command-injection-vuln.md → py-cmd-vuln.md} +3 -3
  60. package/bench/corpus/py-deser-json.md +9 -0
  61. package/bench/corpus/py-deser-vuln.md +9 -0
  62. package/bench/corpus/py-eval-vuln.md +8 -0
  63. package/bench/corpus/py-pathtraversal-vuln.md +8 -0
  64. package/bench/corpus/py-sqli-param.md +9 -0
  65. package/bench/corpus/py-sqli-vuln.md +9 -0
  66. package/bench/corpus/py-ssrf-vuln.md +9 -0
  67. package/bench/corpus/py-xss-vuln.md +8 -0
  68. package/dist/index.js +404 -98
  69. package/package.json +1 -1
  70. package/bench/corpus/java-command-injection-vuln.md +0 -7
  71. package/bench/corpus/php-file-upload-vuln.md +0 -9
  72. package/bench/corpus/py-command-injection-sanitized.md +0 -9
package/dist/index.js CHANGED
@@ -14820,8 +14820,8 @@ var require_src = __commonJS(function(exports, module) {
14820
14820
  });
14821
14821
 
14822
14822
  // src/sync.ts
14823
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync2 } from "node:fs";
14824
- import { join as join10 } from "node:path";
14823
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync15, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync3 } from "node:fs";
14824
+ import { join as join11 } from "node:path";
14825
14825
  import { homedir as homedir4 } from "node:os";
14826
14826
  import { fileURLToPath as fileURLToPath7 } from "node:url";
14827
14827
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -14838,7 +14838,7 @@ function countFiles(dir) {
14838
14838
  const walk = (d) => {
14839
14839
  for (const e of readdirSync5(d, { withFileTypes: true })) {
14840
14840
  if (e.isDirectory())
14841
- walk(join10(d, e.name));
14841
+ walk(join11(d, e.name));
14842
14842
  else
14843
14843
  n++;
14844
14844
  }
@@ -14852,8 +14852,8 @@ function countFiles(dir) {
14852
14852
  function syncData() {
14853
14853
  if (!dirExists(DATA_ROOT2))
14854
14854
  mkdirSync3(DATA_ROOT2, { recursive: true });
14855
- const payloadsPath = join10(DATA_ROOT2, "payloads");
14856
- const templatesPath = join10(DATA_ROOT2, "templates");
14855
+ const payloadsPath = join11(DATA_ROOT2, "payloads");
14856
+ const templatesPath = join11(DATA_ROOT2, "templates");
14857
14857
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
14858
14858
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
14859
14859
  if (hasPayloads && hasTemplates) {
@@ -14863,8 +14863,8 @@ function syncData() {
14863
14863
  data_root: DATA_ROOT2
14864
14864
  };
14865
14865
  }
14866
- const localPayloads = join10(ROOT5, "payloads");
14867
- const localTemplates = join10(ROOT5, "templates");
14866
+ const localPayloads = join11(ROOT5, "payloads");
14867
+ const localTemplates = join11(ROOT5, "templates");
14868
14868
  let payloadsSynced = false;
14869
14869
  let templatesSynced = false;
14870
14870
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -14877,23 +14877,23 @@ function syncData() {
14877
14877
  }
14878
14878
  const gitOk = spawnSync2("git", ["--version"]).status === 0;
14879
14879
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
14880
- const tmp = join10(DATA_ROOT2, ".tmp-clone");
14880
+ const tmp = join11(DATA_ROOT2, ".tmp-clone");
14881
14881
  if (dirExists(tmp))
14882
- rmSync2(tmp, { recursive: true, force: true });
14882
+ rmSync3(tmp, { recursive: true, force: true });
14883
14883
  const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
14884
14884
  if (r.status === 0) {
14885
14885
  const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
14886
14886
  if (sparse.status === 0) {
14887
14887
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
14888
- copyDir(join10(tmp, "payloads"), payloadsPath);
14888
+ copyDir(join11(tmp, "payloads"), payloadsPath);
14889
14889
  payloadsSynced = true;
14890
14890
  }
14891
14891
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
14892
- copyDir(join10(tmp, "templates"), templatesPath);
14892
+ copyDir(join11(tmp, "templates"), templatesPath);
14893
14893
  templatesSynced = true;
14894
14894
  }
14895
14895
  }
14896
- rmSync2(tmp, { recursive: true, force: true });
14896
+ rmSync3(tmp, { recursive: true, force: true });
14897
14897
  }
14898
14898
  }
14899
14899
  return {
@@ -14911,18 +14911,18 @@ function copyDir(src, dst) {
14911
14911
  function manualCopy(src, dst) {
14912
14912
  mkdirSync3(dst, { recursive: true });
14913
14913
  for (const e of readdirSync5(src, { withFileTypes: true })) {
14914
- const s = join10(src, e.name);
14915
- const d = join10(dst, e.name);
14914
+ const s = join11(src, e.name);
14915
+ const d = join11(dst, e.name);
14916
14916
  if (e.isDirectory())
14917
14917
  manualCopy(s, d);
14918
14918
  else
14919
- writeFileSync3(d, readFileSync14(s));
14919
+ writeFileSync4(d, readFileSync15(s));
14920
14920
  }
14921
14921
  }
14922
14922
  var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
14923
14923
  var init_sync = __esm(() => {
14924
- ROOT5 = join10(fileURLToPath7(new URL(".", import.meta.url)), "..");
14925
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join10(homedir4(), ".blitzstrike", "data");
14924
+ ROOT5 = join11(fileURLToPath7(new URL(".", import.meta.url)), "..");
14925
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join11(homedir4(), ".blitzstrike", "data");
14926
14926
  });
14927
14927
 
14928
14928
  // src/update.ts
@@ -31072,8 +31072,8 @@ var SINK_CLASS_MAP = [
31072
31072
  [/\bmove_uploaded_file\(|\bfile_put_contents\(|\bfwrite\(|\bfopen\(|\bunlink\(/, { id: "file_operations", category: "File operations", cwe: "CWE-434" }],
31073
31073
  [/\binclude\(|\brequire\(|\binclude_once\(|\brequire_once\(/, { id: "file_inclusion", category: "File inclusion", cwe: "CWE-98" }],
31074
31074
  [/\bunserialize\(|\bmaybe_unserialize\(/, { id: "deserialization", category: "Deserialization", cwe: "CWE-502" }],
31075
- [/\bwp_remote_get\(|\bwp_remote_post\(|\bfile_get_contents\(|\bcurl_exec\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
31076
- [/\bheader\(|\bwp_redirect\(|\bwp_safe_redirect\(/, { id: "redirect", category: "Redirect handling", cwe: "CWE-601" }],
31075
+ [/\bwp_remote_get\(|\bwp_remote_post\(|\bfile_get_contents\(|\bcurl_exec\(|\bcurl_setopt\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
31076
+ [/\bheader\(|\bwp_redirect\(/, { id: "redirect", category: "Redirect handling", cwe: "CWE-601" }],
31077
31077
  [/echo\s|print\s|\bprintf\(/, { id: "html_render", category: "HTML rendering (XSS)", cwe: "CWE-79" }],
31078
31078
  [/\bsimplexml_load_string\(|new SimpleXMLElement|DOMDocument/, { id: "xml_processing", category: "XML processing (XXE)", cwe: "CWE-611" }],
31079
31079
  [/\bZipArchive|\bPharData|->extractTo\(/, { id: "archive_extraction", category: "Archive extraction (zip slip)", cwe: "CWE-22" }]
@@ -31777,6 +31777,30 @@ function resolveCall(g, call, callerId, ctx, file, line) {
31777
31777
  return;
31778
31778
  }
31779
31779
  }
31780
+ function reachableFrom(g, entry) {
31781
+ const seen = new Set;
31782
+ const stack = [entry];
31783
+ while (stack.length) {
31784
+ const cur = stack.pop();
31785
+ if (seen.has(cur))
31786
+ continue;
31787
+ seen.add(cur);
31788
+ const n = g.nodes.get(cur);
31789
+ if (!n)
31790
+ continue;
31791
+ for (const c of n.callees)
31792
+ stack.push(c);
31793
+ }
31794
+ return seen;
31795
+ }
31796
+ function entryPoints(g) {
31797
+ const entries = [];
31798
+ for (const [id, n] of g.nodes) {
31799
+ if (n.callers.size === 0)
31800
+ entries.push(id);
31801
+ }
31802
+ return entries;
31803
+ }
31780
31804
 
31781
31805
  // src/eagle2.ts
31782
31806
  var parser3 = new import_php_parser3.Engine({
@@ -31859,9 +31883,11 @@ function sinkClassOf2(call) {
31859
31883
  return null;
31860
31884
  let token = "";
31861
31885
  if (what.kind === "propertylookup") {
31862
- const base = rootVarName2(what.what);
31886
+ const baseNode = what.what;
31887
+ const base = rootVarName2(baseNode);
31863
31888
  const method = what.offset?.name ?? what.offset?.value;
31864
- token = `${base}->${method}(`;
31889
+ const baseStr = isNode3(baseNode) && (baseNode.kind === "variable" || baseNode.kind === "offsetlookup") ? `$${base}` : base;
31890
+ token = `${baseStr}->${method}(`;
31865
31891
  } else if (what.kind === "name") {
31866
31892
  token = `${what.name}(`;
31867
31893
  }
@@ -31918,26 +31944,27 @@ function summarizeFunction(fn, id, name, summaries) {
31918
31944
  }
31919
31945
  return summary;
31920
31946
  }
31921
- function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
31947
+ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
31922
31948
  for (const stmt of statements) {
31923
31949
  if (!isNode3(stmt))
31924
31950
  continue;
31925
31951
  const kind = stmt.kind;
31926
31952
  if (kind === "expressionstatement") {
31927
- analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31953
+ analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31928
31954
  } else if (kind === "if") {
31929
31955
  const condGates = authGatesOf(stmt.test);
31930
31956
  const branchAuth = [...activeAuth, ...condGates];
31957
+ const condValidating = validationGatesOf(stmt.test);
31931
31958
  const thenStmts = stmt.body?.kind === "block" ? stmt.body.children ?? [] : [stmt.body];
31932
31959
  const thenEnv = cloneEnv(env);
31933
31960
  const thenCtx = { sinks: [], returnsTainted: false, calls: [] };
31934
- analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries);
31961
+ analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries, validating || condValidating);
31935
31962
  mergeCtx(ctx, thenCtx);
31936
31963
  if (stmt.alternate) {
31937
31964
  const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children ?? [] : [stmt.alternate];
31938
31965
  const altEnv = cloneEnv(env);
31939
31966
  const altCtx = { sinks: [], returnsTainted: false, calls: [] };
31940
- analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries);
31967
+ analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries, validating);
31941
31968
  mergeCtx(ctx, altCtx);
31942
31969
  for (const [k, v] of thenEnv) {
31943
31970
  const alt = altEnv.get(k);
@@ -31948,25 +31975,25 @@ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeA
31948
31975
  env.set(k, v);
31949
31976
  }
31950
31977
  } else if (kind === "block") {
31951
- analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries);
31978
+ analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31952
31979
  } else if (kind === "return") {
31953
- const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31980
+ const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31954
31981
  if (t.tainted)
31955
31982
  ctx.returnsTainted = true;
31956
31983
  } else if (kind === "echo" || kind === "print") {
31957
- analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31984
+ analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31958
31985
  } else {
31959
31986
  for (const c of children3(stmt)) {
31960
31987
  if (c.kind === "block" || c.kind === "if") {
31961
- analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries);
31988
+ analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31962
31989
  } else {
31963
- analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31990
+ analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31964
31991
  }
31965
31992
  }
31966
31993
  }
31967
31994
  }
31968
31995
  }
31969
- function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
31996
+ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
31970
31997
  if (!isNode3(node))
31971
31998
  return CLEAN;
31972
31999
  const src = sourceOf2(node);
@@ -31974,7 +32001,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
31974
32001
  return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
31975
32002
  if (node.kind === "assign") {
31976
32003
  const byref = node.operator === "=&";
31977
- const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32004
+ const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31978
32005
  const lhs = node.left;
31979
32006
  if (isNode3(lhs) && lhs.kind === "variable") {
31980
32007
  const varName = lhs.name;
@@ -32006,7 +32033,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32006
32033
  }
32007
32034
  if (node.kind === "call") {
32008
32035
  const sink = sinkClassOf2(node);
32009
- const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries));
32036
+ const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
32010
32037
  const taintedArgs = argTaints.filter((t) => t.tainted);
32011
32038
  if (sink) {
32012
32039
  if (taintedArgs.length > 0) {
@@ -32016,7 +32043,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32016
32043
  ctx.sinks.push({ sink: sink.token, line: node.loc?.start?.line ?? 0, sanitized, authGated: gates.length > 0, authGates: gates });
32017
32044
  }
32018
32045
  for (const a of node.arguments)
32019
- analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32046
+ analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32020
32047
  return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
32021
32048
  }
32022
32049
  const fnName = rootVarName2(node.what);
@@ -32059,6 +32086,9 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32059
32086
  }
32060
32087
  }
32061
32088
  }
32089
+ if (isNode3(node.what) && node.what.kind === "propertylookup") {
32090
+ analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32091
+ }
32062
32092
  return CLEAN;
32063
32093
  }
32064
32094
  if (node.kind === "variable") {
@@ -32072,7 +32102,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32072
32102
  if (t)
32073
32103
  return { ...t };
32074
32104
  }
32075
- return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32105
+ return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32076
32106
  }
32077
32107
  if (node.kind === "propertylookup") {
32078
32108
  const objName = rootVarName2(node.what);
@@ -32082,35 +32112,55 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32082
32112
  if (pt)
32083
32113
  return { ...pt };
32084
32114
  }
32085
- return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32115
+ return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32086
32116
  }
32087
32117
  if (node.kind === "retif") {
32088
- const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32089
- const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries) : CLEAN;
32118
+ const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32119
+ const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating) : CLEAN;
32090
32120
  return mergeTaint(t, f);
32091
32121
  }
32092
32122
  if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
32093
32123
  const parts = children3(node);
32094
- const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries));
32124
+ const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
32095
32125
  return mergeTaint(...taints);
32096
32126
  }
32097
32127
  if (node.kind === "encapsedpart") {
32098
- return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32128
+ return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32099
32129
  }
32100
32130
  if (node.kind === "echo" || node.kind === "print") {
32101
32131
  const exprs = node.expressions ?? [node.expression];
32102
32132
  const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
32103
32133
  let ret = CLEAN;
32104
32134
  for (const e of exprs) {
32105
- const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32135
+ const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32106
32136
  if (t.tainted && cls) {
32107
32137
  const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(e))];
32108
- ctx.sinks.push({ sink: node.kind + " ", line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id), authGated: activeAuth.length > 0, authGates: activeAuth });
32138
+ ctx.sinks.push({ sink: node.kind + " ", line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
32109
32139
  }
32110
32140
  ret = mergeTaint(ret, t);
32111
32141
  }
32112
32142
  return ret;
32113
32143
  }
32144
+ if (node.kind === "include") {
32145
+ const base = node.require ? "require" : "include";
32146
+ const suffix = node.once ? "_once" : "";
32147
+ const sinkToken = base + suffix + "(";
32148
+ const cls = classifySink2(sinkToken);
32149
+ const t = analyzeExpr(node.target, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32150
+ if (t.tainted && cls) {
32151
+ const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(node))];
32152
+ ctx.sinks.push({ sink: sinkToken, line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
32153
+ }
32154
+ return t;
32155
+ }
32156
+ if (node.kind === "eval") {
32157
+ const cls = classifySink2("eval(");
32158
+ const t = analyzeExpr(node.source, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32159
+ if (t.tainted && cls) {
32160
+ ctx.sinks.push({ sink: "eval(", line: node.loc?.start?.line ?? 0, sanitized: false, authGated: activeAuth.length > 0, authGates: activeAuth });
32161
+ }
32162
+ return t;
32163
+ }
32114
32164
  return CLEAN;
32115
32165
  }
32116
32166
  function cloneEnv(env) {
@@ -32133,6 +32183,13 @@ function authGatesOf(node) {
32133
32183
  return [];
32134
32184
  return AUTH_GATES.filter((g) => JSON.stringify(node).includes(g));
32135
32185
  }
32186
+ var VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
32187
+ function validationGatesOf(node) {
32188
+ if (!isNode3(node))
32189
+ return false;
32190
+ const text = JSON.stringify(node);
32191
+ return VALIDATION_GATES.some((g) => text.includes(g));
32192
+ }
32136
32193
  function collectDecls(ast, out) {
32137
32194
  walkDecls(ast, out);
32138
32195
  }
@@ -32173,11 +32230,17 @@ function resolveFuncId(name, summaries) {
32173
32230
  }
32174
32231
  function analyzeDataFlow2(path) {
32175
32232
  const cg = buildCallGraph(path);
32233
+ const entries = entryPoints(cg);
32234
+ const reachable = new Set;
32235
+ for (const e of entries)
32236
+ for (const n of reachableFrom(cg, e))
32237
+ reachable.add(n);
32238
+ const unreachable = [...cg.nodes.keys()].filter((id) => !reachable.has(id));
32176
32239
  const result = {
32177
32240
  file: path,
32178
32241
  findings: [],
32179
32242
  suppressed: 0,
32180
- summary: { functions: 0, methods: 0, edges: cg.edges.length, call_graph: cg }
32243
+ summary: { functions: 0, methods: 0, edges: cg.edges.length, entry_points: entries, unreachable }
32181
32244
  };
32182
32245
  let code;
32183
32246
  try {
@@ -32293,6 +32356,12 @@ function analyzeDataFlow2(path) {
32293
32356
  return result;
32294
32357
  }
32295
32358
 
32359
+ // src/differential.ts
32360
+ import { writeFileSync as writeFileSync2, rmSync, mkdtempSync, readFileSync as readFileSync13 } from "node:fs";
32361
+ import { join as join9 } from "node:path";
32362
+ import { tmpdir } from "node:os";
32363
+ import { execSync } from "node:child_process";
32364
+
32296
32365
  // src/universal-taint.ts
32297
32366
  function runTaintEngine(adapter, code, file) {
32298
32367
  const result = { file, language: adapter.language, findings: [], suppressed: 0 };
@@ -50346,8 +50415,13 @@ var JS_SINKS = [
50346
50415
  [/document\.write/i, "html_render", "CWE-79"],
50347
50416
  [/fs\.writeFile/i, "file_operations", "CWE-434"],
50348
50417
  [/fs\.appendFile/i, "file_operations", "CWE-434"],
50418
+ [/fs\.readFile/i, "path_traversal", "CWE-22"],
50419
+ [/fs\.readFileSync/i, "path_traversal", "CWE-22"],
50420
+ [/fs\.unlink/i, "file_operations", "CWE-434"],
50349
50421
  [/require\s*\(/i, "file_inclusion", "CWE-98"],
50350
50422
  [/fetch\s*\(/i, "http_request", "CWE-918"],
50423
+ [/http\.get|https\.get|axios/i, "http_request", "CWE-918"],
50424
+ [/http\.request|https\.request/i, "http_request", "CWE-918"],
50351
50425
  [/redirect\s*\(/i, "redirect", "CWE-601"],
50352
50426
  [/res\.redirect/i, "redirect", "CWE-601"]
50353
50427
  ];
@@ -50434,6 +50508,8 @@ var jsAdapter = {
50434
50508
  argVars.push(c.name);
50435
50509
  for (const [re, kind, cwe] of JS_SINKS) {
50436
50510
  if (re.test(fullCallText) || re.test(calleeText + "(")) {
50511
+ if (kind === "sql_execution" && /\?/.test(fullCallText) && /,\s*\[/.test(fullCallText))
50512
+ continue;
50437
50513
  for (const v of argVars)
50438
50514
  sinks.push({ variable: v, kind, line: n.loc?.start?.line ?? 0, cwe });
50439
50515
  }
@@ -50472,6 +50548,8 @@ var PY_SINKS = [
50472
50548
  [/\.save\s*\(/i, "file_operations", "CWE-434"],
50473
50549
  [/render_template/i, "html_render", "CWE-79"],
50474
50550
  [/redirect\s*\(/i, "redirect", "CWE-601"],
50551
+ [/requests\.(get|post|put|head|request)\s*\(/i, "http_request", "CWE-918"],
50552
+ [/urllib\.request/i, "http_request", "CWE-918"],
50475
50553
  [/pickle\.loads\s*\(/i, "deserialization", "CWE-502"],
50476
50554
  [/yaml\.load\s*\(/i, "deserialization", "CWE-502"],
50477
50555
  [/os\.popen\s*\(/i, "command_execution", "CWE-78"],
@@ -50556,9 +50634,14 @@ var pythonAdapter = {
50556
50634
  if (m[1] !== callee.trim().split(".").pop())
50557
50635
  argVars.push(m[1]);
50558
50636
  for (const [re, kind, cwe] of PY_SINKS) {
50559
- if (re.test(full))
50560
- for (const v of argVars)
50561
- sinks.push({ variable: v, kind, line: lineOf(n), cwe });
50637
+ if (!re.test(full))
50638
+ continue;
50639
+ if (kind === "sql_execution" && /\?/.test(full) && /,\s*\(/.test(full))
50640
+ continue;
50641
+ if (kind === "command_execution" && /subprocess/.test(full) && /\[/.test(full))
50642
+ continue;
50643
+ for (const v of argVars)
50644
+ sinks.push({ variable: v, kind, line: lineOf(n), cwe });
50562
50645
  }
50563
50646
  for (const [re, kinds] of PY_SANITIZERS) {
50564
50647
  if (re.test(full))
@@ -50594,6 +50677,10 @@ var JAVA_SINKS = [
50594
50677
  [/readObject\s*\(/i, "deserialization", "CWE-502"],
50595
50678
  [/new File\s*\(/i, "file_operations", "CWE-434"],
50596
50679
  [/\.getInputStream\s*\(/i, "http_request", "CWE-918"],
50680
+ [/new URL\s*\(/i, "http_request", "CWE-918"],
50681
+ [/openConnection\s*\(/i, "http_request", "CWE-918"],
50682
+ [/DocumentBuilder|SAXParser|XMLReader/i, "xml_processing", "CWE-611"],
50683
+ [/\.parse\s*\(/i, "xml_processing", "CWE-611"],
50597
50684
  [/sendRedirect\s*\(/i, "redirect", "CWE-601"],
50598
50685
  [/\.forward\s*\(/i, "redirect", "CWE-601"]
50599
50686
  ];
@@ -50661,6 +50748,195 @@ registerLanguage(jsAdapter);
50661
50748
  registerLanguage(pythonAdapter);
50662
50749
  registerLanguage(javaAdapter);
50663
50750
 
50751
+ // src/differential.ts
50752
+ init_scanner();
50753
+ function runEngine(code, language, file) {
50754
+ if (language === "php") {
50755
+ const dir = mkdtempSync(join9(tmpdir(), "blitz-diff-"));
50756
+ const tmp = join9(dir, "old.php");
50757
+ writeFileSync2(tmp, code);
50758
+ try {
50759
+ const r = analyzeDataFlow2(tmp);
50760
+ return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
50761
+ } finally {
50762
+ rmSync(dir, { recursive: true, force: true });
50763
+ }
50764
+ }
50765
+ const extMap = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" };
50766
+ const probe = file.includes(".") ? file : `target${extMap[language] ?? ""}`;
50767
+ const r = analyzeTaintUniversal(code, probe);
50768
+ return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.sink, source: f.source_kind, cwe: f.cwe }));
50769
+ }
50770
+ function diffLines(oldText, newText) {
50771
+ const a = oldText.split(`
50772
+ `);
50773
+ const b = newText.split(`
50774
+ `);
50775
+ const m = a.length;
50776
+ const n = b.length;
50777
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
50778
+ for (let i = m - 1;i >= 0; i--) {
50779
+ for (let j = n - 1;j >= 0; j--) {
50780
+ dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
50781
+ }
50782
+ }
50783
+ const added = [];
50784
+ const removed = [];
50785
+ let i = 0;
50786
+ let j = 0;
50787
+ while (i < m && j < n) {
50788
+ if (a[i] === b[j]) {
50789
+ i++;
50790
+ j++;
50791
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
50792
+ removed.push({ line: i + 1, text: a[i] });
50793
+ i++;
50794
+ } else {
50795
+ added.push({ line: j + 1, text: b[j] });
50796
+ j++;
50797
+ }
50798
+ }
50799
+ while (i < m) {
50800
+ removed.push({ line: i + 1, text: a[i] });
50801
+ i++;
50802
+ }
50803
+ while (j < n) {
50804
+ added.push({ line: j + 1, text: b[j] });
50805
+ j++;
50806
+ }
50807
+ return { added, removed };
50808
+ }
50809
+ function analyzeDifferential(oldCode, newCode, language, file = "target") {
50810
+ const changes = [];
50811
+ const oldFindings = runEngine(oldCode, language, file);
50812
+ const newFindings = runEngine(newCode, language, file);
50813
+ const key = (f) => `${f.category}|${f.sink}|${f.sink_line}`;
50814
+ const oldKeys = new Set(oldFindings.map(key));
50815
+ const newKeys = new Set(newFindings.map(key));
50816
+ for (const f of newFindings) {
50817
+ if (!oldKeys.has(key(f))) {
50818
+ changes.push({
50819
+ kind: "new_vulnerability",
50820
+ severity: "high",
50821
+ sink: f.sink,
50822
+ category: f.category,
50823
+ source: f.source,
50824
+ cwe: f.cwe,
50825
+ line: f.sink_line,
50826
+ detail: `new ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
50827
+ });
50828
+ }
50829
+ }
50830
+ for (const f of oldFindings) {
50831
+ if (!newKeys.has(key(f))) {
50832
+ changes.push({
50833
+ kind: "fixed_vulnerability",
50834
+ severity: "low",
50835
+ sink: f.sink,
50836
+ category: f.category,
50837
+ source: f.source,
50838
+ cwe: f.cwe,
50839
+ line: f.sink_line,
50840
+ detail: `removed ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
50841
+ });
50842
+ }
50843
+ }
50844
+ const { added, removed } = diffLines(oldCode, newCode);
50845
+ for (const l of added) {
50846
+ const sink = classifySink2(l.text);
50847
+ if (sink) {
50848
+ changes.push({
50849
+ kind: "new_sink",
50850
+ severity: "medium",
50851
+ sink: l.text.trim(),
50852
+ category: sink.category,
50853
+ cwe: sink.cwe,
50854
+ line: l.line,
50855
+ detail: `added sink ${l.text.trim()} (${sink.category})`
50856
+ });
50857
+ }
50858
+ const source = classifySource(l.text);
50859
+ if (source) {
50860
+ changes.push({
50861
+ kind: "new_source",
50862
+ severity: source.attacker_controlled ? "medium" : "low",
50863
+ source: source.label,
50864
+ line: l.line,
50865
+ detail: `added source ${l.text.trim()} (${source.label})`
50866
+ });
50867
+ }
50868
+ }
50869
+ for (const l of removed) {
50870
+ const san = findSanitizers(l.text);
50871
+ if (san.length > 0) {
50872
+ changes.push({
50873
+ kind: "sanitizer_removed",
50874
+ severity: "high",
50875
+ line: l.line,
50876
+ detail: `removed sanitizer ${san.map((s) => s.label).join(", ")} (${l.text.trim()})`
50877
+ });
50878
+ }
50879
+ const gates = AUTH_GATES.filter((g) => l.text.includes(g));
50880
+ if (gates.length > 0) {
50881
+ changes.push({
50882
+ kind: "authorization_removed",
50883
+ severity: "high",
50884
+ line: l.line,
50885
+ detail: `removed authorization gate ${gates.join(", ")} (${l.text.trim()})`
50886
+ });
50887
+ }
50888
+ }
50889
+ const rank = { high: 0, medium: 1, low: 2 };
50890
+ changes.sort((x, y) => rank[x.severity] - rank[y.severity] || x.line - y.line);
50891
+ const count = (k) => changes.filter((c) => c.kind === k).length;
50892
+ return {
50893
+ language,
50894
+ changes,
50895
+ summary: {
50896
+ new_vulnerabilities: count("new_vulnerability"),
50897
+ fixed_vulnerabilities: count("fixed_vulnerability"),
50898
+ new_sinks: count("new_sink"),
50899
+ new_sources: count("new_source"),
50900
+ sanitizers_removed: count("sanitizer_removed"),
50901
+ authorizations_removed: count("authorization_removed")
50902
+ }
50903
+ };
50904
+ }
50905
+ function languageOf(file) {
50906
+ const adapter = detectLanguage(file);
50907
+ return adapter?.language ?? "php";
50908
+ }
50909
+ function analyzeDifferentialFiles(oldPath, newPath, language) {
50910
+ const oldCode = readFileSync13(oldPath, "utf8");
50911
+ const newCode = readFileSync13(newPath, "utf8");
50912
+ const lang = language ?? languageOf(newPath);
50913
+ return analyzeDifferential(oldCode, newCode, lang, newPath);
50914
+ }
50915
+ var SOURCE_EXTS = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
50916
+ function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
50917
+ const git = (args) => {
50918
+ try {
50919
+ return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
50920
+ } catch {
50921
+ return "";
50922
+ }
50923
+ };
50924
+ const changed = git(`diff --name-only ${base} ${head}`).split(`
50925
+ `).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
50926
+ const files = [];
50927
+ for (const file of changed) {
50928
+ const oldCode = git(`show ${base}:${file}`);
50929
+ const newCode = git(`show ${head}:${file}`);
50930
+ if (!newCode)
50931
+ continue;
50932
+ const lang = languageOf(file);
50933
+ files.push({ file, result: analyzeDifferential(oldCode || "", newCode, lang, file) });
50934
+ }
50935
+ const total_changes = files.reduce((n, f) => n + f.result.changes.length, 0);
50936
+ const security_sensitive = files.reduce((n, f) => n + f.result.changes.filter((c) => c.severity === "high").length, 0);
50937
+ return { repo, base, head, files, total_changes, security_sensitive };
50938
+ }
50939
+
50664
50940
  // src/guidance.ts
50665
50941
  var SINK_DIRECTIVES = {
50666
50942
  sql_execution: "Validate the SQL injection live with strike_verify (use a unique MARKER and a NEGATIVE CONTROL that stays inert), or read_tool_manual for sqlmap. Only a reflected marker + inert control = confirmed.",
@@ -51474,16 +51750,16 @@ function reportJson(findings, opts = {}) {
51474
51750
  }
51475
51751
 
51476
51752
  // src/benchmark.ts
51477
- import { readFileSync as readFileSync13, readdirSync as readdirSync4, writeFileSync as writeFileSync2, rmSync } from "node:fs";
51478
- import { join as join9 } from "node:path";
51753
+ import { readFileSync as readFileSync14, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
51754
+ import { join as join10 } from "node:path";
51479
51755
  import { fileURLToPath as fileURLToPath6 } from "node:url";
51480
- import { tmpdir } from "node:os";
51481
- var ROOT4 = join9(fileURLToPath6(new URL(".", import.meta.url)), "..");
51482
- var CORPUS = join9(ROOT4, "bench", "corpus");
51756
+ import { tmpdir as tmpdir2 } from "node:os";
51757
+ var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
51758
+ var CORPUS = join10(ROOT4, "bench", "corpus");
51483
51759
  function parseCorpusFile(path) {
51484
51760
  let text;
51485
51761
  try {
51486
- text = readFileSync13(path, "utf8");
51762
+ text = readFileSync14(path, "utf8");
51487
51763
  } catch {
51488
51764
  return null;
51489
51765
  }
@@ -51533,7 +51809,7 @@ function loadCorpus() {
51533
51809
  for (const f of readdirSync4(CORPUS)) {
51534
51810
  if (!f.endsWith(".md"))
51535
51811
  continue;
51536
- const e = parseCorpusFile(join9(CORPUS, f));
51812
+ const e = parseCorpusFile(join10(CORPUS, f));
51537
51813
  if (e)
51538
51814
  entries.push(e);
51539
51815
  }
@@ -51545,10 +51821,10 @@ function loadCorpus() {
51545
51821
  function detect(entry) {
51546
51822
  const lang = entry.language;
51547
51823
  if (lang === "php") {
51548
- const tmp = join9(tmpdir(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
51549
- writeFileSync2(tmp, entry.code);
51550
- const r = analyzeTaint(tmp);
51551
- rmSync(tmp, { force: true });
51824
+ const tmp = join10(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
51825
+ writeFileSync3(tmp, entry.code);
51826
+ const r = analyzeDataFlow2(tmp);
51827
+ rmSync2(tmp, { force: true });
51552
51828
  if (r.findings.length > 0)
51553
51829
  return { detected: true, sinkKind: r.findings[0].sink };
51554
51830
  return { detected: false };
@@ -52357,12 +52633,42 @@ function createServer() {
52357
52633
  functions: r.summary.functions,
52358
52634
  methods: r.summary.methods,
52359
52635
  call_edges: r.summary.edges,
52636
+ entry_points: r.summary.entry_points,
52637
+ unreachable: r.summary.unreachable,
52360
52638
  results: r.findings,
52361
52639
  next_steps: guidance
52362
52640
  })
52363
52641
  }]
52364
52642
  };
52365
52643
  });
52644
+ server.registerTool("diff_analyze", {
52645
+ title: "Differential security analysis",
52646
+ description: "PHASE 4: compare two versions of code (files or a git commit range) and surface only security-sensitive changes — new vulnerabilities, fixed vulnerabilities, new sinks/sources, removed sanitizers, and removed authorization gates.",
52647
+ inputSchema: {
52648
+ old_path: string2().optional().describe("Old file path"),
52649
+ new_path: string2().optional().describe("New file path"),
52650
+ old_code: string2().optional().describe("Old source code (when not using paths)"),
52651
+ new_code: string2().optional().describe("New source code (when not using paths)"),
52652
+ language: string2().optional().describe("Language (php/javascript/python/java); auto-detected from path if omitted"),
52653
+ repo: string2().optional().describe("Git repo path (for git-diff mode)"),
52654
+ base: string2().optional().describe("Git base ref (default HEAD~1)"),
52655
+ head: string2().optional().describe("Git head ref (default HEAD)")
52656
+ }
52657
+ }, async ({ old_path, new_path, old_code, new_code, language, repo, base, head }) => {
52658
+ if (repo) {
52659
+ const r = analyzeGitDiff(repo, base ?? "HEAD~1", head ?? "HEAD");
52660
+ return { content: [{ type: "text", text: JSON.stringify(r) }] };
52661
+ }
52662
+ let result;
52663
+ if (old_path && new_path) {
52664
+ result = analyzeDifferentialFiles(old_path, new_path, language);
52665
+ } else if (old_code !== undefined && new_code !== undefined) {
52666
+ result = analyzeDifferential(old_code, new_code, language ?? "php", new_path ?? "target");
52667
+ } else {
52668
+ return { content: [{ type: "text", text: JSON.stringify({ error: "provide (old_path+new_path), (old_code+new_code), or (repo)" }) }] };
52669
+ }
52670
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
52671
+ });
52366
52672
  server.registerTool("taint_tree", {
52367
52673
  title: "Whole-tree taint scan",
52368
52674
  description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
@@ -52516,12 +52822,12 @@ async function serve() {
52516
52822
  }
52517
52823
 
52518
52824
  // src/cli.ts
52519
- import { readFileSync as readFileSync15, existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "node:fs";
52520
- import { join as join11, dirname as dirname2 } from "node:path";
52825
+ import { readFileSync as readFileSync16, existsSync as existsSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
52826
+ import { join as join12, dirname as dirname2 } from "node:path";
52521
52827
  import { homedir as homedir5 } from "node:os";
52522
52828
  import { fileURLToPath as fileURLToPath8 } from "node:url";
52523
52829
  import { spawnSync as spawnSync3 } from "node:child_process";
52524
- var ROOT6 = join11(fileURLToPath8(new URL(".", import.meta.url)), "..");
52830
+ var ROOT6 = join12(fileURLToPath8(new URL(".", import.meta.url)), "..");
52525
52831
  function which(cmd) {
52526
52832
  return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
52527
52833
  }
@@ -52556,7 +52862,7 @@ function runDoctor() {
52556
52862
  detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
52557
52863
  fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
52558
52864
  });
52559
- const dataOk = existsSync6(join11(ROOT6, "chains.json")) && existsSync6(join11(ROOT6, "tools-catalog.json"));
52865
+ const dataOk = existsSync6(join12(ROOT6, "chains.json")) && existsSync6(join12(ROOT6, "tools-catalog.json"));
52560
52866
  issues.push({
52561
52867
  name: "Data layers (chains + tools-catalog)",
52562
52868
  status: dataOk ? "ok" : "fail",
@@ -52584,21 +52890,21 @@ BlitzStrike — ${verdict}`);
52584
52890
  function resolveCommand() {
52585
52891
  if (which("blitzstrike"))
52586
52892
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
52587
- const src = join11(ROOT6, "src", "index.ts");
52893
+ const src = join12(ROOT6, "src", "index.ts");
52588
52894
  if (which("bun") && existsSync6(src))
52589
52895
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
52590
52896
  if (which("npx"))
52591
52897
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
52592
52898
  if (which("bunx"))
52593
52899
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
52594
- const dist = join11(ROOT6, "dist", "index.js");
52900
+ const dist = join12(ROOT6, "dist", "index.js");
52595
52901
  if (existsSync6(dist))
52596
52902
  return { command: "bun", args: [dist, "serve", "--mcp"] };
52597
52903
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
52598
52904
  }
52599
52905
  function readJson(p) {
52600
52906
  try {
52601
- return JSON.parse(readFileSync15(p, "utf8"));
52907
+ return JSON.parse(readFileSync16(p, "utf8"));
52602
52908
  } catch {
52603
52909
  return null;
52604
52910
  }
@@ -52613,7 +52919,7 @@ function jsonMcpServersWrite(p) {
52613
52919
  ...existing.mcpServers ?? {},
52614
52920
  blitzstrike: { command, args }
52615
52921
  };
52616
- writeFileSync4(p, JSON.stringify(existing, null, 2));
52922
+ writeFileSync5(p, JSON.stringify(existing, null, 2));
52617
52923
  };
52618
52924
  }
52619
52925
  function opencodeWrite(p) {
@@ -52626,22 +52932,22 @@ function opencodeWrite(p) {
52626
52932
  ...existing.mcp ?? {},
52627
52933
  blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
52628
52934
  };
52629
- writeFileSync4(p, JSON.stringify(existing, null, 2));
52935
+ writeFileSync5(p, JSON.stringify(existing, null, 2));
52630
52936
  copyOpenCodeAgents();
52631
52937
  };
52632
52938
  }
52633
52939
  function copyOpenCodeAgents() {
52634
- const srcDir = join11(ROOT6, "opencode-agents");
52635
- const dstDir = join11(homedir5(), ".config", "opencode", "agents");
52940
+ const srcDir = join12(ROOT6, "opencode-agents");
52941
+ const dstDir = join12(homedir5(), ".config", "opencode", "agents");
52636
52942
  if (!existsSync6(srcDir))
52637
52943
  return;
52638
52944
  if (!existsSync6(dstDir))
52639
52945
  mkdirSync4(dstDir, { recursive: true });
52640
52946
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
52641
- const src = join11(srcDir, f);
52642
- const dst = join11(dstDir, f);
52947
+ const src = join12(srcDir, f);
52948
+ const dst = join12(dstDir, f);
52643
52949
  if (existsSync6(src)) {
52644
- writeFileSync4(dst, readFileSync15(src, "utf8"));
52950
+ writeFileSync5(dst, readFileSync16(src, "utf8"));
52645
52951
  }
52646
52952
  }
52647
52953
  }
@@ -52650,7 +52956,7 @@ function codexWrite(p) {
52650
52956
  const dir = dirname2(p);
52651
52957
  if (!existsSync6(dir))
52652
52958
  mkdirSync4(dir, { recursive: true });
52653
- let existing = existsSync6(p) ? readFileSync15(p, "utf8") : "";
52959
+ let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
52654
52960
  if (!existing.trimEnd().endsWith(`
52655
52961
  `))
52656
52962
  existing += `
@@ -52661,7 +52967,7 @@ function codexWrite(p) {
52661
52967
  command = "${command}"
52662
52968
  args = ${JSON.stringify(args)}
52663
52969
  `;
52664
- writeFileSync4(p, existing);
52970
+ writeFileSync5(p, existing);
52665
52971
  };
52666
52972
  }
52667
52973
  function hermesWrite(p) {
@@ -52669,7 +52975,7 @@ function hermesWrite(p) {
52669
52975
  const dir = dirname2(p);
52670
52976
  if (!existsSync6(dir))
52671
52977
  mkdirSync4(dir, { recursive: true });
52672
- let existing = existsSync6(p) ? readFileSync15(p, "utf8") : "";
52978
+ let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
52673
52979
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
52674
52980
  if (!existing.trimEnd().endsWith(`
52675
52981
  `))
@@ -52690,80 +52996,80 @@ mcp_servers:
52690
52996
  args: ${JSON.stringify(args)}
52691
52997
  `;
52692
52998
  }
52693
- writeFileSync4(p, existing);
52999
+ writeFileSync5(p, existing);
52694
53000
  };
52695
53001
  }
52696
53002
  function detectAgents() {
52697
53003
  const home = homedir5();
52698
53004
  const agents = [];
52699
- const claudeJson = join11(home, ".claude.json");
53005
+ const claudeJson = join12(home, ".claude.json");
52700
53006
  agents.push({
52701
53007
  name: "Claude Code",
52702
53008
  detectPath: claudeJson,
52703
53009
  installed: () => existsSync6(claudeJson) || which("claude"),
52704
53010
  write: jsonMcpServersWrite(claudeJson)
52705
53011
  });
52706
- const claudeDesktop = join11(home, ".config", "Claude", "claude_desktop_config.json");
53012
+ const claudeDesktop = join12(home, ".config", "Claude", "claude_desktop_config.json");
52707
53013
  agents.push({
52708
53014
  name: "Claude Desktop",
52709
53015
  detectPath: claudeDesktop,
52710
53016
  installed: () => existsSync6(claudeDesktop),
52711
53017
  write: jsonMcpServersWrite(claudeDesktop)
52712
53018
  });
52713
- const cursor = join11(home, ".cursor", "mcp.json");
53019
+ const cursor = join12(home, ".cursor", "mcp.json");
52714
53020
  agents.push({
52715
53021
  name: "Cursor",
52716
53022
  detectPath: cursor,
52717
- installed: () => existsSync6(join11(home, ".cursor")) || which("cursor"),
53023
+ installed: () => existsSync6(join12(home, ".cursor")) || which("cursor"),
52718
53024
  write: jsonMcpServersWrite(cursor)
52719
53025
  });
52720
- const opencode = join11(home, ".config", "opencode", "opencode.jsonc");
53026
+ const opencode = join12(home, ".config", "opencode", "opencode.jsonc");
52721
53027
  agents.push({
52722
53028
  name: "OpenCode",
52723
53029
  detectPath: opencode,
52724
- installed: () => existsSync6(join11(home, ".config", "opencode")) || which("opencode"),
53030
+ installed: () => existsSync6(join12(home, ".config", "opencode")) || which("opencode"),
52725
53031
  write: opencodeWrite(opencode)
52726
53032
  });
52727
- const codex = join11(home, ".codex", "config.toml");
53033
+ const codex = join12(home, ".codex", "config.toml");
52728
53034
  agents.push({
52729
53035
  name: "Codex",
52730
53036
  detectPath: codex,
52731
- installed: () => existsSync6(join11(home, ".codex")) || which("codex"),
53037
+ installed: () => existsSync6(join12(home, ".codex")) || which("codex"),
52732
53038
  write: codexWrite(codex)
52733
53039
  });
52734
- const hermes = join11(home, ".hermes", "config.yaml");
53040
+ const hermes = join12(home, ".hermes", "config.yaml");
52735
53041
  agents.push({
52736
53042
  name: "Hermes",
52737
53043
  detectPath: hermes,
52738
- installed: () => existsSync6(join11(home, ".hermes")) || which("hermes"),
53044
+ installed: () => existsSync6(join12(home, ".hermes")) || which("hermes"),
52739
53045
  write: hermesWrite(hermes)
52740
53046
  });
52741
- const gemini = join11(home, ".gemini", "settings.json");
53047
+ const gemini = join12(home, ".gemini", "settings.json");
52742
53048
  agents.push({
52743
53049
  name: "Gemini CLI",
52744
53050
  detectPath: gemini,
52745
- installed: () => existsSync6(join11(home, ".gemini")) || which("gemini"),
53051
+ installed: () => existsSync6(join12(home, ".gemini")) || which("gemini"),
52746
53052
  write: jsonMcpServersWrite(gemini)
52747
53053
  });
52748
- const windsurf = join11(home, ".codeium", "windsurf", "mcp_config.json");
53054
+ const windsurf = join12(home, ".codeium", "windsurf", "mcp_config.json");
52749
53055
  agents.push({
52750
53056
  name: "Windsurf",
52751
53057
  detectPath: windsurf,
52752
- installed: () => existsSync6(join11(home, ".codeium")) || which("windsurf"),
53058
+ installed: () => existsSync6(join12(home, ".codeium")) || which("windsurf"),
52753
53059
  write: jsonMcpServersWrite(windsurf)
52754
53060
  });
52755
- const copilot = join11(home, ".copilot", "mcp.json");
53061
+ const copilot = join12(home, ".copilot", "mcp.json");
52756
53062
  agents.push({
52757
53063
  name: "Copilot",
52758
53064
  detectPath: copilot,
52759
- installed: () => existsSync6(join11(home, ".copilot")),
53065
+ installed: () => existsSync6(join12(home, ".copilot")),
52760
53066
  write: jsonMcpServersWrite(copilot)
52761
53067
  });
52762
- const cline = join11(home, ".cline", "mcp_settings.json");
53068
+ const cline = join12(home, ".cline", "mcp_settings.json");
52763
53069
  agents.push({
52764
53070
  name: "Cline",
52765
53071
  detectPath: cline,
52766
- installed: () => existsSync6(join11(home, ".cline")),
53072
+ installed: () => existsSync6(join12(home, ".cline")),
52767
53073
  write: jsonMcpServersWrite(cline)
52768
53074
  });
52769
53075
  return agents;
@@ -52807,15 +53113,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
52807
53113
  }
52808
53114
 
52809
53115
  // src/index.ts
52810
- import { readFileSync as readFileSync16, existsSync as existsSync7 } from "node:fs";
52811
- import { join as join12 } from "node:path";
53116
+ import { readFileSync as readFileSync17, existsSync as existsSync7 } from "node:fs";
53117
+ import { join as join13 } from "node:path";
52812
53118
  import { fileURLToPath as fileURLToPath9 } from "node:url";
52813
- var ROOT7 = join12(fileURLToPath9(new URL(".", import.meta.url)), "..");
53119
+ var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
52814
53120
  function readVersion() {
52815
53121
  try {
52816
- const p = join12(ROOT7, "package.json");
53122
+ const p = join13(ROOT7, "package.json");
52817
53123
  if (existsSync7(p))
52818
- return JSON.parse(readFileSync16(p, "utf8")).version ?? "1.0.0";
53124
+ return JSON.parse(readFileSync17(p, "utf8")).version ?? "1.0.0";
52819
53125
  } catch {}
52820
53126
  return "1.0.0";
52821
53127
  }