blitzstrike 1.0.35 → 1.0.37

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 +43 -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 +369 -97
  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" }]
@@ -31859,9 +31859,11 @@ function sinkClassOf2(call) {
31859
31859
  return null;
31860
31860
  let token = "";
31861
31861
  if (what.kind === "propertylookup") {
31862
- const base = rootVarName2(what.what);
31862
+ const baseNode = what.what;
31863
+ const base = rootVarName2(baseNode);
31863
31864
  const method = what.offset?.name ?? what.offset?.value;
31864
- token = `${base}->${method}(`;
31865
+ const baseStr = isNode3(baseNode) && (baseNode.kind === "variable" || baseNode.kind === "offsetlookup") ? `$${base}` : base;
31866
+ token = `${baseStr}->${method}(`;
31865
31867
  } else if (what.kind === "name") {
31866
31868
  token = `${what.name}(`;
31867
31869
  }
@@ -31918,26 +31920,27 @@ function summarizeFunction(fn, id, name, summaries) {
31918
31920
  }
31919
31921
  return summary;
31920
31922
  }
31921
- function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
31923
+ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
31922
31924
  for (const stmt of statements) {
31923
31925
  if (!isNode3(stmt))
31924
31926
  continue;
31925
31927
  const kind = stmt.kind;
31926
31928
  if (kind === "expressionstatement") {
31927
- analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31929
+ analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31928
31930
  } else if (kind === "if") {
31929
31931
  const condGates = authGatesOf(stmt.test);
31930
31932
  const branchAuth = [...activeAuth, ...condGates];
31933
+ const condValidating = validationGatesOf(stmt.test);
31931
31934
  const thenStmts = stmt.body?.kind === "block" ? stmt.body.children ?? [] : [stmt.body];
31932
31935
  const thenEnv = cloneEnv(env);
31933
31936
  const thenCtx = { sinks: [], returnsTainted: false, calls: [] };
31934
- analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries);
31937
+ analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries, validating || condValidating);
31935
31938
  mergeCtx(ctx, thenCtx);
31936
31939
  if (stmt.alternate) {
31937
31940
  const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children ?? [] : [stmt.alternate];
31938
31941
  const altEnv = cloneEnv(env);
31939
31942
  const altCtx = { sinks: [], returnsTainted: false, calls: [] };
31940
- analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries);
31943
+ analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries, validating);
31941
31944
  mergeCtx(ctx, altCtx);
31942
31945
  for (const [k, v] of thenEnv) {
31943
31946
  const alt = altEnv.get(k);
@@ -31948,25 +31951,25 @@ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeA
31948
31951
  env.set(k, v);
31949
31952
  }
31950
31953
  } else if (kind === "block") {
31951
- analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries);
31954
+ analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31952
31955
  } else if (kind === "return") {
31953
- const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31956
+ const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31954
31957
  if (t.tainted)
31955
31958
  ctx.returnsTainted = true;
31956
31959
  } else if (kind === "echo" || kind === "print") {
31957
- analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31960
+ analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31958
31961
  } else {
31959
31962
  for (const c of children3(stmt)) {
31960
31963
  if (c.kind === "block" || c.kind === "if") {
31961
- analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries);
31964
+ analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31962
31965
  } else {
31963
- analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31966
+ analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31964
31967
  }
31965
31968
  }
31966
31969
  }
31967
31970
  }
31968
31971
  }
31969
- function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
31972
+ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
31970
31973
  if (!isNode3(node))
31971
31974
  return CLEAN;
31972
31975
  const src = sourceOf2(node);
@@ -31974,7 +31977,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
31974
31977
  return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
31975
31978
  if (node.kind === "assign") {
31976
31979
  const byref = node.operator === "=&";
31977
- const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31980
+ const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
31978
31981
  const lhs = node.left;
31979
31982
  if (isNode3(lhs) && lhs.kind === "variable") {
31980
31983
  const varName = lhs.name;
@@ -32006,7 +32009,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32006
32009
  }
32007
32010
  if (node.kind === "call") {
32008
32011
  const sink = sinkClassOf2(node);
32009
- const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries));
32012
+ const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
32010
32013
  const taintedArgs = argTaints.filter((t) => t.tainted);
32011
32014
  if (sink) {
32012
32015
  if (taintedArgs.length > 0) {
@@ -32016,7 +32019,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32016
32019
  ctx.sinks.push({ sink: sink.token, line: node.loc?.start?.line ?? 0, sanitized, authGated: gates.length > 0, authGates: gates });
32017
32020
  }
32018
32021
  for (const a of node.arguments)
32019
- analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32022
+ analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32020
32023
  return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
32021
32024
  }
32022
32025
  const fnName = rootVarName2(node.what);
@@ -32059,6 +32062,9 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32059
32062
  }
32060
32063
  }
32061
32064
  }
32065
+ if (isNode3(node.what) && node.what.kind === "propertylookup") {
32066
+ analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32067
+ }
32062
32068
  return CLEAN;
32063
32069
  }
32064
32070
  if (node.kind === "variable") {
@@ -32072,7 +32078,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32072
32078
  if (t)
32073
32079
  return { ...t };
32074
32080
  }
32075
- return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32081
+ return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32076
32082
  }
32077
32083
  if (node.kind === "propertylookup") {
32078
32084
  const objName = rootVarName2(node.what);
@@ -32082,35 +32088,55 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32082
32088
  if (pt)
32083
32089
  return { ...pt };
32084
32090
  }
32085
- return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32091
+ return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32086
32092
  }
32087
32093
  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;
32094
+ const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32095
+ const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating) : CLEAN;
32090
32096
  return mergeTaint(t, f);
32091
32097
  }
32092
32098
  if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
32093
32099
  const parts = children3(node);
32094
- const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries));
32100
+ const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
32095
32101
  return mergeTaint(...taints);
32096
32102
  }
32097
32103
  if (node.kind === "encapsedpart") {
32098
- return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32104
+ return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32099
32105
  }
32100
32106
  if (node.kind === "echo" || node.kind === "print") {
32101
32107
  const exprs = node.expressions ?? [node.expression];
32102
32108
  const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
32103
32109
  let ret = CLEAN;
32104
32110
  for (const e of exprs) {
32105
- const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32111
+ const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32106
32112
  if (t.tainted && cls) {
32107
32113
  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 });
32114
+ 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
32115
  }
32110
32116
  ret = mergeTaint(ret, t);
32111
32117
  }
32112
32118
  return ret;
32113
32119
  }
32120
+ if (node.kind === "include") {
32121
+ const base = node.require ? "require" : "include";
32122
+ const suffix = node.once ? "_once" : "";
32123
+ const sinkToken = base + suffix + "(";
32124
+ const cls = classifySink2(sinkToken);
32125
+ const t = analyzeExpr(node.target, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32126
+ if (t.tainted && cls) {
32127
+ const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(node))];
32128
+ ctx.sinks.push({ sink: sinkToken, line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
32129
+ }
32130
+ return t;
32131
+ }
32132
+ if (node.kind === "eval") {
32133
+ const cls = classifySink2("eval(");
32134
+ const t = analyzeExpr(node.source, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32135
+ if (t.tainted && cls) {
32136
+ ctx.sinks.push({ sink: "eval(", line: node.loc?.start?.line ?? 0, sanitized: false, authGated: activeAuth.length > 0, authGates: activeAuth });
32137
+ }
32138
+ return t;
32139
+ }
32114
32140
  return CLEAN;
32115
32141
  }
32116
32142
  function cloneEnv(env) {
@@ -32133,6 +32159,13 @@ function authGatesOf(node) {
32133
32159
  return [];
32134
32160
  return AUTH_GATES.filter((g) => JSON.stringify(node).includes(g));
32135
32161
  }
32162
+ var VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
32163
+ function validationGatesOf(node) {
32164
+ if (!isNode3(node))
32165
+ return false;
32166
+ const text = JSON.stringify(node);
32167
+ return VALIDATION_GATES.some((g) => text.includes(g));
32168
+ }
32136
32169
  function collectDecls(ast, out) {
32137
32170
  walkDecls(ast, out);
32138
32171
  }
@@ -32293,6 +32326,12 @@ function analyzeDataFlow2(path) {
32293
32326
  return result;
32294
32327
  }
32295
32328
 
32329
+ // src/differential.ts
32330
+ import { writeFileSync as writeFileSync2, rmSync, mkdtempSync, readFileSync as readFileSync13 } from "node:fs";
32331
+ import { join as join9 } from "node:path";
32332
+ import { tmpdir } from "node:os";
32333
+ import { execSync } from "node:child_process";
32334
+
32296
32335
  // src/universal-taint.ts
32297
32336
  function runTaintEngine(adapter, code, file) {
32298
32337
  const result = { file, language: adapter.language, findings: [], suppressed: 0 };
@@ -50346,8 +50385,13 @@ var JS_SINKS = [
50346
50385
  [/document\.write/i, "html_render", "CWE-79"],
50347
50386
  [/fs\.writeFile/i, "file_operations", "CWE-434"],
50348
50387
  [/fs\.appendFile/i, "file_operations", "CWE-434"],
50388
+ [/fs\.readFile/i, "path_traversal", "CWE-22"],
50389
+ [/fs\.readFileSync/i, "path_traversal", "CWE-22"],
50390
+ [/fs\.unlink/i, "file_operations", "CWE-434"],
50349
50391
  [/require\s*\(/i, "file_inclusion", "CWE-98"],
50350
50392
  [/fetch\s*\(/i, "http_request", "CWE-918"],
50393
+ [/http\.get|https\.get|axios/i, "http_request", "CWE-918"],
50394
+ [/http\.request|https\.request/i, "http_request", "CWE-918"],
50351
50395
  [/redirect\s*\(/i, "redirect", "CWE-601"],
50352
50396
  [/res\.redirect/i, "redirect", "CWE-601"]
50353
50397
  ];
@@ -50434,6 +50478,8 @@ var jsAdapter = {
50434
50478
  argVars.push(c.name);
50435
50479
  for (const [re, kind, cwe] of JS_SINKS) {
50436
50480
  if (re.test(fullCallText) || re.test(calleeText + "(")) {
50481
+ if (kind === "sql_execution" && /\?/.test(fullCallText) && /,\s*\[/.test(fullCallText))
50482
+ continue;
50437
50483
  for (const v of argVars)
50438
50484
  sinks.push({ variable: v, kind, line: n.loc?.start?.line ?? 0, cwe });
50439
50485
  }
@@ -50472,6 +50518,8 @@ var PY_SINKS = [
50472
50518
  [/\.save\s*\(/i, "file_operations", "CWE-434"],
50473
50519
  [/render_template/i, "html_render", "CWE-79"],
50474
50520
  [/redirect\s*\(/i, "redirect", "CWE-601"],
50521
+ [/requests\.(get|post|put|head|request)\s*\(/i, "http_request", "CWE-918"],
50522
+ [/urllib\.request/i, "http_request", "CWE-918"],
50475
50523
  [/pickle\.loads\s*\(/i, "deserialization", "CWE-502"],
50476
50524
  [/yaml\.load\s*\(/i, "deserialization", "CWE-502"],
50477
50525
  [/os\.popen\s*\(/i, "command_execution", "CWE-78"],
@@ -50556,9 +50604,14 @@ var pythonAdapter = {
50556
50604
  if (m[1] !== callee.trim().split(".").pop())
50557
50605
  argVars.push(m[1]);
50558
50606
  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 });
50607
+ if (!re.test(full))
50608
+ continue;
50609
+ if (kind === "sql_execution" && /\?/.test(full) && /,\s*\(/.test(full))
50610
+ continue;
50611
+ if (kind === "command_execution" && /subprocess/.test(full) && /\[/.test(full))
50612
+ continue;
50613
+ for (const v of argVars)
50614
+ sinks.push({ variable: v, kind, line: lineOf(n), cwe });
50562
50615
  }
50563
50616
  for (const [re, kinds] of PY_SANITIZERS) {
50564
50617
  if (re.test(full))
@@ -50594,6 +50647,10 @@ var JAVA_SINKS = [
50594
50647
  [/readObject\s*\(/i, "deserialization", "CWE-502"],
50595
50648
  [/new File\s*\(/i, "file_operations", "CWE-434"],
50596
50649
  [/\.getInputStream\s*\(/i, "http_request", "CWE-918"],
50650
+ [/new URL\s*\(/i, "http_request", "CWE-918"],
50651
+ [/openConnection\s*\(/i, "http_request", "CWE-918"],
50652
+ [/DocumentBuilder|SAXParser|XMLReader/i, "xml_processing", "CWE-611"],
50653
+ [/\.parse\s*\(/i, "xml_processing", "CWE-611"],
50597
50654
  [/sendRedirect\s*\(/i, "redirect", "CWE-601"],
50598
50655
  [/\.forward\s*\(/i, "redirect", "CWE-601"]
50599
50656
  ];
@@ -50661,6 +50718,193 @@ registerLanguage(jsAdapter);
50661
50718
  registerLanguage(pythonAdapter);
50662
50719
  registerLanguage(javaAdapter);
50663
50720
 
50721
+ // src/differential.ts
50722
+ init_scanner();
50723
+ function runEngine(code, language, file) {
50724
+ if (language === "php") {
50725
+ const dir = mkdtempSync(join9(tmpdir(), "blitz-diff-"));
50726
+ const tmp = join9(dir, "old.php");
50727
+ writeFileSync2(tmp, code);
50728
+ try {
50729
+ const r = analyzeDataFlow2(tmp);
50730
+ return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
50731
+ } finally {
50732
+ rmSync(dir, { recursive: true, force: true });
50733
+ }
50734
+ }
50735
+ const r = analyzeTaintUniversal(code, file);
50736
+ return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.sink, source: f.source_kind, cwe: f.cwe }));
50737
+ }
50738
+ function diffLines(oldText, newText) {
50739
+ const a = oldText.split(`
50740
+ `);
50741
+ const b = newText.split(`
50742
+ `);
50743
+ const m = a.length;
50744
+ const n = b.length;
50745
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
50746
+ for (let i = m - 1;i >= 0; i--) {
50747
+ for (let j = n - 1;j >= 0; j--) {
50748
+ dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
50749
+ }
50750
+ }
50751
+ const added = [];
50752
+ const removed = [];
50753
+ let i = 0;
50754
+ let j = 0;
50755
+ while (i < m && j < n) {
50756
+ if (a[i] === b[j]) {
50757
+ i++;
50758
+ j++;
50759
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
50760
+ removed.push({ line: i + 1, text: a[i] });
50761
+ i++;
50762
+ } else {
50763
+ added.push({ line: j + 1, text: b[j] });
50764
+ j++;
50765
+ }
50766
+ }
50767
+ while (i < m) {
50768
+ removed.push({ line: i + 1, text: a[i] });
50769
+ i++;
50770
+ }
50771
+ while (j < n) {
50772
+ added.push({ line: j + 1, text: b[j] });
50773
+ j++;
50774
+ }
50775
+ return { added, removed };
50776
+ }
50777
+ function analyzeDifferential(oldCode, newCode, language, file = "target") {
50778
+ const changes = [];
50779
+ const oldFindings = runEngine(oldCode, language, file);
50780
+ const newFindings = runEngine(newCode, language, file);
50781
+ const key = (f) => `${f.category}|${f.sink}|${f.sink_line}`;
50782
+ const oldKeys = new Set(oldFindings.map(key));
50783
+ const newKeys = new Set(newFindings.map(key));
50784
+ for (const f of newFindings) {
50785
+ if (!oldKeys.has(key(f))) {
50786
+ changes.push({
50787
+ kind: "new_vulnerability",
50788
+ severity: "high",
50789
+ sink: f.sink,
50790
+ category: f.category,
50791
+ source: f.source,
50792
+ cwe: f.cwe,
50793
+ line: f.sink_line,
50794
+ detail: `new ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
50795
+ });
50796
+ }
50797
+ }
50798
+ for (const f of oldFindings) {
50799
+ if (!newKeys.has(key(f))) {
50800
+ changes.push({
50801
+ kind: "fixed_vulnerability",
50802
+ severity: "low",
50803
+ sink: f.sink,
50804
+ category: f.category,
50805
+ source: f.source,
50806
+ cwe: f.cwe,
50807
+ line: f.sink_line,
50808
+ detail: `removed ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
50809
+ });
50810
+ }
50811
+ }
50812
+ const { added, removed } = diffLines(oldCode, newCode);
50813
+ for (const l of added) {
50814
+ const sink = classifySink2(l.text);
50815
+ if (sink) {
50816
+ changes.push({
50817
+ kind: "new_sink",
50818
+ severity: "medium",
50819
+ sink: l.text.trim(),
50820
+ category: sink.category,
50821
+ cwe: sink.cwe,
50822
+ line: l.line,
50823
+ detail: `added sink ${l.text.trim()} (${sink.category})`
50824
+ });
50825
+ }
50826
+ const source = classifySource(l.text);
50827
+ if (source) {
50828
+ changes.push({
50829
+ kind: "new_source",
50830
+ severity: source.attacker_controlled ? "medium" : "low",
50831
+ source: source.label,
50832
+ line: l.line,
50833
+ detail: `added source ${l.text.trim()} (${source.label})`
50834
+ });
50835
+ }
50836
+ }
50837
+ for (const l of removed) {
50838
+ const san = findSanitizers(l.text);
50839
+ if (san.length > 0) {
50840
+ changes.push({
50841
+ kind: "sanitizer_removed",
50842
+ severity: "high",
50843
+ line: l.line,
50844
+ detail: `removed sanitizer ${san.map((s) => s.label).join(", ")} (${l.text.trim()})`
50845
+ });
50846
+ }
50847
+ const gates = AUTH_GATES.filter((g) => l.text.includes(g));
50848
+ if (gates.length > 0) {
50849
+ changes.push({
50850
+ kind: "authorization_removed",
50851
+ severity: "high",
50852
+ line: l.line,
50853
+ detail: `removed authorization gate ${gates.join(", ")} (${l.text.trim()})`
50854
+ });
50855
+ }
50856
+ }
50857
+ const rank = { high: 0, medium: 1, low: 2 };
50858
+ changes.sort((x, y) => rank[x.severity] - rank[y.severity] || x.line - y.line);
50859
+ const count = (k) => changes.filter((c) => c.kind === k).length;
50860
+ return {
50861
+ language,
50862
+ changes,
50863
+ summary: {
50864
+ new_vulnerabilities: count("new_vulnerability"),
50865
+ fixed_vulnerabilities: count("fixed_vulnerability"),
50866
+ new_sinks: count("new_sink"),
50867
+ new_sources: count("new_source"),
50868
+ sanitizers_removed: count("sanitizer_removed"),
50869
+ authorizations_removed: count("authorization_removed")
50870
+ }
50871
+ };
50872
+ }
50873
+ function languageOf(file) {
50874
+ const adapter = detectLanguage(file);
50875
+ return adapter?.language ?? "php";
50876
+ }
50877
+ function analyzeDifferentialFiles(oldPath, newPath, language) {
50878
+ const oldCode = readFileSync13(oldPath, "utf8");
50879
+ const newCode = readFileSync13(newPath, "utf8");
50880
+ const lang = language ?? languageOf(newPath);
50881
+ return analyzeDifferential(oldCode, newCode, lang, newPath);
50882
+ }
50883
+ var SOURCE_EXTS = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
50884
+ function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
50885
+ const git = (args) => {
50886
+ try {
50887
+ return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
50888
+ } catch {
50889
+ return "";
50890
+ }
50891
+ };
50892
+ const changed = git(`diff --name-only ${base} ${head}`).split(`
50893
+ `).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
50894
+ const files = [];
50895
+ for (const file of changed) {
50896
+ const oldCode = git(`show ${base}:${file}`);
50897
+ const newCode = git(`show ${head}:${file}`);
50898
+ if (!newCode)
50899
+ continue;
50900
+ const lang = languageOf(file);
50901
+ files.push({ file, result: analyzeDifferential(oldCode || "", newCode, lang, file) });
50902
+ }
50903
+ const total_changes = files.reduce((n, f) => n + f.result.changes.length, 0);
50904
+ const security_sensitive = files.reduce((n, f) => n + f.result.changes.filter((c) => c.severity === "high").length, 0);
50905
+ return { repo, base, head, files, total_changes, security_sensitive };
50906
+ }
50907
+
50664
50908
  // src/guidance.ts
50665
50909
  var SINK_DIRECTIVES = {
50666
50910
  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 +51718,16 @@ function reportJson(findings, opts = {}) {
51474
51718
  }
51475
51719
 
51476
51720
  // 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";
51721
+ import { readFileSync as readFileSync14, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
51722
+ import { join as join10 } from "node:path";
51479
51723
  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");
51724
+ import { tmpdir as tmpdir2 } from "node:os";
51725
+ var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
51726
+ var CORPUS = join10(ROOT4, "bench", "corpus");
51483
51727
  function parseCorpusFile(path) {
51484
51728
  let text;
51485
51729
  try {
51486
- text = readFileSync13(path, "utf8");
51730
+ text = readFileSync14(path, "utf8");
51487
51731
  } catch {
51488
51732
  return null;
51489
51733
  }
@@ -51533,7 +51777,7 @@ function loadCorpus() {
51533
51777
  for (const f of readdirSync4(CORPUS)) {
51534
51778
  if (!f.endsWith(".md"))
51535
51779
  continue;
51536
- const e = parseCorpusFile(join9(CORPUS, f));
51780
+ const e = parseCorpusFile(join10(CORPUS, f));
51537
51781
  if (e)
51538
51782
  entries.push(e);
51539
51783
  }
@@ -51545,10 +51789,10 @@ function loadCorpus() {
51545
51789
  function detect(entry) {
51546
51790
  const lang = entry.language;
51547
51791
  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 });
51792
+ const tmp = join10(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
51793
+ writeFileSync3(tmp, entry.code);
51794
+ const r = analyzeDataFlow2(tmp);
51795
+ rmSync2(tmp, { force: true });
51552
51796
  if (r.findings.length > 0)
51553
51797
  return { detected: true, sinkKind: r.findings[0].sink };
51554
51798
  return { detected: false };
@@ -52363,6 +52607,34 @@ function createServer() {
52363
52607
  }]
52364
52608
  };
52365
52609
  });
52610
+ server.registerTool("diff_analyze", {
52611
+ title: "Differential security analysis",
52612
+ 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.",
52613
+ inputSchema: {
52614
+ old_path: string2().optional().describe("Old file path"),
52615
+ new_path: string2().optional().describe("New file path"),
52616
+ old_code: string2().optional().describe("Old source code (when not using paths)"),
52617
+ new_code: string2().optional().describe("New source code (when not using paths)"),
52618
+ language: string2().optional().describe("Language (php/javascript/python/java); auto-detected from path if omitted"),
52619
+ repo: string2().optional().describe("Git repo path (for git-diff mode)"),
52620
+ base: string2().optional().describe("Git base ref (default HEAD~1)"),
52621
+ head: string2().optional().describe("Git head ref (default HEAD)")
52622
+ }
52623
+ }, async ({ old_path, new_path, old_code, new_code, language, repo, base, head }) => {
52624
+ if (repo) {
52625
+ const r = analyzeGitDiff(repo, base ?? "HEAD~1", head ?? "HEAD");
52626
+ return { content: [{ type: "text", text: JSON.stringify(r) }] };
52627
+ }
52628
+ let result;
52629
+ if (old_path && new_path) {
52630
+ result = analyzeDifferentialFiles(old_path, new_path, language);
52631
+ } else if (old_code !== undefined && new_code !== undefined) {
52632
+ result = analyzeDifferential(old_code, new_code, language ?? "php", new_path ?? "target");
52633
+ } else {
52634
+ return { content: [{ type: "text", text: JSON.stringify({ error: "provide (old_path+new_path), (old_code+new_code), or (repo)" }) }] };
52635
+ }
52636
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
52637
+ });
52366
52638
  server.registerTool("taint_tree", {
52367
52639
  title: "Whole-tree taint scan",
52368
52640
  description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
@@ -52516,12 +52788,12 @@ async function serve() {
52516
52788
  }
52517
52789
 
52518
52790
  // 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";
52791
+ import { readFileSync as readFileSync16, existsSync as existsSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
52792
+ import { join as join12, dirname as dirname2 } from "node:path";
52521
52793
  import { homedir as homedir5 } from "node:os";
52522
52794
  import { fileURLToPath as fileURLToPath8 } from "node:url";
52523
52795
  import { spawnSync as spawnSync3 } from "node:child_process";
52524
- var ROOT6 = join11(fileURLToPath8(new URL(".", import.meta.url)), "..");
52796
+ var ROOT6 = join12(fileURLToPath8(new URL(".", import.meta.url)), "..");
52525
52797
  function which(cmd) {
52526
52798
  return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
52527
52799
  }
@@ -52556,7 +52828,7 @@ function runDoctor() {
52556
52828
  detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
52557
52829
  fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
52558
52830
  });
52559
- const dataOk = existsSync6(join11(ROOT6, "chains.json")) && existsSync6(join11(ROOT6, "tools-catalog.json"));
52831
+ const dataOk = existsSync6(join12(ROOT6, "chains.json")) && existsSync6(join12(ROOT6, "tools-catalog.json"));
52560
52832
  issues.push({
52561
52833
  name: "Data layers (chains + tools-catalog)",
52562
52834
  status: dataOk ? "ok" : "fail",
@@ -52584,21 +52856,21 @@ BlitzStrike — ${verdict}`);
52584
52856
  function resolveCommand() {
52585
52857
  if (which("blitzstrike"))
52586
52858
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
52587
- const src = join11(ROOT6, "src", "index.ts");
52859
+ const src = join12(ROOT6, "src", "index.ts");
52588
52860
  if (which("bun") && existsSync6(src))
52589
52861
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
52590
52862
  if (which("npx"))
52591
52863
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
52592
52864
  if (which("bunx"))
52593
52865
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
52594
- const dist = join11(ROOT6, "dist", "index.js");
52866
+ const dist = join12(ROOT6, "dist", "index.js");
52595
52867
  if (existsSync6(dist))
52596
52868
  return { command: "bun", args: [dist, "serve", "--mcp"] };
52597
52869
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
52598
52870
  }
52599
52871
  function readJson(p) {
52600
52872
  try {
52601
- return JSON.parse(readFileSync15(p, "utf8"));
52873
+ return JSON.parse(readFileSync16(p, "utf8"));
52602
52874
  } catch {
52603
52875
  return null;
52604
52876
  }
@@ -52613,7 +52885,7 @@ function jsonMcpServersWrite(p) {
52613
52885
  ...existing.mcpServers ?? {},
52614
52886
  blitzstrike: { command, args }
52615
52887
  };
52616
- writeFileSync4(p, JSON.stringify(existing, null, 2));
52888
+ writeFileSync5(p, JSON.stringify(existing, null, 2));
52617
52889
  };
52618
52890
  }
52619
52891
  function opencodeWrite(p) {
@@ -52626,22 +52898,22 @@ function opencodeWrite(p) {
52626
52898
  ...existing.mcp ?? {},
52627
52899
  blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
52628
52900
  };
52629
- writeFileSync4(p, JSON.stringify(existing, null, 2));
52901
+ writeFileSync5(p, JSON.stringify(existing, null, 2));
52630
52902
  copyOpenCodeAgents();
52631
52903
  };
52632
52904
  }
52633
52905
  function copyOpenCodeAgents() {
52634
- const srcDir = join11(ROOT6, "opencode-agents");
52635
- const dstDir = join11(homedir5(), ".config", "opencode", "agents");
52906
+ const srcDir = join12(ROOT6, "opencode-agents");
52907
+ const dstDir = join12(homedir5(), ".config", "opencode", "agents");
52636
52908
  if (!existsSync6(srcDir))
52637
52909
  return;
52638
52910
  if (!existsSync6(dstDir))
52639
52911
  mkdirSync4(dstDir, { recursive: true });
52640
52912
  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);
52913
+ const src = join12(srcDir, f);
52914
+ const dst = join12(dstDir, f);
52643
52915
  if (existsSync6(src)) {
52644
- writeFileSync4(dst, readFileSync15(src, "utf8"));
52916
+ writeFileSync5(dst, readFileSync16(src, "utf8"));
52645
52917
  }
52646
52918
  }
52647
52919
  }
@@ -52650,7 +52922,7 @@ function codexWrite(p) {
52650
52922
  const dir = dirname2(p);
52651
52923
  if (!existsSync6(dir))
52652
52924
  mkdirSync4(dir, { recursive: true });
52653
- let existing = existsSync6(p) ? readFileSync15(p, "utf8") : "";
52925
+ let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
52654
52926
  if (!existing.trimEnd().endsWith(`
52655
52927
  `))
52656
52928
  existing += `
@@ -52661,7 +52933,7 @@ function codexWrite(p) {
52661
52933
  command = "${command}"
52662
52934
  args = ${JSON.stringify(args)}
52663
52935
  `;
52664
- writeFileSync4(p, existing);
52936
+ writeFileSync5(p, existing);
52665
52937
  };
52666
52938
  }
52667
52939
  function hermesWrite(p) {
@@ -52669,7 +52941,7 @@ function hermesWrite(p) {
52669
52941
  const dir = dirname2(p);
52670
52942
  if (!existsSync6(dir))
52671
52943
  mkdirSync4(dir, { recursive: true });
52672
- let existing = existsSync6(p) ? readFileSync15(p, "utf8") : "";
52944
+ let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
52673
52945
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
52674
52946
  if (!existing.trimEnd().endsWith(`
52675
52947
  `))
@@ -52690,80 +52962,80 @@ mcp_servers:
52690
52962
  args: ${JSON.stringify(args)}
52691
52963
  `;
52692
52964
  }
52693
- writeFileSync4(p, existing);
52965
+ writeFileSync5(p, existing);
52694
52966
  };
52695
52967
  }
52696
52968
  function detectAgents() {
52697
52969
  const home = homedir5();
52698
52970
  const agents = [];
52699
- const claudeJson = join11(home, ".claude.json");
52971
+ const claudeJson = join12(home, ".claude.json");
52700
52972
  agents.push({
52701
52973
  name: "Claude Code",
52702
52974
  detectPath: claudeJson,
52703
52975
  installed: () => existsSync6(claudeJson) || which("claude"),
52704
52976
  write: jsonMcpServersWrite(claudeJson)
52705
52977
  });
52706
- const claudeDesktop = join11(home, ".config", "Claude", "claude_desktop_config.json");
52978
+ const claudeDesktop = join12(home, ".config", "Claude", "claude_desktop_config.json");
52707
52979
  agents.push({
52708
52980
  name: "Claude Desktop",
52709
52981
  detectPath: claudeDesktop,
52710
52982
  installed: () => existsSync6(claudeDesktop),
52711
52983
  write: jsonMcpServersWrite(claudeDesktop)
52712
52984
  });
52713
- const cursor = join11(home, ".cursor", "mcp.json");
52985
+ const cursor = join12(home, ".cursor", "mcp.json");
52714
52986
  agents.push({
52715
52987
  name: "Cursor",
52716
52988
  detectPath: cursor,
52717
- installed: () => existsSync6(join11(home, ".cursor")) || which("cursor"),
52989
+ installed: () => existsSync6(join12(home, ".cursor")) || which("cursor"),
52718
52990
  write: jsonMcpServersWrite(cursor)
52719
52991
  });
52720
- const opencode = join11(home, ".config", "opencode", "opencode.jsonc");
52992
+ const opencode = join12(home, ".config", "opencode", "opencode.jsonc");
52721
52993
  agents.push({
52722
52994
  name: "OpenCode",
52723
52995
  detectPath: opencode,
52724
- installed: () => existsSync6(join11(home, ".config", "opencode")) || which("opencode"),
52996
+ installed: () => existsSync6(join12(home, ".config", "opencode")) || which("opencode"),
52725
52997
  write: opencodeWrite(opencode)
52726
52998
  });
52727
- const codex = join11(home, ".codex", "config.toml");
52999
+ const codex = join12(home, ".codex", "config.toml");
52728
53000
  agents.push({
52729
53001
  name: "Codex",
52730
53002
  detectPath: codex,
52731
- installed: () => existsSync6(join11(home, ".codex")) || which("codex"),
53003
+ installed: () => existsSync6(join12(home, ".codex")) || which("codex"),
52732
53004
  write: codexWrite(codex)
52733
53005
  });
52734
- const hermes = join11(home, ".hermes", "config.yaml");
53006
+ const hermes = join12(home, ".hermes", "config.yaml");
52735
53007
  agents.push({
52736
53008
  name: "Hermes",
52737
53009
  detectPath: hermes,
52738
- installed: () => existsSync6(join11(home, ".hermes")) || which("hermes"),
53010
+ installed: () => existsSync6(join12(home, ".hermes")) || which("hermes"),
52739
53011
  write: hermesWrite(hermes)
52740
53012
  });
52741
- const gemini = join11(home, ".gemini", "settings.json");
53013
+ const gemini = join12(home, ".gemini", "settings.json");
52742
53014
  agents.push({
52743
53015
  name: "Gemini CLI",
52744
53016
  detectPath: gemini,
52745
- installed: () => existsSync6(join11(home, ".gemini")) || which("gemini"),
53017
+ installed: () => existsSync6(join12(home, ".gemini")) || which("gemini"),
52746
53018
  write: jsonMcpServersWrite(gemini)
52747
53019
  });
52748
- const windsurf = join11(home, ".codeium", "windsurf", "mcp_config.json");
53020
+ const windsurf = join12(home, ".codeium", "windsurf", "mcp_config.json");
52749
53021
  agents.push({
52750
53022
  name: "Windsurf",
52751
53023
  detectPath: windsurf,
52752
- installed: () => existsSync6(join11(home, ".codeium")) || which("windsurf"),
53024
+ installed: () => existsSync6(join12(home, ".codeium")) || which("windsurf"),
52753
53025
  write: jsonMcpServersWrite(windsurf)
52754
53026
  });
52755
- const copilot = join11(home, ".copilot", "mcp.json");
53027
+ const copilot = join12(home, ".copilot", "mcp.json");
52756
53028
  agents.push({
52757
53029
  name: "Copilot",
52758
53030
  detectPath: copilot,
52759
- installed: () => existsSync6(join11(home, ".copilot")),
53031
+ installed: () => existsSync6(join12(home, ".copilot")),
52760
53032
  write: jsonMcpServersWrite(copilot)
52761
53033
  });
52762
- const cline = join11(home, ".cline", "mcp_settings.json");
53034
+ const cline = join12(home, ".cline", "mcp_settings.json");
52763
53035
  agents.push({
52764
53036
  name: "Cline",
52765
53037
  detectPath: cline,
52766
- installed: () => existsSync6(join11(home, ".cline")),
53038
+ installed: () => existsSync6(join12(home, ".cline")),
52767
53039
  write: jsonMcpServersWrite(cline)
52768
53040
  });
52769
53041
  return agents;
@@ -52807,15 +53079,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
52807
53079
  }
52808
53080
 
52809
53081
  // src/index.ts
52810
- import { readFileSync as readFileSync16, existsSync as existsSync7 } from "node:fs";
52811
- import { join as join12 } from "node:path";
53082
+ import { readFileSync as readFileSync17, existsSync as existsSync7 } from "node:fs";
53083
+ import { join as join13 } from "node:path";
52812
53084
  import { fileURLToPath as fileURLToPath9 } from "node:url";
52813
- var ROOT7 = join12(fileURLToPath9(new URL(".", import.meta.url)), "..");
53085
+ var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
52814
53086
  function readVersion() {
52815
53087
  try {
52816
- const p = join12(ROOT7, "package.json");
53088
+ const p = join13(ROOT7, "package.json");
52817
53089
  if (existsSync7(p))
52818
- return JSON.parse(readFileSync16(p, "utf8")).version ?? "1.0.0";
53090
+ return JSON.parse(readFileSync17(p, "utf8")).version ?? "1.0.0";
52819
53091
  } catch {}
52820
53092
  return "1.0.0";
52821
53093
  }