blitzstrike 1.0.91 → 1.0.93

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 (3) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.js +616 -561
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14898,8 +14898,8 @@ var require_src = __commonJS(function(exports, module) {
14898
14898
  });
14899
14899
 
14900
14900
  // src/sync.ts
14901
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync17, readdirSync as readdirSync6, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
14902
- import { join as join13 } from "node:path";
14901
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as readFileSync17, readdirSync as readdirSync6, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
14902
+ import { join as join14 } from "node:path";
14903
14903
  import { homedir as homedir4 } from "node:os";
14904
14904
  import { fileURLToPath as fileURLToPath8 } from "node:url";
14905
14905
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -14916,7 +14916,7 @@ function countFiles(dir) {
14916
14916
  const walk = (d) => {
14917
14917
  for (const e of readdirSync6(d, { withFileTypes: true })) {
14918
14918
  if (e.isDirectory())
14919
- walk(join13(d, e.name));
14919
+ walk(join14(d, e.name));
14920
14920
  else
14921
14921
  n++;
14922
14922
  }
@@ -14930,8 +14930,8 @@ function countFiles(dir) {
14930
14930
  function syncData() {
14931
14931
  if (!dirExists(DATA_ROOT2))
14932
14932
  mkdirSync3(DATA_ROOT2, { recursive: true });
14933
- const payloadsPath = join13(DATA_ROOT2, "payloads");
14934
- const templatesPath = join13(DATA_ROOT2, "templates");
14933
+ const payloadsPath = join14(DATA_ROOT2, "payloads");
14934
+ const templatesPath = join14(DATA_ROOT2, "templates");
14935
14935
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
14936
14936
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
14937
14937
  if (hasPayloads && hasTemplates) {
@@ -14941,8 +14941,8 @@ function syncData() {
14941
14941
  data_root: DATA_ROOT2
14942
14942
  };
14943
14943
  }
14944
- const localPayloads = join13(ROOT7, "payloads");
14945
- const localTemplates = join13(ROOT7, "templates");
14944
+ const localPayloads = join14(ROOT7, "payloads");
14945
+ const localTemplates = join14(ROOT7, "templates");
14946
14946
  let payloadsSynced = false;
14947
14947
  let templatesSynced = false;
14948
14948
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -14955,23 +14955,23 @@ function syncData() {
14955
14955
  }
14956
14956
  const gitOk = spawnSync2("git", ["--version"]).status === 0;
14957
14957
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
14958
- const tmp = join13(DATA_ROOT2, ".tmp-clone");
14958
+ const tmp = join14(DATA_ROOT2, ".tmp-clone");
14959
14959
  if (dirExists(tmp))
14960
- rmSync3(tmp, { recursive: true, force: true });
14960
+ rmSync4(tmp, { recursive: true, force: true });
14961
14961
  const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
14962
14962
  if (r.status === 0) {
14963
14963
  const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
14964
14964
  if (sparse.status === 0) {
14965
14965
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
14966
- copyDir(join13(tmp, "payloads"), payloadsPath);
14966
+ copyDir(join14(tmp, "payloads"), payloadsPath);
14967
14967
  payloadsSynced = true;
14968
14968
  }
14969
14969
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
14970
- copyDir(join13(tmp, "templates"), templatesPath);
14970
+ copyDir(join14(tmp, "templates"), templatesPath);
14971
14971
  templatesSynced = true;
14972
14972
  }
14973
14973
  }
14974
- rmSync3(tmp, { recursive: true, force: true });
14974
+ rmSync4(tmp, { recursive: true, force: true });
14975
14975
  }
14976
14976
  }
14977
14977
  return {
@@ -14989,18 +14989,18 @@ function copyDir(src, dst) {
14989
14989
  function manualCopy(src, dst) {
14990
14990
  mkdirSync3(dst, { recursive: true });
14991
14991
  for (const e of readdirSync6(src, { withFileTypes: true })) {
14992
- const s = join13(src, e.name);
14993
- const d = join13(dst, e.name);
14992
+ const s = join14(src, e.name);
14993
+ const d = join14(dst, e.name);
14994
14994
  if (e.isDirectory())
14995
14995
  manualCopy(s, d);
14996
14996
  else
14997
- writeFileSync4(d, readFileSync17(s));
14997
+ writeFileSync5(d, readFileSync17(s));
14998
14998
  }
14999
14999
  }
15000
15000
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
15001
15001
  var init_sync = __esm(() => {
15002
- ROOT7 = join13(fileURLToPath8(new URL(".", import.meta.url)), "..");
15003
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join13(homedir4(), ".blitzstrike", "data");
15002
+ ROOT7 = join14(fileURLToPath8(new URL(".", import.meta.url)), "..");
15003
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join14(homedir4(), ".blitzstrike", "data");
15004
15004
  });
15005
15005
 
15006
15006
  // src/update.ts
@@ -31917,6 +31917,16 @@ function engagementStatus() {
31917
31917
  };
31918
31918
  }
31919
31919
 
31920
+ // src/incremental.ts
31921
+ import { execSync } from "node:child_process";
31922
+ import { writeFileSync as writeFileSync2, mkdtempSync, rmSync } from "node:fs";
31923
+ import { join as join9 } from "node:path";
31924
+ import { tmpdir } from "node:os";
31925
+
31926
+ // src/eagle2.ts
31927
+ var import_php_parser2 = __toESM(require_src(), 1);
31928
+ import { readFileSync as readFileSync11 } from "node:fs";
31929
+
31920
31930
  // src/dataflow.ts
31921
31931
  init_scanner();
31922
31932
  import { readFileSync as readFileSync9 } from "node:fs";
@@ -32080,8 +32090,10 @@ function groupVariants(results) {
32080
32090
  return [...groups.values()].sort((a, b) => b.occurrences - a.occurrences);
32081
32091
  }
32082
32092
 
32083
- // src/taint.ts
32093
+ // src/eagle2.ts
32084
32094
  init_scanner();
32095
+
32096
+ // src/callgraph.ts
32085
32097
  var import_php_parser = __toESM(require_src(), 1);
32086
32098
  import { readFileSync as readFileSync10 } from "node:fs";
32087
32099
  var parser = new import_php_parser.Engine({
@@ -32089,7 +32101,7 @@ var parser = new import_php_parser.Engine({
32089
32101
  ast: { withPositions: true, withSource: false }
32090
32102
  });
32091
32103
  function isNode(v) {
32092
- return v && typeof v === "object" && typeof v.kind === "string";
32104
+ return !!v && typeof v === "object" && typeof v.kind === "string";
32093
32105
  }
32094
32106
  function children(node) {
32095
32107
  const out = [];
@@ -32107,410 +32119,12 @@ function children(node) {
32107
32119
  }
32108
32120
  return out;
32109
32121
  }
32110
- function findKind(node, kind, out = []) {
32111
- if (!isNode(node))
32112
- return out;
32113
- if (node.kind === kind)
32114
- out.push(node);
32115
- for (const c of children(node))
32116
- findKind(c, kind, out);
32117
- return out;
32118
- }
32119
- function rootVarName(node) {
32120
- if (!isNode(node))
32121
- return null;
32122
- if (node.kind === "variable")
32123
- return node.name ?? null;
32124
- if (node.kind === "name")
32125
- return typeof node.name === "string" ? node.name : node.name?.name ?? null;
32126
- if (node.kind === "offsetlookup" || node.kind === "propertylookup")
32127
- return rootVarName(node.what);
32128
- return null;
32129
- }
32130
- function sourceOf(node) {
32131
- if (!isNode(node))
32132
- return null;
32133
- if (node.kind === "offsetlookup" && node.what?.kind === "variable") {
32134
- const n = node.what.name;
32135
- if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
32136
- return "$" + n;
32137
- }
32138
- if (node.kind === "variable") {
32139
- const n = node.name;
32140
- if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
32141
- return "$" + n;
32142
- }
32143
- if (node.kind === "call") {
32144
- const n = rootVarName(node.what);
32145
- if (n === "getallheaders")
32146
- return "getallheaders";
32147
- if (n === "file_get_contents" && JSON.stringify(node).includes("php://input"))
32148
- return "php://input";
32149
- }
32150
- return null;
32151
- }
32152
- function sinkClassOf(call) {
32153
- const what = call.what;
32154
- if (!isNode(what))
32155
- return null;
32156
- let token = "";
32157
- if (what.kind === "propertylookup") {
32158
- const base = rootVarName(what.what);
32159
- const method = what.offset?.name ?? what.offset?.value;
32160
- token = `${base}->${method}(`;
32161
- } else if (what.kind === "name") {
32162
- token = `${what.name}(`;
32163
- }
32164
- if (!token)
32165
- return null;
32166
- const cls = classifySink2(token) ?? classifySink2(what.name ?? "");
32167
- if (!cls)
32168
- return null;
32169
- return { token, cls };
32170
- }
32171
- function analyzeBlock(statements, env, summaries, findings, suppressed, params = [], file) {
32172
- for (const stmt of statements) {
32173
- if (!isNode(stmt))
32174
- continue;
32175
- const kind = stmt.kind;
32176
- if (kind === "expressionstatement") {
32177
- const expr = stmt.expression;
32178
- walkExpression(expr, env, summaries, findings, suppressed, file);
32179
- } else if (kind === "if") {
32180
- const ifEnv = new Map(env);
32181
- const bodyStmts = stmt.body?.kind === "block" ? stmt.body.children : [stmt.body];
32182
- analyzeBlock(bodyStmts, ifEnv, summaries, findings, suppressed, params, file);
32183
- if (stmt.alternate) {
32184
- const altEnv = new Map(env);
32185
- const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children : [stmt.alternate];
32186
- analyzeBlock(altStmts, altEnv, summaries, findings, suppressed, params, file);
32187
- }
32188
- } else if (kind === "block") {
32189
- analyzeBlock(stmt.children ?? [], env, summaries, findings, suppressed, params, file);
32190
- } else if (kind === "return" || kind === "echo" || kind === "print") {
32191
- walkExpression(stmt, env, summaries, findings, suppressed, file);
32192
- } else {
32193
- for (const c of children(stmt)) {
32194
- if (c.kind === "block" || c.kind === "if") {
32195
- analyzeBlock([c], new Map(env), summaries, findings, suppressed, params, file);
32196
- }
32197
- }
32198
- }
32199
- }
32200
- }
32201
- function walkExpression(node, env, summaries, findings, suppressed, file) {
32202
- if (!isNode(node))
32203
- return;
32204
- if (node.kind === "assign") {
32205
- const varName = rootVarName(node.left);
32206
- const taint = evalTaint(node.right, env, summaries);
32207
- if (varName) {
32208
- env.set(varName, taint);
32209
- }
32210
- return;
32211
- }
32212
- if (node.kind === "call") {
32213
- const sink = sinkClassOf(node);
32214
- if (sink) {
32215
- const argTaints = node.arguments.map((a) => {
32216
- return evalTaint(a, env, summaries);
32217
- });
32218
- const tainted = argTaints.filter((t) => t.tainted);
32219
- if (tainted.length > 0) {
32220
- const sanitizers = tainted.flatMap((t) => t.sanitizers);
32221
- const inlineSan = findSanitizers(JSON.stringify(node));
32222
- const san = [...sanitizers, ...inlineSan].filter((s, i, arr) => arr.indexOf(s) === i);
32223
- const cls = sink.cls;
32224
- if (!isSanitized(san, cls.id)) {
32225
- findings.push({
32226
- sink: sink.token,
32227
- sink_line: node.loc?.start?.line ?? 0,
32228
- category: cls.category,
32229
- cwe: cls.cwe,
32230
- variables: tainted.map((t) => t.source ?? "var"),
32231
- source: tainted[0].source,
32232
- source_line: tainted[0].sourceLine,
32233
- sanitized: false,
32234
- sanitizers: san.map((s) => s.id),
32235
- auth_gated: false,
32236
- interprocedural: false
32237
- });
32238
- } else {
32239
- suppressed.n += 1;
32240
- }
32241
- }
32242
- for (const a of node.arguments)
32243
- walkExpression(a, env, summaries, findings, suppressed, file);
32244
- return;
32245
- }
32246
- const fnName = rootVarName(node.what);
32247
- if (fnName && summaries.has(fnName)) {
32248
- const sum = summaries.get(fnName);
32249
- const argTaints = node.arguments.map((a) => {
32250
- const v = a;
32251
- return evalTaint(v, env, summaries);
32252
- });
32253
- const taintedArgs = argTaints.filter((t) => t.tainted);
32254
- if (taintedArgs.length > 0 && sum.sinkParams.size > 0) {
32255
- const firstSink = [...sum.sinkParams.values()][0];
32256
- const cls = classifySink2(firstSink.sink);
32257
- if (cls) {
32258
- const san = [
32259
- ...taintedArgs.flatMap((t) => t.sanitizers),
32260
- ...[...sum.sanitizedParams.values()].flat()
32261
- ];
32262
- if (!isSanitized(san, cls.id)) {
32263
- findings.push({
32264
- sink: firstSink.sink,
32265
- sink_line: firstSink.line,
32266
- category: cls.category,
32267
- cwe: cls.cwe,
32268
- variables: taintedArgs.map((t) => t.source ?? "var"),
32269
- source: taintedArgs[0].source,
32270
- source_line: taintedArgs[0].sourceLine,
32271
- sanitized: false,
32272
- sanitizers: san.map((s) => s.id),
32273
- auth_gated: false,
32274
- interprocedural: true
32275
- });
32276
- } else {
32277
- suppressed.n += 1;
32278
- }
32279
- }
32280
- }
32281
- return;
32282
- }
32283
- for (const c of children(node))
32284
- walkExpression(c, env, summaries, findings, suppressed, file);
32285
- return;
32286
- }
32287
- if (node.kind === "echo" || node.kind === "print") {
32288
- const exprs = node.expressions ?? [node.expression];
32289
- for (const e of exprs) {
32290
- const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
32291
- const t = evalTaint(e, env, summaries);
32292
- if (t.tainted && cls) {
32293
- const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(e))];
32294
- if (!isSanitized(san, cls.id)) {
32295
- findings.push({
32296
- sink: node.kind + " ",
32297
- sink_line: node.loc?.start?.line ?? 0,
32298
- category: cls.category,
32299
- cwe: cls.cwe,
32300
- variables: [t.source ?? "var"],
32301
- source: t.source,
32302
- source_line: t.sourceLine,
32303
- sanitized: false,
32304
- sanitizers: san.map((s) => s.id),
32305
- auth_gated: false,
32306
- interprocedural: false
32307
- });
32308
- } else {
32309
- suppressed.n += 1;
32310
- }
32311
- }
32312
- }
32313
- return;
32314
- }
32315
- for (const c of children(node))
32316
- walkExpression(c, env, summaries, findings, suppressed, file);
32317
- }
32318
- function evalTaint(node, env, summaries) {
32319
- if (!isNode(node))
32320
- return { tainted: false, sanitizers: [] };
32321
- const src = sourceOf(node);
32322
- if (src)
32323
- return { tainted: true, sanitizers: [], source: src, sourceLine: node.loc?.start?.line };
32324
- if (node.kind === "variable" || node.kind === "offsetlookup") {
32325
- const root = rootVarName(node);
32326
- if (root) {
32327
- const st = env.get(root);
32328
- if (st)
32329
- return { ...st };
32330
- }
32331
- if (node.kind === "offsetlookup") {
32332
- const whatTaint = evalTaint(node.what, env, summaries);
32333
- if (whatTaint.tainted)
32334
- return whatTaint;
32335
- }
32336
- return { tainted: false, sanitizers: [] };
32337
- }
32338
- if (node.kind === "call") {
32339
- const fnName = rootVarName(node.what);
32340
- const san = findSanitizers(fnName ? fnName + "(" : "");
32341
- if (san.length > 0) {
32342
- const inner = node.arguments.map((a) => evalTaint(a, env, summaries));
32343
- const tainted = inner.some((t) => t.tainted);
32344
- return {
32345
- tainted,
32346
- sanitizers: tainted ? [...inner.flatMap((t) => t.sanitizers), ...san] : [],
32347
- source: tainted ? inner.find((t) => t.tainted)?.source : undefined,
32348
- sourceLine: tainted ? inner.find((t) => t.tainted)?.sourceLine : undefined
32349
- };
32350
- }
32351
- if (fnName && summaries.has(fnName)) {
32352
- const sum = summaries.get(fnName);
32353
- const argTaints = node.arguments.map((a) => evalTaint(a, env, summaries));
32354
- const tainted = argTaints.filter((t) => t.tainted);
32355
- if (tainted.length > 0) {
32356
- const helperSan = [...sum.sanitizedParams.values()].flat();
32357
- return {
32358
- tainted: true,
32359
- sanitizers: [...tainted.flatMap((t) => t.sanitizers), ...helperSan],
32360
- source: tainted[0].source,
32361
- sourceLine: tainted[0].sourceLine
32362
- };
32363
- }
32364
- }
32365
- return { tainted: false, sanitizers: [] };
32366
- }
32367
- if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
32368
- const parts = children(node);
32369
- const taints = parts.map((p) => evalTaint(p, env, summaries));
32370
- const tainted = taints.find((t) => t.tainted);
32371
- if (tainted)
32372
- return { ...tainted, sanitizers: taints.flatMap((t) => t.sanitizers) };
32373
- return { tainted: false, sanitizers: [] };
32374
- }
32375
- if (node.kind === "encapsedpart") {
32376
- return evalTaint(node.expression, env, summaries);
32377
- }
32378
- return { tainted: false, sanitizers: [] };
32379
- }
32380
- function analyzeTaint(path) {
32381
- const result = { file: path, findings: [], suppressed: 0 };
32382
- let code;
32383
- try {
32384
- code = readFileSync10(path, "utf8");
32385
- } catch {
32386
- return result;
32387
- }
32388
- let ast;
32389
- try {
32390
- ast = parser.parseCode(code, path);
32391
- } catch {
32392
- return result;
32393
- }
32394
- const functions = findKind(ast, "function");
32395
- const summaries = new Map;
32396
- for (const fn of functions) {
32397
- const name = typeof fn.name === "string" ? fn.name : fn.name?.name ?? "<anonymous>";
32398
- const params = (fn.arguments ?? []).map((a) => typeof a === "string" ? a : typeof a.name === "string" ? a.name : a.name?.name ?? "?");
32399
- const summary = { name, params, sinkParams: new Map, sanitizedParams: new Map };
32400
- const env = new Map;
32401
- for (const p of params)
32402
- env.set(p, { tainted: false, sanitizers: [] });
32403
- const calls = findKind(fn, "call");
32404
- for (const call of calls) {
32405
- const sink = sinkClassOf(call);
32406
- if (!sink)
32407
- continue;
32408
- const line = call.loc?.start?.line ?? 0;
32409
- const varsInCall = findKind(call, "variable").map((v) => v.name).filter(Boolean);
32410
- for (const v of varsInCall) {
32411
- if (params.includes(v))
32412
- summary.sinkParams.set(v, { sink: sink.token, line });
32413
- }
32414
- const san = findSanitizers(JSON.stringify(call));
32415
- for (const v of varsInCall) {
32416
- if (params.includes(v) && san.length > 0) {
32417
- summary.sanitizedParams.set(v, [...summary.sanitizedParams.get(v) ?? [], ...san]);
32418
- }
32419
- }
32420
- }
32421
- for (const ek of ["echo", "print"]) {
32422
- for (const en of findKind(fn, ek)) {
32423
- const line = en.loc?.start?.line ?? 0;
32424
- const vars = findKind(en, "variable").map((v) => v.name).filter(Boolean);
32425
- const san = findSanitizers(JSON.stringify(en));
32426
- for (const v of vars) {
32427
- if (params.includes(v)) {
32428
- summary.sinkParams.set(v, { sink: ek + " ", line });
32429
- if (san.length > 0) {
32430
- summary.sanitizedParams.set(v, [...summary.sanitizedParams.get(v) ?? [], ...san]);
32431
- }
32432
- }
32433
- }
32434
- }
32435
- }
32436
- const assigns = findKind(fn, "assign");
32437
- for (const a of assigns) {
32438
- const san = findSanitizers(JSON.stringify(a.right));
32439
- if (san.length > 0) {
32440
- const lv = rootVarName(a.left);
32441
- if (lv && params.includes(lv)) {
32442
- summary.sanitizedParams.set(lv, [...summary.sanitizedParams.get(lv) ?? [], ...san]);
32443
- }
32444
- }
32445
- }
32446
- for (const ek of ["echo", "print"]) {
32447
- for (const en of findKind(fn, ek)) {
32448
- const san = findSanitizers(JSON.stringify(en));
32449
- if (san.length > 0) {
32450
- for (const v of findKind(en, "variable").map((v2) => v2.name).filter(Boolean)) {
32451
- if (params.includes(v)) {
32452
- summary.sanitizedParams.set(v, [...summary.sanitizedParams.get(v) ?? [], ...san]);
32453
- }
32454
- }
32455
- }
32456
- }
32457
- }
32458
- summaries.set(name, summary);
32459
- }
32460
- const topStmts = [];
32461
- const body = ast.children ?? [];
32462
- for (const s of body) {
32463
- if (isNode(s) && s.kind !== "function")
32464
- topStmts.push(s);
32465
- }
32466
- const globalEnv = new Map;
32467
- const suppressRef = { n: 0 };
32468
- analyzeBlock(topStmts, globalEnv, summaries, result.findings, suppressRef, [], path);
32469
- result.suppressed = suppressRef.n;
32470
- return result;
32471
- }
32472
- function taintTree(root, maxFiles = 2000) {
32473
- const files = iterSourceFiles(root, maxFiles);
32474
- return files.map((f) => analyzeTaint(f));
32475
- }
32476
-
32477
- // src/eagle2.ts
32478
- var import_php_parser3 = __toESM(require_src(), 1);
32479
- import { readFileSync as readFileSync12 } from "node:fs";
32480
- init_scanner();
32481
-
32482
- // src/callgraph.ts
32483
- var import_php_parser2 = __toESM(require_src(), 1);
32484
- import { readFileSync as readFileSync11 } from "node:fs";
32485
- var parser2 = new import_php_parser2.Engine({
32486
- parser: { extractDoc: false, php7: true },
32487
- ast: { withPositions: true, withSource: false }
32488
- });
32489
- function isNode2(v) {
32490
- return !!v && typeof v === "object" && typeof v.kind === "string";
32491
- }
32492
- function children2(node) {
32493
- const out = [];
32494
- for (const k of Object.keys(node)) {
32495
- if (k === "loc")
32496
- continue;
32497
- const v = node[k];
32498
- if (Array.isArray(v)) {
32499
- for (const c of v)
32500
- if (isNode2(c))
32501
- out.push(c);
32502
- } else if (isNode2(v)) {
32503
- out.push(v);
32504
- }
32505
- }
32506
- return out;
32507
- }
32508
32122
  function nameOf(node) {
32509
- if (!isNode2(node))
32123
+ if (!isNode(node))
32510
32124
  return null;
32511
32125
  if (typeof node.name === "string")
32512
32126
  return node.name;
32513
- if (isNode2(node.name))
32127
+ if (isNode(node.name))
32514
32128
  return nameOf(node.name);
32515
32129
  return null;
32516
32130
  }
@@ -32542,13 +32156,13 @@ function buildCallGraph(path) {
32542
32156
  const g = { nodes: new Map, edges: [] };
32543
32157
  let code;
32544
32158
  try {
32545
- code = readFileSync11(path, "utf8");
32159
+ code = readFileSync10(path, "utf8");
32546
32160
  } catch {
32547
32161
  return g;
32548
32162
  }
32549
32163
  let ast;
32550
32164
  try {
32551
- ast = parser2.parseCode(code, path);
32165
+ ast = parser.parseCode(code, path);
32552
32166
  } catch {
32553
32167
  return g;
32554
32168
  }
@@ -32585,7 +32199,7 @@ function buildCallGraph(path) {
32585
32199
  return g;
32586
32200
  }
32587
32201
  function walk(node, file, visit, ctx = {}) {
32588
- if (!isNode2(node))
32202
+ if (!isNode(node))
32589
32203
  return;
32590
32204
  let childCtx = ctx;
32591
32205
  if (node.kind === "class") {
@@ -32598,12 +32212,12 @@ function walk(node, file, visit, ctx = {}) {
32598
32212
  childCtx = { ...ctx, enclosing: name ? nodeId("method", name, ctx.className) : ctx.enclosing };
32599
32213
  }
32600
32214
  visit(node, ctx);
32601
- for (const c of children2(node))
32215
+ for (const c of children(node))
32602
32216
  walk(c, file, visit, childCtx);
32603
32217
  }
32604
32218
  function resolveCall(g, call, callerId, ctx, file, line) {
32605
32219
  const what = call.what;
32606
- if (!isNode2(what))
32220
+ if (!isNode(what))
32607
32221
  return;
32608
32222
  if (what.kind === "name") {
32609
32223
  const fn = nameOf(what);
@@ -32619,7 +32233,7 @@ function resolveCall(g, call, callerId, ctx, file, line) {
32619
32233
  }
32620
32234
  if (what.kind === "staticlookup") {
32621
32235
  const base = what.what;
32622
- const baseName = nameOf(base) ?? (isNode2(base) && base.kind === "variable" ? base.name : null);
32236
+ const baseName = nameOf(base) ?? (isNode(base) && base.kind === "variable" ? base.name : null);
32623
32237
  const method = nameOf(what.offset);
32624
32238
  if (!method)
32625
32239
  return;
@@ -32638,7 +32252,7 @@ function resolveCall(g, call, callerId, ctx, file, line) {
32638
32252
  const method = nameOf(what.offset);
32639
32253
  if (!method)
32640
32254
  return;
32641
- if (isNode2(base) && base.kind === "variable" && base.name === "this") {
32255
+ if (isNode(base) && base.kind === "variable" && base.name === "this") {
32642
32256
  if (ctx.className) {
32643
32257
  const id = nodeId("method", method, ctx.className);
32644
32258
  if (g.nodes.has(id))
@@ -32707,14 +32321,14 @@ function setCache(kind, source, value) {
32707
32321
  }
32708
32322
 
32709
32323
  // src/eagle2.ts
32710
- var parser3 = new import_php_parser3.Engine({
32324
+ var parser2 = new import_php_parser2.Engine({
32711
32325
  parser: { extractDoc: false, php7: true },
32712
32326
  ast: { withPositions: true, withSource: false }
32713
32327
  });
32714
- function isNode3(v) {
32328
+ function isNode2(v) {
32715
32329
  return !!v && typeof v === "object" && typeof v.kind === "string";
32716
32330
  }
32717
- function children3(node) {
32331
+ function children2(node) {
32718
32332
  const out = [];
32719
32333
  for (const k of Object.keys(node)) {
32720
32334
  if (k === "loc")
@@ -32722,45 +32336,45 @@ function children3(node) {
32722
32336
  const v = node[k];
32723
32337
  if (Array.isArray(v)) {
32724
32338
  for (const c of v)
32725
- if (isNode3(c))
32339
+ if (isNode2(c))
32726
32340
  out.push(c);
32727
- } else if (isNode3(v)) {
32341
+ } else if (isNode2(v)) {
32728
32342
  out.push(v);
32729
32343
  }
32730
32344
  }
32731
32345
  return out;
32732
32346
  }
32733
- function findKind2(node, kind, out = []) {
32734
- if (!isNode3(node))
32347
+ function findKind(node, kind, out = []) {
32348
+ if (!isNode2(node))
32735
32349
  return out;
32736
32350
  if (node.kind === kind)
32737
32351
  out.push(node);
32738
- for (const c of children3(node))
32739
- findKind2(c, kind, out);
32352
+ for (const c of children2(node))
32353
+ findKind(c, kind, out);
32740
32354
  return out;
32741
32355
  }
32742
32356
  function nameOf2(node) {
32743
- if (!isNode3(node))
32357
+ if (!isNode2(node))
32744
32358
  return null;
32745
32359
  if (typeof node.name === "string")
32746
32360
  return node.name;
32747
- if (isNode3(node.name))
32361
+ if (isNode2(node.name))
32748
32362
  return nameOf2(node.name);
32749
32363
  return null;
32750
32364
  }
32751
- function rootVarName2(node) {
32752
- if (!isNode3(node))
32365
+ function rootVarName(node) {
32366
+ if (!isNode2(node))
32753
32367
  return null;
32754
32368
  if (node.kind === "variable")
32755
32369
  return node.name ?? null;
32756
32370
  if (node.kind === "name")
32757
32371
  return typeof node.name === "string" ? node.name : node.name?.name ?? null;
32758
32372
  if (node.kind === "offsetlookup" || node.kind === "propertylookup")
32759
- return rootVarName2(node.what);
32373
+ return rootVarName(node.what);
32760
32374
  return null;
32761
32375
  }
32762
32376
  function calleeName(what) {
32763
- if (!isNode3(what))
32377
+ if (!isNode2(what))
32764
32378
  return null;
32765
32379
  if (what.kind === "name")
32766
32380
  return typeof what.name === "string" ? what.name : what.name?.name ?? null;
@@ -32795,8 +32409,8 @@ var TAINT_PROPAGATING_BUILTINS = new Set([
32795
32409
  "preg_quote",
32796
32410
  "nl2br"
32797
32411
  ]);
32798
- function sourceOf2(node) {
32799
- if (!isNode3(node))
32412
+ function sourceOf(node) {
32413
+ if (!isNode2(node))
32800
32414
  return null;
32801
32415
  if (node.kind === "offsetlookup" && node.what?.kind === "variable") {
32802
32416
  const n = node.what.name;
@@ -32809,14 +32423,14 @@ function sourceOf2(node) {
32809
32423
  return "$" + n;
32810
32424
  }
32811
32425
  if (node.kind === "call") {
32812
- const n = rootVarName2(node.what);
32426
+ const n = rootVarName(node.what);
32813
32427
  if (n === "getallheaders")
32814
32428
  return "getallheaders";
32815
32429
  if (n === "file_get_contents" && JSON.stringify(node).includes("php://input"))
32816
32430
  return "php://input";
32817
32431
  if (node.what?.kind === "propertylookup") {
32818
32432
  const method = node.what.offset?.name ?? node.what.offset?.value;
32819
- const base = rootVarName2(node.what.what);
32433
+ const base = rootVarName(node.what.what);
32820
32434
  const reqMethods = ["input", "query", "get", "post", "all", "json", "cookie", "file", "header", "only", "except", "server"];
32821
32435
  if (base && /^(request|req)$/i.test(base) && reqMethods.includes(String(method))) {
32822
32436
  return `$${base}->${method}()`;
@@ -32825,16 +32439,16 @@ function sourceOf2(node) {
32825
32439
  }
32826
32440
  return null;
32827
32441
  }
32828
- function sinkClassOf2(call) {
32442
+ function sinkClassOf(call) {
32829
32443
  const what = call.what;
32830
- if (!isNode3(what))
32444
+ if (!isNode2(what))
32831
32445
  return null;
32832
32446
  let token = "";
32833
32447
  if (what.kind === "propertylookup") {
32834
32448
  const baseNode = what.what;
32835
- const base = rootVarName2(baseNode);
32449
+ const base = rootVarName(baseNode);
32836
32450
  const method = what.offset?.name ?? what.offset?.value;
32837
- const baseStr = isNode3(baseNode) && (baseNode.kind === "variable" || baseNode.kind === "offsetlookup") ? `$${base}` : base;
32451
+ const baseStr = isNode2(baseNode) && (baseNode.kind === "variable" || baseNode.kind === "offsetlookup") ? `$${base}` : base;
32838
32452
  token = `${baseStr}->${method}(`;
32839
32453
  } else if (what.kind === "name") {
32840
32454
  token = `${what.name}(`;
@@ -32907,7 +32521,7 @@ function summarizeFunction(fn, id, name, summaries) {
32907
32521
  }
32908
32522
  function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
32909
32523
  for (const stmt of statements) {
32910
- if (!isNode3(stmt))
32524
+ if (!isNode2(stmt))
32911
32525
  continue;
32912
32526
  const kind = stmt.kind;
32913
32527
  if (kind === "expressionstatement") {
@@ -32944,7 +32558,7 @@ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeA
32944
32558
  } else if (kind === "echo" || kind === "print") {
32945
32559
  analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32946
32560
  } else {
32947
- for (const c of children3(stmt)) {
32561
+ for (const c of children2(stmt)) {
32948
32562
  if (c.kind === "block" || c.kind === "if") {
32949
32563
  analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32950
32564
  } else {
@@ -32955,9 +32569,9 @@ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeA
32955
32569
  }
32956
32570
  }
32957
32571
  function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
32958
- if (!isNode3(node))
32572
+ if (!isNode2(node))
32959
32573
  return CLEAN;
32960
- const src = sourceOf2(node);
32574
+ const src = sourceOf(node);
32961
32575
  if (src)
32962
32576
  return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
32963
32577
  if (node.kind === "cast") {
@@ -32972,10 +32586,10 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32972
32586
  const byref = node.operator === "=&";
32973
32587
  const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
32974
32588
  const lhs = node.left;
32975
- if (isNode3(lhs) && lhs.kind === "variable") {
32589
+ if (isNode2(lhs) && lhs.kind === "variable") {
32976
32590
  const varName = lhs.name;
32977
32591
  if (byref) {
32978
- const refVar = rootVarName2(node.right);
32592
+ const refVar = rootVarName(node.right);
32979
32593
  if (refVar) {
32980
32594
  const refTaint = env.get(refVar) ?? rhs;
32981
32595
  env.set(varName, refTaint);
@@ -32986,8 +32600,8 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
32986
32600
  } else {
32987
32601
  env.set(varName, rhs);
32988
32602
  }
32989
- } else if (isNode3(lhs) && lhs.kind === "propertylookup") {
32990
- const objName = rootVarName2(lhs.what);
32603
+ } else if (isNode2(lhs) && lhs.kind === "propertylookup") {
32604
+ const objName = rootVarName(lhs.what);
32991
32605
  const propName = lhs.offset?.name ?? "?";
32992
32606
  if (objName) {
32993
32607
  let oprops = props.get(objName);
@@ -33001,7 +32615,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
33001
32615
  return rhs;
33002
32616
  }
33003
32617
  if (node.kind === "call") {
33004
- const sink = sinkClassOf2(node);
32618
+ const sink = sinkClassOf(node);
33005
32619
  const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
33006
32620
  const taintedArgs = argTaints.filter((t) => t.tainted);
33007
32621
  if (sink) {
@@ -33028,7 +32642,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
33028
32642
  return CLEAN;
33029
32643
  }
33030
32644
  for (let i = 0;i < node.arguments.length; i++) {
33031
- const argVar = rootVarName2(node.arguments[i]);
32645
+ const argVar = rootVarName(node.arguments[i]);
33032
32646
  if (argVar === taintedParam) {
33033
32647
  ctx.calls.push({ callee: fnName, argIndex: i, line: node.loc?.start?.line ?? 0 });
33034
32648
  }
@@ -33050,7 +32664,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
33050
32664
  const paramName = helperSum.params[i];
33051
32665
  if (!paramName || !helperSum.paramWrites.has(paramName))
33052
32666
  continue;
33053
- const argVar = rootVarName2(node.arguments[i]);
32667
+ const argVar = rootVarName(node.arguments[i]);
33054
32668
  const written = helperSum.paramWrites.get(paramName);
33055
32669
  if (argVar) {
33056
32670
  env.set(argVar, { tainted: true, sources: written, sanitizers: [] });
@@ -33058,7 +32672,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
33058
32672
  }
33059
32673
  }
33060
32674
  }
33061
- if (isNode3(node.what) && node.what.kind === "propertylookup") {
32675
+ if (isNode2(node.what) && node.what.kind === "propertylookup") {
33062
32676
  analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
33063
32677
  }
33064
32678
  return CLEAN;
@@ -33068,7 +32682,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
33068
32682
  return t ? { ...t } : CLEAN;
33069
32683
  }
33070
32684
  if (node.kind === "offsetlookup") {
33071
- const root = rootVarName2(node);
32685
+ const root = rootVarName(node);
33072
32686
  if (root) {
33073
32687
  const t = env.get(root);
33074
32688
  if (t)
@@ -33077,7 +32691,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
33077
32691
  return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
33078
32692
  }
33079
32693
  if (node.kind === "propertylookup") {
33080
- const objName = rootVarName2(node.what);
32694
+ const objName = rootVarName(node.what);
33081
32695
  const propName = node.offset?.name ?? "?";
33082
32696
  if (objName) {
33083
32697
  const pt = props.get(objName)?.get(propName);
@@ -33092,7 +32706,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
33092
32706
  return mergeTaint(t, f);
33093
32707
  }
33094
32708
  if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
33095
- const parts = children3(node);
32709
+ const parts = children2(node);
33096
32710
  const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
33097
32711
  return mergeTaint(...taints);
33098
32712
  }
@@ -33151,13 +32765,13 @@ function mergeCtx(ctx, other) {
33151
32765
  ctx.calls.push(...other.calls);
33152
32766
  }
33153
32767
  function authGatesOf(node) {
33154
- if (!isNode3(node))
32768
+ if (!isNode2(node))
33155
32769
  return [];
33156
32770
  return AUTH_GATES.filter((g) => JSON.stringify(node).includes(g));
33157
32771
  }
33158
32772
  var VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
33159
32773
  function validationGatesOf(node) {
33160
- if (!isNode3(node))
32774
+ if (!isNode2(node))
33161
32775
  return false;
33162
32776
  const text = JSON.stringify(node);
33163
32777
  return VALIDATION_GATES.some((g) => text.includes(g));
@@ -33166,7 +32780,7 @@ function collectDecls(ast, out) {
33166
32780
  walkDecls(ast, out);
33167
32781
  }
33168
32782
  function walkDecls(node, out, className) {
33169
- if (!isNode3(node))
32783
+ if (!isNode2(node))
33170
32784
  return;
33171
32785
  if (node.kind === "function") {
33172
32786
  const name = nameOf2(node.name);
@@ -33174,7 +32788,7 @@ function walkDecls(node, out, className) {
33174
32788
  out.push({ id: `func:${name}`, name, node, kind: "function" });
33175
32789
  } else if (node.kind === "class") {
33176
32790
  const cn = nameOf2(node.name);
33177
- for (const c of children3(node)) {
32791
+ for (const c of children2(node)) {
33178
32792
  if (c.kind === "method") {
33179
32793
  const mn = nameOf2(c.name);
33180
32794
  if (mn && cn)
@@ -33188,7 +32802,7 @@ function walkDecls(node, out, className) {
33188
32802
  out.push({ id: `method:${className}::${mn}`, name: mn, node, kind: "method" });
33189
32803
  }
33190
32804
  const childCtx = node.kind === "class" ? nameOf2(node.name) ?? className : className;
33191
- for (const c of children3(node))
32805
+ for (const c of children2(node))
33192
32806
  walkDecls(c, out, childCtx);
33193
32807
  }
33194
32808
  function resolveFuncId(name, summaries) {
@@ -33203,7 +32817,7 @@ function resolveFuncId(name, summaries) {
33203
32817
  function analyzeDataFlow2(path) {
33204
32818
  let cacheCode = "";
33205
32819
  try {
33206
- cacheCode = readFileSync12(path, "utf8");
32820
+ cacheCode = readFileSync11(path, "utf8");
33207
32821
  } catch {}
33208
32822
  if (cacheCode) {
33209
32823
  const cached = getCache("eagle2", cacheCode);
@@ -33225,13 +32839,13 @@ function analyzeDataFlow2(path) {
33225
32839
  };
33226
32840
  let code;
33227
32841
  try {
33228
- code = cacheCode || readFileSync12(path, "utf8");
32842
+ code = cacheCode || readFileSync11(path, "utf8");
33229
32843
  } catch {
33230
32844
  return result;
33231
32845
  }
33232
32846
  let ast;
33233
32847
  try {
33234
- ast = parser3.parseCode(code, path);
32848
+ ast = parser2.parseCode(code, path);
33235
32849
  } catch {
33236
32850
  return result;
33237
32851
  }
@@ -33247,7 +32861,7 @@ function analyzeDataFlow2(path) {
33247
32861
  }
33248
32862
  const topStmts = [];
33249
32863
  for (const s of ast.children ?? []) {
33250
- if (isNode3(s) && s.kind !== "function" && s.kind !== "class")
32864
+ if (isNode2(s) && s.kind !== "function" && s.kind !== "class")
33251
32865
  topStmts.push(s);
33252
32866
  }
33253
32867
  const mainSummary = { id: "main", name: "main", params: [], byrefParams: new Set, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map, internalSinks: [] };
@@ -33277,10 +32891,10 @@ function analyzeDataFlow2(path) {
33277
32891
  }
33278
32892
  const queue = [];
33279
32893
  const seen = new Set;
33280
- const mainCalls = topStmts.flatMap((s) => findKind2(s, "call"));
32894
+ const mainCalls = topStmts.flatMap((s) => findKind(s, "call"));
33281
32895
  for (const call of mainCalls) {
33282
32896
  const fnName = calleeName(call.what);
33283
- if (!fnName || sinkClassOf2(call))
32897
+ if (!fnName || sinkClassOf(call))
33284
32898
  continue;
33285
32899
  const target = resolveFuncId(fnName, summaries);
33286
32900
  if (!target)
@@ -33367,12 +32981,6 @@ function analyzeDataFlow2(path) {
33367
32981
  return result;
33368
32982
  }
33369
32983
 
33370
- // src/differential.ts
33371
- import { writeFileSync as writeFileSync2, rmSync, mkdtempSync, readFileSync as readFileSync13 } from "node:fs";
33372
- import { join as join9 } from "node:path";
33373
- import { tmpdir } from "node:os";
33374
- import { execSync } from "node:child_process";
33375
-
33376
32984
  // src/universal-taint.ts
33377
32985
  function runTaintEngine(adapter, code, file) {
33378
32986
  const result = { file, language: adapter.language, findings: [], suppressed: 0 };
@@ -33474,7 +33082,7 @@ function analyzeTaintUniversal(code, file) {
33474
33082
  }
33475
33083
 
33476
33084
  // src/adapters.ts
33477
- var import_php_parser4 = __toESM(require_src(), 1);
33085
+ var import_php_parser3 = __toESM(require_src(), 1);
33478
33086
 
33479
33087
  // node_modules/@babel/parser/lib/index.js
33480
33088
  class Position {
@@ -51253,7 +50861,7 @@ var pythonHighlighting = styleTags({
51253
50861
  ", ;": tags.separator
51254
50862
  });
51255
50863
  var spec_identifier = { __proto__: null, await: 44, or: 54, and: 56, in: 60, not: 62, is: 64, if: 70, else: 72, lambda: 76, yield: 94, from: 96, async: 102, for: 104, None: 162, True: 164, False: 164, del: 178, pass: 182, break: 186, continue: 190, return: 194, raise: 202, import: 206, as: 208, global: 212, nonlocal: 214, assert: 218, type: 223, elif: 236, while: 240, try: 246, except: 248, finally: 250, with: 254, def: 258, class: 268, match: 279, case: 285 };
51256
- var parser4 = LRParser.deserialize({
50864
+ var parser3 = LRParser.deserialize({
51257
50865
  version: 14,
51258
50866
  states: "##jQ`QeOOP$}OSOOO&WQtO'#HUOOQS'#Co'#CoOOQS'#Cp'#CpO'vQdO'#CnO*UQtO'#HTOOQS'#HU'#HUOOQS'#DU'#DUOOQS'#HT'#HTO*rQdO'#D_O+VQdO'#DfO+gQdO'#DjO+zOWO'#DuO,VOWO'#DvO.[QtO'#GuOOQS'#Gu'#GuO'vQdO'#GtO0ZQtO'#GtOOQS'#Eb'#EbO0rQdO'#EcOOQS'#Gs'#GsO0|QdO'#GrOOQV'#Gr'#GrO1XQdO'#FYOOQS'#G^'#G^O1^QdO'#FXOOQV'#IS'#ISOOQV'#Gq'#GqOOQV'#Fq'#FqQ`QeOOO'vQdO'#CqO1lQdO'#C}O1sQdO'#DRO2RQdO'#HYO2cQtO'#EVO'vQdO'#EWOOQS'#EY'#EYOOQS'#E['#E[OOQS'#E^'#E^O2wQdO'#E`O3_QdO'#EdO3rQdO'#EfO3zQtO'#EfO1XQdO'#EiO0rQdO'#ElO1XQdO'#EnO0rQdO'#EtO0rQdO'#EwO4VQdO'#EyO4^QdO'#FOO4iQdO'#EzO0rQdO'#FOO1XQdO'#FQO1XQdO'#FVO4nQdO'#F[P4uOdO'#GpPOOO)CBd)CBdOOQS'#Ce'#CeOOQS'#Cf'#CfOOQS'#Cg'#CgOOQS'#Ch'#ChOOQS'#Ci'#CiOOQS'#Cj'#CjOOQS'#Cl'#ClO'vQdO,59OO'vQdO,59OO'vQdO,59OO'vQdO,59OO'vQdO,59OO'vQdO,59OO5TQdO'#DoOOQS,5:Y,5:YO5hQdO'#HdOOQS,5:],5:]O5uQ!fO,5:]O5zQtO,59YO1lQdO,59bO1lQdO,59bO1lQdO,59bO8jQdO,59bO8oQdO,59bO8vQdO,59jO8}QdO'#HTO:TQdO'#HSOOQS'#HS'#HSOOQS'#D['#D[O:lQdO,59aO'vQdO,59aO:zQdO,59aOOQS,59y,59yO;PQdO,5:RO'vQdO,5:ROOQS,5:Q,5:QO;_QdO,5:QO;dQdO,5:XO'vQdO,5:XO'vQdO,5:VOOQS,5:U,5:UO;uQdO,5:UO;zQdO,5:WOOOW'#Fy'#FyO<POWO,5:aOOQS,5:a,5:aO<[QdO'#HwOOOW'#Dw'#DwOOOW'#Fz'#FzO<lOWO,5:bOOQS,5:b,5:bOOQS'#F}'#F}O<zQtO,5:iO?lQtO,5=`O@VQ#xO,5=`O@vQtO,5=`OOQS,5:},5:}OA_QeO'#GWOBqQdO,5;^OOQV,5=^,5=^OB|QtO'#IPOCkQdO,5;tOOQS-E:[-E:[OOQV,5;s,5;sO4dQdO'#FQOOQV-E9o-E9oOCsQtO,59]OEzQtO,59iOFeQdO'#HVOFpQdO'#HVO1XQdO'#HVOF{QdO'#DTOGTQdO,59mOGYQdO'#HZO'vQdO'#HZO0rQdO,5=tOOQS,5=t,5=tO0rQdO'#EROOQS'#ES'#ESOGwQdO'#GPOHXQdO,58|OHXQdO,58|O*xQdO,5:oOHgQtO'#H]OOQS,5:r,5:rOOQS,5:z,5:zOHzQdO,5;OOI]QdO'#IOO1XQdO'#H}OOQS,5;Q,5;QOOQS'#GT'#GTOIqQtO,5;QOJPQdO,5;QOJUQdO'#IQOOQS,5;T,5;TOJdQdO'#H|OOQS,5;W,5;WOJuQdO,5;YO4iQdO,5;`O4iQdO,5;cOJ}QtO'#ITO'vQdO'#ITOKXQdO,5;eO4VQdO,5;eO0rQdO,5;jO1XQdO,5;lOK^QeO'#EuOLjQgO,5;fO!!kQdO'#IUO4iQdO,5;jO!!vQdO,5;lO!#OQdO,5;qO!#ZQtO,5;vO'vQdO,5;vPOOO,5=[,5=[P!#bOSO,5=[P!#jOdO,5=[O!&bQtO1G.jO!&iQtO1G.jO!)YQtO1G.jO!)dQtO1G.jO!+}QtO1G.jO!,bQtO1G.jO!,uQdO'#HcO!-TQtO'#GuO0rQdO'#HcO!-_QdO'#HbOOQS,5:Z,5:ZO!-gQdO,5:ZO!-lQdO'#HeO!-wQdO'#HeO!.[QdO,5>OOOQS'#Ds'#DsOOQS1G/w1G/wOOQS1G.|1G.|O!/[QtO1G.|O!/cQtO1G.|O1lQdO1G.|O!0OQdO1G/UOOQS'#DZ'#DZO0rQdO,59tOOQS1G.{1G.{O!0VQdO1G/eO!0gQdO1G/eO!0oQdO1G/fO'vQdO'#H[O!0tQdO'#H[O!0yQtO1G.{O!1ZQdO,59iO!2aQdO,5=zO!2qQdO,5=zO!2yQdO1G/mO!3OQtO1G/mOOQS1G/l1G/lO!3`QdO,5=uO!4VQdO,5=uO0rQdO1G/qO!4tQdO1G/sO!4yQtO1G/sO!5ZQtO1G/qOOQS1G/p1G/pOOQS1G/r1G/rOOOW-E9w-E9wOOQS1G/{1G/{O!5kQdO'#HxO0rQdO'#HxO!5|QdO,5>cOOOW-E9x-E9xOOQS1G/|1G/|OOQS-E9{-E9{O!6[Q#xO1G2zO!6{QtO1G2zO'vQdO,5<jOOQS,5<j,5<jOOQS-E9|-E9|OOQS,5<r,5<rOOQS-E:U-E:UOOQV1G0x1G0xO1XQdO'#GRO!7dQtO,5>kOOQS1G1`1G1`O!8RQdO1G1`OOQS'#DV'#DVO0rQdO,5=qOOQS,5=q,5=qO!8WQdO'#FrO!8cQdO,59oO!8kQdO1G/XO!8uQtO,5=uOOQS1G3`1G3`OOQS,5:m,5:mO!9fQdO'#GtOOQS,5<k,5<kOOQS-E9}-E9}O!9wQdO1G.hOOQS1G0Z1G0ZO!:VQdO,5=wO!:gQdO,5=wO0rQdO1G0jO0rQdO1G0jO!:xQdO,5>jO!;ZQdO,5>jO1XQdO,5>jO!;lQdO,5>iOOQS-E:R-E:RO!;qQdO1G0lO!;|QdO1G0lO!<RQdO,5>lO!<aQdO,5>lO!<oQdO,5>hO!=VQdO,5>hO!=hQdO'#EpO0rQdO1G0tO!=sQdO1G0tO!=xQgO1G0zO!AvQgO1G0}O!EqQdO,5>oO!E{QdO,5>oO!FTQtO,5>oO0rQdO1G1PO!F_QdO1G1PO4iQdO1G1UO!!vQdO1G1WOOQV,5;a,5;aO!FdQfO,5;aO!FiQgO1G1QO!JjQdO'#GZO4iQdO1G1QO4iQdO1G1QO!JzQdO,5>pO!KXQdO,5>pO1XQdO,5>pOOQV1G1U1G1UO!KaQdO'#FSO!KrQ!fO1G1WO!KzQdO1G1WOOQV1G1]1G1]O4iQdO1G1]O!LPQdO1G1]O!LXQdO'#F^OOQV1G1b1G1bO!#ZQtO1G1bPOOO1G2v1G2vP!L^OSO1G2vOOQS,5=},5=}OOQS'#Dp'#DpO0rQdO,5=}O!LfQdO,5=|O!LyQdO,5=|OOQS1G/u1G/uO!MRQdO,5>PO!McQdO,5>PO!MkQdO,5>PO!NOQdO,5>PO!N`QdO,5>POOQS1G3j1G3jOOQS7+$h7+$hO!8kQdO7+$pO#!RQdO1G.|O#!YQdO1G.|OOQS1G/`1G/`OOQS,5<`,5<`O'vQdO,5<`OOQS7+%P7+%PO#!aQdO7+%POOQS-E9r-E9rOOQS7+%Q7+%QO#!qQdO,5=vO'vQdO,5=vOOQS7+$g7+$gO#!vQdO7+%PO##OQdO7+%QO##TQdO1G3fOOQS7+%X7+%XO##eQdO1G3fO##mQdO7+%XOOQS,5<_,5<_O'vQdO,5<_O##rQdO1G3aOOQS-E9q-E9qO#$iQdO7+%]OOQS7+%_7+%_O#$wQdO1G3aO#%fQdO7+%_O#%kQdO1G3gO#%{QdO1G3gO#&TQdO7+%]O#&YQdO,5>dO#&sQdO,5>dO#&sQdO,5>dOOQS'#Dx'#DxO#'UO&jO'#DzO#'aO`O'#HyOOOW1G3}1G3}O#'fQdO1G3}O#'nQdO1G3}O#'yQ#xO7+(fO#(jQtO1G2UP#)TQdO'#GOOOQS,5<m,5<mOOQS-E:P-E:POOQS7+&z7+&zOOQS1G3]1G3]OOQS,5<^,5<^OOQS-E9p-E9pOOQS7+$s7+$sO#)bQdO,5=`O#){QdO,5=`O#*^QtO,5<aO#*qQdO1G3cOOQS-E9s-E9sOOQS7+&U7+&UO#+RQdO7+&UO#+aQdO,5<nO#+uQdO1G4UOOQS-E:Q-E:QO#,WQdO1G4UOOQS1G4T1G4TOOQS7+&W7+&WO#,iQdO7+&WOOQS,5<p,5<pO#,tQdO1G4WOOQS-E:S-E:SOOQS,5<l,5<lO#-SQdO1G4SOOQS-E:O-E:OO1XQdO'#EqO#-jQdO'#EqO#-uQdO'#IRO#-}QdO,5;[OOQS7+&`7+&`O0rQdO7+&`O#.SQgO7+&fO!JmQdO'#GXO4iQdO7+&fO4iQdO7+&iO#2QQtO,5<tO'vQdO,5<tO#2[QdO1G4ZOOQS-E:W-E:WO#2fQdO1G4ZO4iQdO7+&kO0rQdO7+&kOOQV7+&p7+&pO!KrQ!fO7+&rO!KzQdO7+&rO`QeO1G0{OOQV-E:X-E:XO4iQdO7+&lO4iQdO7+&lOOQV,5<u,5<uO#2nQdO,5<uO!JmQdO,5<uOOQV7+&l7+&lO#2yQgO7+&lO#6tQdO,5<vO#7PQdO1G4[OOQS-E:Y-E:YO#7^QdO1G4[O#7fQdO'#IWO#7tQdO'#IWO1XQdO'#IWOOQS'#IW'#IWO#8PQdO'#IVOOQS,5;n,5;nO#8XQdO,5;nO0rQdO'#FUOOQV7+&r7+&rO4iQdO7+&rOOQV7+&w7+&wO4iQdO7+&wO#8^QfO,5;xOOQV7+&|7+&|POOO7+(b7+(bO#8cQdO1G3iOOQS,5<c,5<cO#8qQdO1G3hOOQS-E9u-E9uO#9UQdO,5<dO#9aQdO,5<dO#9tQdO1G3kOOQS-E9v-E9vO#:UQdO1G3kO#:^QdO1G3kO#:nQdO1G3kO#:UQdO1G3kOOQS<<H[<<H[O#:yQtO1G1zOOQS<<Hk<<HkP#;WQdO'#FtO8vQdO1G3bO#;eQdO1G3bO#;jQdO<<HkOOQS<<Hl<<HlO#;zQdO7+)QOOQS<<Hs<<HsO#<[QtO1G1yP#<{QdO'#FsO#=YQdO7+)RO#=jQdO7+)RO#=rQdO<<HwO#=wQdO7+({OOQS<<Hy<<HyO#>nQdO,5<bO'vQdO,5<bOOQS-E9t-E9tOOQS<<Hw<<HwOOQS,5<g,5<gO0rQdO,5<gO#>sQdO1G4OOOQS-E9y-E9yO#?^QdO1G4OO<[QdO'#H{OOOO'#D{'#D{OOOO'#F|'#F|O#?oO&jO,5:fOOOW,5>e,5>eOOOW7+)i7+)iO#?zQdO7+)iO#@SQdO1G2zO#@mQdO1G2zP'vQdO'#FuO0rQdO<<IpO1XQdO1G2YP1XQdO'#GSO#AOQdO7+)pO#AaQdO7+)pOOQS<<Ir<<IrP1XQdO'#GUP0rQdO'#GQOOQS,5;],5;]O#ArQdO,5>mO#BQQdO,5>mOOQS1G0v1G0vOOQS<<Iz<<IzOOQV-E:V-E:VO4iQdO<<JQOOQV,5<s,5<sO4iQdO,5<sOOQV<<JQ<<JQOOQV<<JT<<JTO#BYQtO1G2`P#BdQdO'#GYO#BkQdO7+)uO#BuQgO<<JVO4iQdO<<JVOOQV<<J^<<J^O4iQdO<<J^O!KrQ!fO<<J^O#FpQgO7+&gOOQV<<JW<<JWO#FzQgO<<JWOOQV1G2a1G2aO1XQdO1G2aO#JuQdO1G2aO4iQdO<<JWO1XQdO1G2bP0rQdO'#G[O#KQQdO7+)vO#K_QdO7+)vOOQS'#FT'#FTO0rQdO,5>rO#KgQdO,5>rO#KrQdO,5>rO#K}QdO,5>qO#L`QdO,5>qOOQS1G1Y1G1YOOQS,5;p,5;pOOQV<<Jc<<JcO#LhQdO1G1dOOQS7+)T7+)TP#LmQdO'#FwO#L}QdO1G2OO#MbQdO1G2OO#MrQdO1G2OP#M}QdO'#FxO#N[QdO7+)VO#NlQdO7+)VO#NlQdO7+)VO#NtQdO7+)VO$ UQdO7+(|O8vQdO7+(|OOQSAN>VAN>VO$ oQdO<<LmOOQSAN>cAN>cO0rQdO1G1|O$!PQtO1G1|P$!ZQdO'#FvOOQS1G2R1G2RP$!hQdO'#F{O$!uQdO7+)jO$#`QdO,5>gOOOO-E9z-E9zOOOW<<MT<<MTO$#nQdO7+(fOOQSAN?[AN?[OOQS7+'t7+'tO$$XQdO<<M[OOQS,5<q,5<qO$$jQdO1G4XOOQS-E:T-E:TOOQVAN?lAN?lOOQV1G2_1G2_O4iQdOAN?qO$$xQgOAN?qOOQVAN?xAN?xO4iQdOAN?xOOQV<<JR<<JRO4iQdOAN?rO4iQdO7+'{OOQV7+'{7+'{O1XQdO7+'{OOQVAN?rAN?rOOQS7+'|7+'|O$(sQdO<<MbOOQS1G4^1G4^O0rQdO1G4^OOQS,5<w,5<wO$)QQdO1G4]OOQS-E:Z-E:ZOOQU'#G_'#G_O$)cQfO7+'OO$)nQdO'#F_O$*uQdO7+'jO$+VQdO7+'jOOQS7+'j7+'jO$+bQdO<<LqO$+rQdO<<LqO$+rQdO<<LqO$+zQdO'#H^OOQS<<Lh<<LhO$,UQdO<<LhOOQS7+'h7+'hOOQS'#D|'#D|OOOO1G4R1G4RO$,oQdO1G4RO$,wQdO1G4RP!=hQdO'#GVOOQVG25]G25]O4iQdOG25]OOQVG25dG25dOOQVG25^G25^OOQV<<Kg<<KgO4iQdO<<KgOOQS7+)x7+)xP$-SQdO'#G]OOQU-E:]-E:]OOQV<<Jj<<JjO$-vQtO'#FaOOQS'#Fc'#FcO$.WQdO'#FbO$.xQdO'#FbOOQS'#Fb'#FbO$.}QdO'#IYO$)nQdO'#FiO$)nQdO'#FiO$/fQdO'#FjO$)nQdO'#FkO$/mQdO'#IZOOQS'#IZ'#IZO$0[QdO,5;yOOQS<<KU<<KUO$0dQdO<<KUO$0tQdOANB]O$1UQdOANB]O$1^QdO'#H_OOQS'#H_'#H_O1sQdO'#DcO$1wQdO,5=xOOQSANBSANBSOOOO7+)m7+)mO$2`QdO7+)mOOQVLD*wLD*wOOQVANARANARO5uQ!fO'#GaO$2hQtO,5<SO$)nQdO'#FmOOQS,5<W,5<WOOQS'#Fd'#FdO$3YQdO,5;|O$3_QdO,5;|OOQS'#Fg'#FgO$)nQdO'#G`O$4PQdO,5<QO$4kQdO,5>tO$4{QdO,5>tO1XQdO,5<PO$5^QdO,5<TO$5cQdO,5<TO$)nQdO'#I[O$5hQdO'#I[O$5mQdO,5<UOOQS,5<V,5<VO0rQdO'#FpOOQU1G1e1G1eO4iQdO1G1eOOQSAN@pAN@pO$5rQdOG27wO$6SQdO,59}OOQS1G3d1G3dOOOO<<MX<<MXOOQS,5<{,5<{OOQS-E:_-E:_O$6XQtO'#FaO$6`QdO'#I]O$6nQdO'#I]O$6vQdO,5<XOOQS1G1h1G1hO$6{QdO1G1hO$7QQdO,5<zOOQS-E:^-E:^O$7lQdO,5=OO$8TQdO1G4`OOQS-E:b-E:bOOQS1G1k1G1kOOQS1G1o1G1oO$8eQdO,5>vO$)nQdO,5>vOOQS1G1p1G1pOOQS,5<[,5<[OOQU7+'P7+'PO$+zQdO1G/iO$)nQdO,5<YO$8sQdO,5>wO$8zQdO,5>wOOQS1G1s1G1sOOQS7+'S7+'SP$)nQdO'#GdO$9SQdO1G4bO$9^QdO1G4bO$9fQdO1G4bOOQS7+%T7+%TO$9tQdO1G1tO$:SQtO'#FaO$:ZQdO,5<}OOQS,5<},5<}O$:iQdO1G4cOOQS-E:a-E:aO$)nQdO,5<|O$:pQdO,5<|O$:uQdO7+)|OOQS-E:`-E:`O$;PQdO7+)|O$)nQdO,5<ZP$)nQdO'#GcO$;XQdO1G2hO$)nQdO1G2hP$;gQdO'#GbO$;nQdO<<MhO$;xQdO1G1uO$<WQdO7+(SO8vQdO'#C}O8vQdO,59bO8vQdO,59bO8vQdO,59bO$<fQtO,5=`O8vQdO1G.|O0rQdO1G/XO0rQdO7+$pP$<yQdO'#GOO'vQdO'#GtO$=WQdO,59bO$=]QdO,59bO$=dQdO,59mO$=iQdO1G/UO1sQdO'#DRO8vQdO,59j",
51259
50867
  stateData: "$>S~O%cOS%^OSSOS%]PQ~OPdOVaOfoOhYOopOs!POvqO!PrO!Q{O!T!SO!U!RO!XZO!][O!h`O!r`O!s`O!t`O!{tO!}uO#PvO#RwO#TxO#XyO#ZzO#^|O#_|O#a}O#c!OO#l!QO#o!TO#s!UO#u!VO#z!WO#}hO$P!XO%oRO%pRO%tSO%uWO&Z]O&[]O&]]O&^]O&_]O&`]O&a]O&b]O&c^O&d^O&e^O&f^O&g^O&h^O&i^O&j^O~O%]!YO~OV!aO_!aOa!bOh!iO!X!kO!f!mO%j![O%k!]O%l!^O%m!_O%n!_O%o!`O%p!`O%q!aO%r!aO%s!aO~Ok%xXl%xXm%xXn%xXo%xXp%xXs%xXz%xX{%xX!x%xX#g%xX%[%xX%_%xX%z%xXg%xX!T%xX!U%xX%{%xX!W%xX![%xX!Q%xX#[%xXt%xX!m%xX~P%SOfoOhYO!XZO!][O!h`O!r`O!s`O!t`O%oRO%pRO%tSO%uWO&Z]O&[]O&]]O&^]O&_]O&`]O&a]O&b]O&c^O&d^O&e^O&f^O&g^O&h^O&i^O&j^O~Oz%wX{%wX#g%wX%[%wX%_%wX%z%wX~Ok!pOl!qOm!oOn!oOo!rOp!sOs!tO!x%wX~P)pOV!zOg!|Oo0cOv0qO!PrO~P'vOV#OOo0cOv0qO!W#PO~P'vOV#SOa#TOo0cOv0qO![#UO~P'vOQ#XO%`#XO%a#ZO~OQ#^OR#[O%`#^O%a#`O~OV%iX_%iXa%iXh%iXk%iXl%iXm%iXn%iXo%iXp%iXs%iXz%iX!X%iX!f%iX%j%iX%k%iX%l%iX%m%iX%n%iX%o%iX%p%iX%q%iX%r%iX%s%iXg%iX!T%iX!U%iX~O&Z]O&[]O&]]O&^]O&_]O&`]O&a]O&b]O&c^O&d^O&e^O&f^O&g^O&h^O&i^O&j^O{%iX!x%iX#g%iX%[%iX%_%iX%z%iX%{%iX!W%iX![%iX!Q%iX#[%iXt%iX!m%iX~P,eOz#dO{%hX!x%hX#g%hX%[%hX%_%hX%z%hX~Oo0cOv0qO~P'vO#g#gO%[#iO%_#iO~O%uWO~O!T#nO#u!VO#z!WO#}hO~OopO~P'vOV#sOa#tO%uWO{wP~OV#xOo0cOv0qO!Q#yO~P'vO{#{O!x$QO%z#|O#g!yX%[!yX%_!yX~OV#xOo0cOv0qO#g#SX%[#SX%_#SX~P'vOo0cOv0qO#g#WX%[#WX%_#WX~P'vOh$WO%uWO~O!f$YO!r$YO%uWO~OV$eO~P'vO!U$gO#s$hO#u$iO~O{$jO~OV$qO~P'vOS$sO%[$rO%_$rO%c$tO~OV$}Oa$}Og%POo0cOv0qO~P'vOo0cOv0qO{%SO~P'vO&Y%UO~Oa!bOh!iO!X!kO!f!mOVba_bakbalbambanbaobapbasbazba{ba!xba#gba%[ba%_ba%jba%kba%lba%mba%nba%oba%pba%qba%rba%sba%zbagba!Tba!Uba%{ba!Wba![ba!Qba#[batba!mba~On%ZO~Oo%ZO~P'vOo0cO~P'vOk0eOl0fOm0dOn0dOo0mOp0nOs0rOg%wX!T%wX!U%wX%{%wX!W%wX![%wX!Q%wX#[%wX!m%wX~P)pO%{%]Og%vXz%vX!T%vX!U%vX!W%vX{%vX~Og%_Oz%`O!T%dO!U%cO~Og%_O~Oz%gO!T%dO!U%cO!W&SX~O!W%kO~Oz%lO{%nO!T%dO!U%cO![%}X~O![%rO~O![%sO~OQ#XO%`#XO%a%uO~OV%wOo0cOv0qO!PrO~P'vOQ#^OR#[O%`#^O%a%zO~OV!qa_!qaa!qah!qak!qal!qam!qan!qao!qap!qas!qaz!qa{!qa!X!qa!f!qa!x!qa#g!qa%[!qa%_!qa%j!qa%k!qa%l!qa%m!qa%n!qa%o!qa%p!qa%q!qa%r!qa%s!qa%z!qag!qa!T!qa!U!qa%{!qa!W!qa![!qa!Q!qa#[!qat!qa!m!qa~P#yOz%|O{%ha!x%ha#g%ha%[%ha%_%ha%z%ha~P%SOV&OOopOvqO{%ha!x%ha#g%ha%[%ha%_%ha%z%ha~P'vOz%|O{%ha!x%ha#g%ha%[%ha%_%ha%z%ha~OPdOVaOopOvqO!PrO!Q{O!{tO!}uO#PvO#RwO#TxO#XyO#ZzO#^|O#_|O#a}O#c!OO#g$zX%[$zX%_$zX~P'vO#g#gO%[&TO%_&TO~O!f&UOh&sX%[&sXz&sX#[&sX#g&sX%_&sX#Z&sXg&sX~Oh!iO%[&WO~Okealeameaneaoeapeaseazea{ea!xea#gea%[ea%_ea%zeagea!Tea!Uea%{ea!Wea![ea!Qea#[eatea!mea~P%SOsqazqa{qa#gqa%[qa%_qa%zqa~Ok!pOl!qOm!oOn!oOo!rOp!sO!xqa~PEcO%z&YOz%yX{%yX~O%uWOz%yX{%yX~Oz&]O{wX~O{&_O~Oz%lO#g%}X%[%}X%_%}Xg%}X{%}X![%}X!m%}X%z%}X~OV0lOo0cOv0qO!PrO~P'vO%z#|O#gUa%[Ua%_Ua~Oz&hO#g&PX%[&PX%_&PXn&PX~P%SOz&kO!Q&jO#g#Wa%[#Wa%_#Wa~Oz&lO#[&nO#g&rX%[&rX%_&rXg&rX~O!f$YO!r$YO#Z&qO%uWO~O#Z&qO~Oz&sO#g&tX%[&tX%_&tX~Oz&uO#g&pX%[&pX%_&pX{&pX~O!X&wO%z&xO~Oz&|On&wX~P%SOn'PO~OPdOVaOopOvqO!PrO!Q{O!{tO!}uO#PvO#RwO#TxO#XyO#ZzO#^|O#_|O#a}O#c!OO%['UO~P'vOt'YO#p'WO#q'XOP#naV#naf#nah#nao#nas#nav#na!P#na!Q#na!T#na!U#na!X#na!]#na!h#na!r#na!s#na!t#na!{#na!}#na#P#na#R#na#T#na#X#na#Z#na#^#na#_#na#a#na#c#na#l#na#o#na#s#na#u#na#z#na#}#na$P#na%X#na%o#na%p#na%t#na%u#na&Z#na&[#na&]#na&^#na&_#na&`#na&a#na&b#na&c#na&d#na&e#na&f#na&g#na&h#na&i#na&j#na%Z#na%_#na~Oz'ZO#[']O{&xX~Oh'_O!X&wO~Oh!iO{$jO!X&wO~O{'eO~P%SO%['hO%_'hO~OS'iO%['hO%_'hO~OV!aO_!aOa!bOh!iO!X!kO!f!mO%l!^O%m!_O%n!_O%o!`O%p!`O%q!aO%r!aO%s!aOkWilWimWinWioWipWisWizWi{Wi!xWi#gWi%[Wi%_Wi%jWi%zWigWi!TWi!UWi%{Wi!WWi![Wi!QWi#[WitWi!mWi~O%k!]O~P!#uO%kWi~P!#uOV!aO_!aOa!bOh!iO!X!kO!f!mO%o!`O%p!`O%q!aO%r!aO%s!aOkWilWimWinWioWipWisWizWi{Wi!xWi#gWi%[Wi%_Wi%jWi%kWi%lWi%zWigWi!TWi!UWi%{Wi!WWi![Wi!QWi#[WitWi!mWi~O%m!_O%n!_O~P!&pO%mWi%nWi~P!&pOa!bOh!iO!X!kO!f!mOkWilWimWinWioWipWisWizWi{Wi!xWi#gWi%[Wi%_Wi%jWi%kWi%lWi%mWi%nWi%oWi%pWi%zWigWi!TWi!UWi%{Wi!WWi![Wi!QWi#[WitWi!mWi~OV!aO_!aO%q!aO%r!aO%s!aO~P!)nOVWi_Wi%qWi%rWi%sWi~P!)nO!T%dO!U%cOg&VXz&VX~O%z'kO%{'kO~P,eOz'mOg&UX~Og'oO~Oz'pO{'rO!W&XX~Oo0cOv0qOz'pO{'sO!W&XX~P'vO!W'uO~Om!oOn!oOo!rOp!sOkjisjizji{ji!xji#gji%[ji%_ji%zji~Ol!qO~P!.aOlji~P!.aOk0eOl0fOm0dOn0dOo0mOp0nO~Ot'wO~P!/jOV'|Og'}Oo0cOv0qO~P'vOg'}Oz(OO~Og(QO~O!U(SO~Og(TOz(OO!T%dO!U%cO~P%SOk0eOl0fOm0dOn0dOo0mOp0nOgqa!Tqa!Uqa%{qa!Wqa![qa!Qqa#[qatqa!mqa~PEcOV'|Oo0cOv0qO!W&Sa~P'vOz(WO!W&Sa~O!W(XO~Oz(WO!T%dO!U%cO!W&Sa~P%SOV(]Oo0cOv0qO![%}a#g%}a%[%}a%_%}ag%}a{%}a!m%}a%z%}a~P'vOz(^O![%}a#g%}a%[%}a%_%}ag%}a{%}a!m%}a%z%}a~O![(aO~Oz(^O!T%dO!U%cO![%}a~P%SOz(dO!T%dO!U%cO![&Ta~P%SOz(gO{&lX![&lX!m&lX%z&lX~O{(kO![(mO!m(nO%z(jO~OV&OOopOvqO{%hi!x%hi#g%hi%[%hi%_%hi%z%hi~P'vOz(pO{%hi!x%hi#g%hi%[%hi%_%hi%z%hi~O!f&UOh&sa%[&saz&sa#[&sa#g&sa%_&sa#Z&sag&sa~O%[(uO~OV#sOa#tO%uWO~Oz&]O{wa~OopOvqO~P'vOz(^O#g%}a%[%}a%_%}ag%}a{%}a![%}a!m%}a%z%}a~P%SOz(zO#g%hX%[%hX%_%hX%z%hX~O%z#|O#gUi%[Ui%_Ui~O#g&Pa%[&Pa%_&Pan&Pa~P'vOz(}O#g&Pa%[&Pa%_&Pan&Pa~O%uWO#g&ra%[&ra%_&rag&ra~Oz)SO#g&ra%[&ra%_&rag&ra~Og)VO~OV)WOh$WO%uWO~O#Z)XO~O%uWO#g&ta%[&ta%_&ta~Oz)ZO#g&ta%[&ta%_&ta~Oo0cOv0qO#g&pa%[&pa%_&pa{&pa~P'vOz)^O#g&pa%[&pa%_&pa{&pa~OV)`Oa)`O%uWO~O%z)eO~Ot)hO#j)gOP#hiV#hif#hih#hio#his#hiv#hi!P#hi!Q#hi!T#hi!U#hi!X#hi!]#hi!h#hi!r#hi!s#hi!t#hi!{#hi!}#hi#P#hi#R#hi#T#hi#X#hi#Z#hi#^#hi#_#hi#a#hi#c#hi#l#hi#o#hi#s#hi#u#hi#z#hi#}#hi$P#hi%X#hi%o#hi%p#hi%t#hi%u#hi&Z#hi&[#hi&]#hi&^#hi&_#hi&`#hi&a#hi&b#hi&c#hi&d#hi&e#hi&f#hi&g#hi&h#hi&i#hi&j#hi%Z#hi%_#hi~Ot)iOP#kiV#kif#kih#kio#kis#kiv#ki!P#ki!Q#ki!T#ki!U#ki!X#ki!]#ki!h#ki!r#ki!s#ki!t#ki!{#ki!}#ki#P#ki#R#ki#T#ki#X#ki#Z#ki#^#ki#_#ki#a#ki#c#ki#l#ki#o#ki#s#ki#u#ki#z#ki#}#ki$P#ki%X#ki%o#ki%p#ki%t#ki%u#ki&Z#ki&[#ki&]#ki&^#ki&_#ki&`#ki&a#ki&b#ki&c#ki&d#ki&e#ki&f#ki&g#ki&h#ki&i#ki&j#ki%Z#ki%_#ki~OV)kOn&wa~P'vOz)lOn&wa~Oz)lOn&wa~P%SOn)pO~O%Y)tO~Ot)wO#p'WO#q)vOP#niV#nif#nih#nio#nis#niv#ni!P#ni!Q#ni!T#ni!U#ni!X#ni!]#ni!h#ni!r#ni!s#ni!t#ni!{#ni!}#ni#P#ni#R#ni#T#ni#X#ni#Z#ni#^#ni#_#ni#a#ni#c#ni#l#ni#o#ni#s#ni#u#ni#z#ni#}#ni$P#ni%X#ni%o#ni%p#ni%t#ni%u#ni&Z#ni&[#ni&]#ni&^#ni&_#ni&`#ni&a#ni&b#ni&c#ni&d#ni&e#ni&f#ni&g#ni&h#ni&i#ni&j#ni%Z#ni%_#ni~OV)zOo0cOv0qO{$jO~P'vOo0cOv0qO{&xa~P'vOz*OO{&xa~OV*SOa*TOg*WO%q*UO%uWO~O{$jO&{*YO~Oh'_O~Oh!iO{$jO~O%[*_O~O%[*aO%_*aO~OV$}Oa$}Oo0cOv0qOg&Ua~P'vOz*dOg&Ua~Oo0cOv0qO{*gO!W&Xa~P'vOz*hO!W&Xa~Oo0cOv0qOz*hO{*kO!W&Xa~P'vOo0cOv0qOz*hO!W&Xa~P'vOz*hO{*kO!W&Xa~Om0dOn0dOo0mOp0nOgjikjisjizji!Tji!Uji%{ji!Wji{ji![ji#gji%[ji%_ji!Qji#[jitji!mji%zji~Ol0fO~P!NkOlji~P!NkOV'|Og*pOo0cOv0qO~P'vOn*rO~Og*pOz*tO~Og*uO~OV'|Oo0cOv0qO!W&Si~P'vOz*vO!W&Si~O!W*wO~OV(]Oo0cOv0qO![%}i#g%}i%[%}i%_%}ig%}i{%}i!m%}i%z%}i~P'vOz*zO!T%dO!U%cO![&Ti~Oz*}O![%}i#g%}i%[%}i%_%}ig%}i{%}i!m%}i%z%}i~O![+OO~Oa+QOo0cOv0qO![&Ti~P'vOz*zO![&Ti~O![+SO~OV+UOo0cOv0qO{&la![&la!m&la%z&la~P'vOz+VO{&la![&la!m&la%z&la~O!]+YO&n+[O![!nX~O![+^O~O{(kO![+_O~O{(kO![+_O!m+`O~OV&OOopOvqO{%hq!x%hq#g%hq%[%hq%_%hq%z%hq~P'vOz$ri{$ri!x$ri#g$ri%[$ri%_$ri%z$ri~P%SOV&OOopOvqO~P'vOV&OOo0cOv0qO#g%ha%[%ha%_%ha%z%ha~P'vOz+aO#g%ha%[%ha%_%ha%z%ha~Oz$ia#g$ia%[$ia%_$ian$ia~P%SO#g&Pi%[&Pi%_&Pin&Pi~P'vOz+dO#g#Wq%[#Wq%_#Wq~O#[+eOz$va#g$va%[$va%_$vag$va~O%uWO#g&ri%[&ri%_&rig&ri~Oz+gO#g&ri%[&ri%_&rig&ri~OV+iOh$WO%uWO~O%uWO#g&ti%[&ti%_&ti~Oo0cOv0qO#g&pi%[&pi%_&pi{&pi~P'vO{#{Oz#eX!W#eX~Oz+mO!W&uX~O!W+oO~Ot+rO#j)gOP#hqV#hqf#hqh#hqo#hqs#hqv#hq!P#hq!Q#hq!T#hq!U#hq!X#hq!]#hq!h#hq!r#hq!s#hq!t#hq!{#hq!}#hq#P#hq#R#hq#T#hq#X#hq#Z#hq#^#hq#_#hq#a#hq#c#hq#l#hq#o#hq#s#hq#u#hq#z#hq#}#hq$P#hq%X#hq%o#hq%p#hq%t#hq%u#hq&Z#hq&[#hq&]#hq&^#hq&_#hq&`#hq&a#hq&b#hq&c#hq&d#hq&e#hq&f#hq&g#hq&h#hq&i#hq&j#hq%Z#hq%_#hq~On$|az$|a~P%SOV)kOn&wi~P'vOz+yOn&wi~Oz,TO{$jO#[,TO~O#q,VOP#nqV#nqf#nqh#nqo#nqs#nqv#nq!P#nq!Q#nq!T#nq!U#nq!X#nq!]#nq!h#nq!r#nq!s#nq!t#nq!{#nq!}#nq#P#nq#R#nq#T#nq#X#nq#Z#nq#^#nq#_#nq#a#nq#c#nq#l#nq#o#nq#s#nq#u#nq#z#nq#}#nq$P#nq%X#nq%o#nq%p#nq%t#nq%u#nq&Z#nq&[#nq&]#nq&^#nq&_#nq&`#nq&a#nq&b#nq&c#nq&d#nq&e#nq&f#nq&g#nq&h#nq&i#nq&j#nq%Z#nq%_#nq~O#[,WOz%Oa{%Oa~Oo0cOv0qO{&xi~P'vOz,YO{&xi~O{#{O%z,[Og&zXz&zX~O%uWOg&zXz&zX~Oz,`Og&yX~Og,bO~O%Y,eO~O!T%dO!U%cOg&Viz&Vi~OV$}Oa$}Oo0cOv0qOg&Ui~P'vO{,hOz$la!W$la~Oo0cOv0qO{,iOz$la!W$la~P'vOo0cOv0qO{*gO!W&Xi~P'vOz,lO!W&Xi~Oo0cOv0qOz,lO!W&Xi~P'vOz,lO{,oO!W&Xi~Og$hiz$hi!W$hi~P%SOV'|Oo0cOv0qO~P'vOn,qO~OV'|Og,rOo0cOv0qO~P'vOV'|Oo0cOv0qO!W&Sq~P'vOz$gi![$gi#g$gi%[$gi%_$gig$gi{$gi!m$gi%z$gi~P%SOV(]Oo0cOv0qO~P'vOa+QOo0cOv0qO![&Tq~P'vOz,sO![&Tq~O![,tO~OV(]Oo0cOv0qO![%}q#g%}q%[%}q%_%}qg%}q{%}q!m%}q%z%}q~P'vO{,uO~OV+UOo0cOv0qO{&li![&li!m&li%z&li~P'vOz,zO{&li![&li!m&li%z&li~O!]+YO&n+[O![!na~O{(kO![,}O~OV&OOo0cOv0qO#g%hi%[%hi%_%hi%z%hi~P'vOz-OO#g%hi%[%hi%_%hi%z%hi~O%uWO#g&rq%[&rq%_&rqg&rq~Oz-RO#g&rq%[&rq%_&rqg&rq~OV)`Oa)`O%uWO!W&ua~Oz-TO!W&ua~On$|iz$|i~P%SOV)kO~P'vOV)kOn&wq~P'vOt-XOP#myV#myf#myh#myo#mys#myv#my!P#my!Q#my!T#my!U#my!X#my!]#my!h#my!r#my!s#my!t#my!{#my!}#my#P#my#R#my#T#my#X#my#Z#my#^#my#_#my#a#my#c#my#l#my#o#my#s#my#u#my#z#my#}#my$P#my%X#my%o#my%p#my%t#my%u#my&Z#my&[#my&]#my&^#my&_#my&`#my&a#my&b#my&c#my&d#my&e#my&f#my&g#my&h#my&i#my&j#my%Z#my%_#my~O%Z-]O%_-]O~P`O#q-^OP#nyV#nyf#nyh#nyo#nys#nyv#ny!P#ny!Q#ny!T#ny!U#ny!X#ny!]#ny!h#ny!r#ny!s#ny!t#ny!{#ny!}#ny#P#ny#R#ny#T#ny#X#ny#Z#ny#^#ny#_#ny#a#ny#c#ny#l#ny#o#ny#s#ny#u#ny#z#ny#}#ny$P#ny%X#ny%o#ny%p#ny%t#ny%u#ny&Z#ny&[#ny&]#ny&^#ny&_#ny&`#ny&a#ny&b#ny&c#ny&d#ny&e#ny&f#ny&g#ny&h#ny&i#ny&j#ny%Z#ny%_#ny~Oz-aO{$jO#[-aO~Oo0cOv0qO{&xq~P'vOz-dO{&xq~O%z,[Og&zaz&za~O{#{Og&zaz&za~OV*SOa*TO%q*UO%uWOg&ya~Oz-hOg&ya~O$S-lO~OV$}Oa$}Oo0cOv0qO~P'vOo0cOv0qO{-mOz$li!W$li~P'vOo0cOv0qOz$li!W$li~P'vO{-mOz$li!W$li~Oo0cOv0qO{*gO~P'vOo0cOv0qO{*gO!W&Xq~P'vOz-pO!W&Xq~Oo0cOv0qOz-pO!W&Xq~P'vOs-sO!T%dO!U%cOg&Oq!W&Oq![&Oqz&Oq~P!/jOa+QOo0cOv0qO![&Ty~P'vOz$ji![$ji~P%SOa+QOo0cOv0qO~P'vOV+UOo0cOv0qO~P'vOV+UOo0cOv0qO{&lq![&lq!m&lq%z&lq~P'vO{(kO![-xO!m-yO%z-wO~OV&OOo0cOv0qO#g%hq%[%hq%_%hq%z%hq~P'vO%uWO#g&ry%[&ry%_&ryg&ry~OV)`Oa)`O%uWO!W&ui~Ot-}OP#m!RV#m!Rf#m!Rh#m!Ro#m!Rs#m!Rv#m!R!P#m!R!Q#m!R!T#m!R!U#m!R!X#m!R!]#m!R!h#m!R!r#m!R!s#m!R!t#m!R!{#m!R!}#m!R#P#m!R#R#m!R#T#m!R#X#m!R#Z#m!R#^#m!R#_#m!R#a#m!R#c#m!R#l#m!R#o#m!R#s#m!R#u#m!R#z#m!R#}#m!R$P#m!R%X#m!R%o#m!R%p#m!R%t#m!R%u#m!R&Z#m!R&[#m!R&]#m!R&^#m!R&_#m!R&`#m!R&a#m!R&b#m!R&c#m!R&d#m!R&e#m!R&f#m!R&g#m!R&h#m!R&i#m!R&j#m!R%Z#m!R%_#m!R~Oo0cOv0qO{&xy~P'vOV*SOa*TO%q*UO%uWOg&yi~O$S-lO%Z.VO%_.VO~OV.aOh._O!X.^O!].`O!h.YO!s.[O!t.[O%p.XO%uWO&Z]O&[]O&]]O&^]O&_]O&`]O&a]O&b]O~Oo0cOv0qOz$lq!W$lq~P'vO{.fOz$lq!W$lq~Oo0cOv0qO{*gO!W&Xy~P'vOz.gO!W&Xy~Oo0cOv.kO~P'vOs-sO!T%dO!U%cOg&Oy!W&Oy![&Oyz&Oy~P!/jO{(kO![.nO~O{(kO![.nO!m.oO~OV*SOa*TO%q*UO%uWO~Oh.tO!f.rOz$TX#[$TX%j$TXg$TX~Os$TX{$TX!W$TX![$TX~P$-bO%o.vO%p.vOs$UXz$UX{$UX#[$UX%j$UX!W$UXg$UX![$UX~O!h.xO~Oz.|O#[/OO%j.yOs&|X{&|X!W&|Xg&|X~Oa/RO~P$)zOh.tOs&}Xz&}X{&}X#[&}X%j&}X!W&}Xg&}X![&}X~Os/VO{$jO~Oo0cOv0qOz$ly!W$ly~P'vOo0cOv0qO{*gO!W&X!R~P'vOz/ZO!W&X!R~Og&RXs&RX!T&RX!U&RX!W&RX![&RXz&RX~P!/jOs-sO!T%dO!U%cOg&Qa!W&Qa![&Qaz&Qa~O{(kO![/^O~O!f.rOh$[as$[az$[a{$[a#[$[a%j$[a!W$[ag$[a![$[a~O!h/eO~O%o.vO%p.vOs$Uaz$Ua{$Ua#[$Ua%j$Ua!W$Uag$Ua![$Ua~O%j.yOs$Yaz$Ya{$Ya#[$Ya!W$Yag$Ya![$Ya~Os&|a{&|a!W&|ag&|a~P$)nOz/jOs&|a{&|a!W&|ag&|a~O!W/mO~Og/mO~O{/oO~O![/pO~Oo0cOv0qO{*gO!W&X!Z~P'vO{/sO~O%z/tO~P$-bOz/uO#[/OO%j.yOg'PX~Oz/uOg'PX~Og/wO~O!h/xO~O#[/OOs%Saz%Sa{%Sa%j%Sa!W%Sag%Sa![%Sa~O#[/OO%j.yOs%Waz%Wa{%Wa!W%Wag%Wa~Os&|i{&|i!W&|ig&|i~P$)nOz/zO#[/OO%j.yO!['Oa~Og'Pa~P$)nOz0SOg'Pa~Oa0UO!['Oi~P$)zOz0WO!['Oi~Oz0WO#[/OO%j.yO!['Oi~O#[/OO%j.yOg$biz$bi~O%z0ZO~P$-bO#[/OO%j.yOg%Vaz%Va~Og'Pi~P$)nO{0^O~Oa0UO!['Oq~P$)zOz0`O!['Oq~O#[/OO%j.yOz%Ui![%Ui~Oa0UO~P$)zOa0UO!['Oy~P$)zO#[/OO%j.yOg$ciz$ci~O#[/OO%j.yOz%Uq![%Uq~Oz+aO#g%ha%[%ha%_%ha%z%ha~P%SOV&OOo0cOv0qO~P'vOn0hO~Oo0hO~P'vO{0iO~Ot0jO~P!/jO&]&Z&j&h&i&g&f&d&e&c&b&`&a&_&^&[%u~",
@@ -51278,7 +50886,7 @@ var parser4 = LRParser.deserialize({
51278
50886
  });
51279
50887
 
51280
50888
  // src/adapters.ts
51281
- var phpEngine = new import_php_parser4.Engine({ parser: { extractDoc: false, php7: true }, ast: { withPositions: true, withSource: false } });
50889
+ var phpEngine = new import_php_parser3.Engine({ parser: { extractDoc: false, php7: true }, ast: { withPositions: true, withSource: false } });
51282
50890
  var PHP_SOURCES = [
51283
50891
  ["_GET", "http_parameter"],
51284
50892
  ["_POST", "http_body"],
@@ -51613,7 +51221,7 @@ var pythonAdapter = {
51613
51221
  language: "python",
51614
51222
  extensions: [".py", ".pyw"],
51615
51223
  parse(code, _file) {
51616
- const tree = parser4.parse(code);
51224
+ const tree = parser3.parse(code);
51617
51225
  const sources = [];
51618
51226
  const sinks = [];
51619
51227
  const sanitizers = [];
@@ -51807,18 +51415,454 @@ registerLanguage(jsAdapter);
51807
51415
  registerLanguage(pythonAdapter);
51808
51416
  registerLanguage(javaAdapter);
51809
51417
 
51418
+ // src/incremental.ts
51419
+ var SOURCE_EXTS = [".php", ".phtml", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
51420
+ function git(repo, args) {
51421
+ try {
51422
+ return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
51423
+ } catch {
51424
+ return "";
51425
+ }
51426
+ }
51427
+ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
51428
+ const changed = git(repo, `diff --name-only ${base} ${head}`).split(`
51429
+ `).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
51430
+ const findings = [];
51431
+ for (const file of changed) {
51432
+ const code = git(repo, `show ${head}:${file}`);
51433
+ if (!code)
51434
+ continue;
51435
+ const lang = detectLanguage(file)?.language;
51436
+ if (lang === "php") {
51437
+ const dir = mkdtempSync(join9(tmpdir(), "blitz-incr-"));
51438
+ const tmp = join9(dir, "changed.php");
51439
+ writeFileSync2(tmp, code);
51440
+ try {
51441
+ const r = analyzeDataFlow2(tmp);
51442
+ for (const f of r.findings)
51443
+ findings.push({ file, sink: f.sink, line: f.sink_line, category: f.category, cwe: f.cwe });
51444
+ } finally {
51445
+ rmSync(dir, { recursive: true, force: true });
51446
+ }
51447
+ } else if (lang) {
51448
+ const r = analyzeTaintUniversal(code, file);
51449
+ for (const f of r.findings)
51450
+ findings.push({ file, sink: f.sink, line: f.sink_line, category: f.sink, cwe: f.cwe });
51451
+ }
51452
+ }
51453
+ return { repo, base, head, changed_files: changed, files_scanned: changed.length, findings };
51454
+ }
51455
+
51456
+ // src/taint.ts
51457
+ init_scanner();
51458
+ var import_php_parser4 = __toESM(require_src(), 1);
51459
+ import { readFileSync as readFileSync12 } from "node:fs";
51460
+ var parser4 = new import_php_parser4.Engine({
51461
+ parser: { extractDoc: false, php7: true },
51462
+ ast: { withPositions: true, withSource: false }
51463
+ });
51464
+ function isNode3(v) {
51465
+ return v && typeof v === "object" && typeof v.kind === "string";
51466
+ }
51467
+ function children3(node) {
51468
+ const out = [];
51469
+ for (const k of Object.keys(node)) {
51470
+ if (k === "loc")
51471
+ continue;
51472
+ const v = node[k];
51473
+ if (Array.isArray(v)) {
51474
+ for (const c of v)
51475
+ if (isNode3(c))
51476
+ out.push(c);
51477
+ } else if (isNode3(v)) {
51478
+ out.push(v);
51479
+ }
51480
+ }
51481
+ return out;
51482
+ }
51483
+ function findKind2(node, kind, out = []) {
51484
+ if (!isNode3(node))
51485
+ return out;
51486
+ if (node.kind === kind)
51487
+ out.push(node);
51488
+ for (const c of children3(node))
51489
+ findKind2(c, kind, out);
51490
+ return out;
51491
+ }
51492
+ function rootVarName2(node) {
51493
+ if (!isNode3(node))
51494
+ return null;
51495
+ if (node.kind === "variable")
51496
+ return node.name ?? null;
51497
+ if (node.kind === "name")
51498
+ return typeof node.name === "string" ? node.name : node.name?.name ?? null;
51499
+ if (node.kind === "offsetlookup" || node.kind === "propertylookup")
51500
+ return rootVarName2(node.what);
51501
+ return null;
51502
+ }
51503
+ function sourceOf2(node) {
51504
+ if (!isNode3(node))
51505
+ return null;
51506
+ if (node.kind === "offsetlookup" && node.what?.kind === "variable") {
51507
+ const n = node.what.name;
51508
+ if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
51509
+ return "$" + n;
51510
+ }
51511
+ if (node.kind === "variable") {
51512
+ const n = node.name;
51513
+ if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
51514
+ return "$" + n;
51515
+ }
51516
+ if (node.kind === "call") {
51517
+ const n = rootVarName2(node.what);
51518
+ if (n === "getallheaders")
51519
+ return "getallheaders";
51520
+ if (n === "file_get_contents" && JSON.stringify(node).includes("php://input"))
51521
+ return "php://input";
51522
+ }
51523
+ return null;
51524
+ }
51525
+ function sinkClassOf2(call) {
51526
+ const what = call.what;
51527
+ if (!isNode3(what))
51528
+ return null;
51529
+ let token = "";
51530
+ if (what.kind === "propertylookup") {
51531
+ const base = rootVarName2(what.what);
51532
+ const method = what.offset?.name ?? what.offset?.value;
51533
+ token = `${base}->${method}(`;
51534
+ } else if (what.kind === "name") {
51535
+ token = `${what.name}(`;
51536
+ }
51537
+ if (!token)
51538
+ return null;
51539
+ const cls = classifySink2(token) ?? classifySink2(what.name ?? "");
51540
+ if (!cls)
51541
+ return null;
51542
+ return { token, cls };
51543
+ }
51544
+ function analyzeBlock(statements, env, summaries, findings, suppressed, params = [], file) {
51545
+ for (const stmt of statements) {
51546
+ if (!isNode3(stmt))
51547
+ continue;
51548
+ const kind = stmt.kind;
51549
+ if (kind === "expressionstatement") {
51550
+ const expr = stmt.expression;
51551
+ walkExpression(expr, env, summaries, findings, suppressed, file);
51552
+ } else if (kind === "if") {
51553
+ const ifEnv = new Map(env);
51554
+ const bodyStmts = stmt.body?.kind === "block" ? stmt.body.children : [stmt.body];
51555
+ analyzeBlock(bodyStmts, ifEnv, summaries, findings, suppressed, params, file);
51556
+ if (stmt.alternate) {
51557
+ const altEnv = new Map(env);
51558
+ const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children : [stmt.alternate];
51559
+ analyzeBlock(altStmts, altEnv, summaries, findings, suppressed, params, file);
51560
+ }
51561
+ } else if (kind === "block") {
51562
+ analyzeBlock(stmt.children ?? [], env, summaries, findings, suppressed, params, file);
51563
+ } else if (kind === "return" || kind === "echo" || kind === "print") {
51564
+ walkExpression(stmt, env, summaries, findings, suppressed, file);
51565
+ } else {
51566
+ for (const c of children3(stmt)) {
51567
+ if (c.kind === "block" || c.kind === "if") {
51568
+ analyzeBlock([c], new Map(env), summaries, findings, suppressed, params, file);
51569
+ }
51570
+ }
51571
+ }
51572
+ }
51573
+ }
51574
+ function walkExpression(node, env, summaries, findings, suppressed, file) {
51575
+ if (!isNode3(node))
51576
+ return;
51577
+ if (node.kind === "assign") {
51578
+ const varName = rootVarName2(node.left);
51579
+ const taint = evalTaint(node.right, env, summaries);
51580
+ if (varName) {
51581
+ env.set(varName, taint);
51582
+ }
51583
+ return;
51584
+ }
51585
+ if (node.kind === "call") {
51586
+ const sink = sinkClassOf2(node);
51587
+ if (sink) {
51588
+ const argTaints = node.arguments.map((a) => {
51589
+ return evalTaint(a, env, summaries);
51590
+ });
51591
+ const tainted = argTaints.filter((t) => t.tainted);
51592
+ if (tainted.length > 0) {
51593
+ const sanitizers = tainted.flatMap((t) => t.sanitizers);
51594
+ const inlineSan = findSanitizers(JSON.stringify(node));
51595
+ const san = [...sanitizers, ...inlineSan].filter((s, i, arr) => arr.indexOf(s) === i);
51596
+ const cls = sink.cls;
51597
+ if (!isSanitized(san, cls.id)) {
51598
+ findings.push({
51599
+ sink: sink.token,
51600
+ sink_line: node.loc?.start?.line ?? 0,
51601
+ category: cls.category,
51602
+ cwe: cls.cwe,
51603
+ variables: tainted.map((t) => t.source ?? "var"),
51604
+ source: tainted[0].source,
51605
+ source_line: tainted[0].sourceLine,
51606
+ sanitized: false,
51607
+ sanitizers: san.map((s) => s.id),
51608
+ auth_gated: false,
51609
+ interprocedural: false
51610
+ });
51611
+ } else {
51612
+ suppressed.n += 1;
51613
+ }
51614
+ }
51615
+ for (const a of node.arguments)
51616
+ walkExpression(a, env, summaries, findings, suppressed, file);
51617
+ return;
51618
+ }
51619
+ const fnName = rootVarName2(node.what);
51620
+ if (fnName && summaries.has(fnName)) {
51621
+ const sum = summaries.get(fnName);
51622
+ const argTaints = node.arguments.map((a) => {
51623
+ const v = a;
51624
+ return evalTaint(v, env, summaries);
51625
+ });
51626
+ const taintedArgs = argTaints.filter((t) => t.tainted);
51627
+ if (taintedArgs.length > 0 && sum.sinkParams.size > 0) {
51628
+ const firstSink = [...sum.sinkParams.values()][0];
51629
+ const cls = classifySink2(firstSink.sink);
51630
+ if (cls) {
51631
+ const san = [
51632
+ ...taintedArgs.flatMap((t) => t.sanitizers),
51633
+ ...[...sum.sanitizedParams.values()].flat()
51634
+ ];
51635
+ if (!isSanitized(san, cls.id)) {
51636
+ findings.push({
51637
+ sink: firstSink.sink,
51638
+ sink_line: firstSink.line,
51639
+ category: cls.category,
51640
+ cwe: cls.cwe,
51641
+ variables: taintedArgs.map((t) => t.source ?? "var"),
51642
+ source: taintedArgs[0].source,
51643
+ source_line: taintedArgs[0].sourceLine,
51644
+ sanitized: false,
51645
+ sanitizers: san.map((s) => s.id),
51646
+ auth_gated: false,
51647
+ interprocedural: true
51648
+ });
51649
+ } else {
51650
+ suppressed.n += 1;
51651
+ }
51652
+ }
51653
+ }
51654
+ return;
51655
+ }
51656
+ for (const c of children3(node))
51657
+ walkExpression(c, env, summaries, findings, suppressed, file);
51658
+ return;
51659
+ }
51660
+ if (node.kind === "echo" || node.kind === "print") {
51661
+ const exprs = node.expressions ?? [node.expression];
51662
+ for (const e of exprs) {
51663
+ const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
51664
+ const t = evalTaint(e, env, summaries);
51665
+ if (t.tainted && cls) {
51666
+ const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(e))];
51667
+ if (!isSanitized(san, cls.id)) {
51668
+ findings.push({
51669
+ sink: node.kind + " ",
51670
+ sink_line: node.loc?.start?.line ?? 0,
51671
+ category: cls.category,
51672
+ cwe: cls.cwe,
51673
+ variables: [t.source ?? "var"],
51674
+ source: t.source,
51675
+ source_line: t.sourceLine,
51676
+ sanitized: false,
51677
+ sanitizers: san.map((s) => s.id),
51678
+ auth_gated: false,
51679
+ interprocedural: false
51680
+ });
51681
+ } else {
51682
+ suppressed.n += 1;
51683
+ }
51684
+ }
51685
+ }
51686
+ return;
51687
+ }
51688
+ for (const c of children3(node))
51689
+ walkExpression(c, env, summaries, findings, suppressed, file);
51690
+ }
51691
+ function evalTaint(node, env, summaries) {
51692
+ if (!isNode3(node))
51693
+ return { tainted: false, sanitizers: [] };
51694
+ const src = sourceOf2(node);
51695
+ if (src)
51696
+ return { tainted: true, sanitizers: [], source: src, sourceLine: node.loc?.start?.line };
51697
+ if (node.kind === "variable" || node.kind === "offsetlookup") {
51698
+ const root = rootVarName2(node);
51699
+ if (root) {
51700
+ const st = env.get(root);
51701
+ if (st)
51702
+ return { ...st };
51703
+ }
51704
+ if (node.kind === "offsetlookup") {
51705
+ const whatTaint = evalTaint(node.what, env, summaries);
51706
+ if (whatTaint.tainted)
51707
+ return whatTaint;
51708
+ }
51709
+ return { tainted: false, sanitizers: [] };
51710
+ }
51711
+ if (node.kind === "call") {
51712
+ const fnName = rootVarName2(node.what);
51713
+ const san = findSanitizers(fnName ? fnName + "(" : "");
51714
+ if (san.length > 0) {
51715
+ const inner = node.arguments.map((a) => evalTaint(a, env, summaries));
51716
+ const tainted = inner.some((t) => t.tainted);
51717
+ return {
51718
+ tainted,
51719
+ sanitizers: tainted ? [...inner.flatMap((t) => t.sanitizers), ...san] : [],
51720
+ source: tainted ? inner.find((t) => t.tainted)?.source : undefined,
51721
+ sourceLine: tainted ? inner.find((t) => t.tainted)?.sourceLine : undefined
51722
+ };
51723
+ }
51724
+ if (fnName && summaries.has(fnName)) {
51725
+ const sum = summaries.get(fnName);
51726
+ const argTaints = node.arguments.map((a) => evalTaint(a, env, summaries));
51727
+ const tainted = argTaints.filter((t) => t.tainted);
51728
+ if (tainted.length > 0) {
51729
+ const helperSan = [...sum.sanitizedParams.values()].flat();
51730
+ return {
51731
+ tainted: true,
51732
+ sanitizers: [...tainted.flatMap((t) => t.sanitizers), ...helperSan],
51733
+ source: tainted[0].source,
51734
+ sourceLine: tainted[0].sourceLine
51735
+ };
51736
+ }
51737
+ }
51738
+ return { tainted: false, sanitizers: [] };
51739
+ }
51740
+ if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
51741
+ const parts = children3(node);
51742
+ const taints = parts.map((p) => evalTaint(p, env, summaries));
51743
+ const tainted = taints.find((t) => t.tainted);
51744
+ if (tainted)
51745
+ return { ...tainted, sanitizers: taints.flatMap((t) => t.sanitizers) };
51746
+ return { tainted: false, sanitizers: [] };
51747
+ }
51748
+ if (node.kind === "encapsedpart") {
51749
+ return evalTaint(node.expression, env, summaries);
51750
+ }
51751
+ return { tainted: false, sanitizers: [] };
51752
+ }
51753
+ function analyzeTaint(path) {
51754
+ const result = { file: path, findings: [], suppressed: 0 };
51755
+ let code;
51756
+ try {
51757
+ code = readFileSync12(path, "utf8");
51758
+ } catch {
51759
+ return result;
51760
+ }
51761
+ let ast;
51762
+ try {
51763
+ ast = parser4.parseCode(code, path);
51764
+ } catch {
51765
+ return result;
51766
+ }
51767
+ const functions = findKind2(ast, "function");
51768
+ const summaries = new Map;
51769
+ for (const fn of functions) {
51770
+ const name = typeof fn.name === "string" ? fn.name : fn.name?.name ?? "<anonymous>";
51771
+ const params = (fn.arguments ?? []).map((a) => typeof a === "string" ? a : typeof a.name === "string" ? a.name : a.name?.name ?? "?");
51772
+ const summary = { name, params, sinkParams: new Map, sanitizedParams: new Map };
51773
+ const env = new Map;
51774
+ for (const p of params)
51775
+ env.set(p, { tainted: false, sanitizers: [] });
51776
+ const calls = findKind2(fn, "call");
51777
+ for (const call of calls) {
51778
+ const sink = sinkClassOf2(call);
51779
+ if (!sink)
51780
+ continue;
51781
+ const line = call.loc?.start?.line ?? 0;
51782
+ const varsInCall = findKind2(call, "variable").map((v) => v.name).filter(Boolean);
51783
+ for (const v of varsInCall) {
51784
+ if (params.includes(v))
51785
+ summary.sinkParams.set(v, { sink: sink.token, line });
51786
+ }
51787
+ const san = findSanitizers(JSON.stringify(call));
51788
+ for (const v of varsInCall) {
51789
+ if (params.includes(v) && san.length > 0) {
51790
+ summary.sanitizedParams.set(v, [...summary.sanitizedParams.get(v) ?? [], ...san]);
51791
+ }
51792
+ }
51793
+ }
51794
+ for (const ek of ["echo", "print"]) {
51795
+ for (const en of findKind2(fn, ek)) {
51796
+ const line = en.loc?.start?.line ?? 0;
51797
+ const vars = findKind2(en, "variable").map((v) => v.name).filter(Boolean);
51798
+ const san = findSanitizers(JSON.stringify(en));
51799
+ for (const v of vars) {
51800
+ if (params.includes(v)) {
51801
+ summary.sinkParams.set(v, { sink: ek + " ", line });
51802
+ if (san.length > 0) {
51803
+ summary.sanitizedParams.set(v, [...summary.sanitizedParams.get(v) ?? [], ...san]);
51804
+ }
51805
+ }
51806
+ }
51807
+ }
51808
+ }
51809
+ const assigns = findKind2(fn, "assign");
51810
+ for (const a of assigns) {
51811
+ const san = findSanitizers(JSON.stringify(a.right));
51812
+ if (san.length > 0) {
51813
+ const lv = rootVarName2(a.left);
51814
+ if (lv && params.includes(lv)) {
51815
+ summary.sanitizedParams.set(lv, [...summary.sanitizedParams.get(lv) ?? [], ...san]);
51816
+ }
51817
+ }
51818
+ }
51819
+ for (const ek of ["echo", "print"]) {
51820
+ for (const en of findKind2(fn, ek)) {
51821
+ const san = findSanitizers(JSON.stringify(en));
51822
+ if (san.length > 0) {
51823
+ for (const v of findKind2(en, "variable").map((v2) => v2.name).filter(Boolean)) {
51824
+ if (params.includes(v)) {
51825
+ summary.sanitizedParams.set(v, [...summary.sanitizedParams.get(v) ?? [], ...san]);
51826
+ }
51827
+ }
51828
+ }
51829
+ }
51830
+ }
51831
+ summaries.set(name, summary);
51832
+ }
51833
+ const topStmts = [];
51834
+ const body = ast.children ?? [];
51835
+ for (const s of body) {
51836
+ if (isNode3(s) && s.kind !== "function")
51837
+ topStmts.push(s);
51838
+ }
51839
+ const globalEnv = new Map;
51840
+ const suppressRef = { n: 0 };
51841
+ analyzeBlock(topStmts, globalEnv, summaries, result.findings, suppressRef, [], path);
51842
+ result.suppressed = suppressRef.n;
51843
+ return result;
51844
+ }
51845
+ function taintTree(root, maxFiles = 2000) {
51846
+ const files = iterSourceFiles(root, maxFiles);
51847
+ return files.map((f) => analyzeTaint(f));
51848
+ }
51849
+
51810
51850
  // src/differential.ts
51851
+ import { writeFileSync as writeFileSync3, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync13 } from "node:fs";
51852
+ import { join as join10 } from "node:path";
51853
+ import { tmpdir as tmpdir2 } from "node:os";
51854
+ import { execSync as execSync2 } from "node:child_process";
51811
51855
  init_scanner();
51812
51856
  function runEngine(code, language, file) {
51813
51857
  if (language === "php") {
51814
- const dir = mkdtempSync(join9(tmpdir(), "blitz-diff-"));
51815
- const tmp = join9(dir, "old.php");
51816
- writeFileSync2(tmp, code);
51858
+ const dir = mkdtempSync2(join10(tmpdir2(), "blitz-diff-"));
51859
+ const tmp = join10(dir, "old.php");
51860
+ writeFileSync3(tmp, code);
51817
51861
  try {
51818
51862
  const r = analyzeDataFlow2(tmp);
51819
51863
  return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
51820
51864
  } finally {
51821
- rmSync(dir, { recursive: true, force: true });
51865
+ rmSync2(dir, { recursive: true, force: true });
51822
51866
  }
51823
51867
  }
51824
51868
  const extMap = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" };
@@ -51971,17 +52015,17 @@ function analyzeDifferentialFiles(oldPath, newPath, language) {
51971
52015
  const lang = language ?? languageOf(newPath);
51972
52016
  return analyzeDifferential(oldCode, newCode, lang, newPath);
51973
52017
  }
51974
- var SOURCE_EXTS = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
52018
+ var SOURCE_EXTS2 = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
51975
52019
  function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
51976
52020
  const git = (args) => {
51977
52021
  try {
51978
- return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
52022
+ return execSync2(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
51979
52023
  } catch {
51980
52024
  return "";
51981
52025
  }
51982
52026
  };
51983
52027
  const changed = git(`diff --name-only ${base} ${head}`).split(`
51984
- `).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
52028
+ `).filter((f) => f && SOURCE_EXTS2.some((e) => f.endsWith(e)));
51985
52029
  const files = [];
51986
52030
  for (const file of changed) {
51987
52031
  const oldCode = git(`show ${base}:${file}`);
@@ -51998,15 +52042,15 @@ function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
51998
52042
 
51999
52043
  // src/frameworks.ts
52000
52044
  import { readFileSync as readFileSync14, existsSync as existsSync6 } from "node:fs";
52001
- import { join as join10 } from "node:path";
52045
+ import { join as join11 } from "node:path";
52002
52046
  import { fileURLToPath as fileURLToPath6 } from "node:url";
52003
- var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
52004
- var INTEL2 = join10(ROOT4, "intelligence");
52047
+ var ROOT4 = join11(fileURLToPath6(new URL(".", import.meta.url)), "..");
52048
+ var INTEL2 = join11(ROOT4, "intelligence");
52005
52049
  var cache = null;
52006
52050
  function load() {
52007
52051
  if (cache)
52008
52052
  return cache;
52009
- const p = join10(INTEL2, "frameworks.json");
52053
+ const p = join11(INTEL2, "frameworks.json");
52010
52054
  if (!existsSync6(p))
52011
52055
  return null;
52012
52056
  try {
@@ -53191,12 +53235,12 @@ async function liveRecon(target, includeActive = false) {
53191
53235
  }
53192
53236
 
53193
53237
  // src/benchmark.ts
53194
- import { readFileSync as readFileSync15, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
53195
- import { join as join11 } from "node:path";
53238
+ import { readFileSync as readFileSync15, readdirSync as readdirSync4, writeFileSync as writeFileSync4, rmSync as rmSync3 } from "node:fs";
53239
+ import { join as join12 } from "node:path";
53196
53240
  import { fileURLToPath as fileURLToPath7 } from "node:url";
53197
- import { tmpdir as tmpdir2 } from "node:os";
53198
- var ROOT5 = join11(fileURLToPath7(new URL(".", import.meta.url)), "..");
53199
- var CORPUS = join11(ROOT5, "bench", "corpus");
53241
+ import { tmpdir as tmpdir3 } from "node:os";
53242
+ var ROOT5 = join12(fileURLToPath7(new URL(".", import.meta.url)), "..");
53243
+ var CORPUS = join12(ROOT5, "bench", "corpus");
53200
53244
  function parseCorpusFile(path) {
53201
53245
  let text;
53202
53246
  try {
@@ -53250,7 +53294,7 @@ function loadCorpus() {
53250
53294
  for (const f of readdirSync4(CORPUS)) {
53251
53295
  if (!f.endsWith(".md"))
53252
53296
  continue;
53253
- const e = parseCorpusFile(join11(CORPUS, f));
53297
+ const e = parseCorpusFile(join12(CORPUS, f));
53254
53298
  if (e)
53255
53299
  entries.push(e);
53256
53300
  }
@@ -53262,10 +53306,10 @@ function loadCorpus() {
53262
53306
  function detect(entry) {
53263
53307
  const lang = entry.language;
53264
53308
  if (lang === "php") {
53265
- const tmp = join11(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
53266
- writeFileSync3(tmp, entry.code);
53309
+ const tmp = join12(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
53310
+ writeFileSync4(tmp, entry.code);
53267
53311
  const r = analyzeDataFlow2(tmp);
53268
- rmSync2(tmp, { force: true });
53312
+ rmSync3(tmp, { force: true });
53269
53313
  if (r.findings.length > 0)
53270
53314
  return { detected: true, sinkKind: r.findings[0].sink };
53271
53315
  return { detected: false };
@@ -53306,15 +53350,15 @@ function runBenchmark() {
53306
53350
 
53307
53351
  // src/enterprise-benchmark.ts
53308
53352
  import { existsSync as existsSync7, readFileSync as readFileSync16, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
53309
- import { join as join12, resolve } from "node:path";
53353
+ import { join as join13, resolve } from "node:path";
53310
53354
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
53311
53355
  var ENTERPRISE_FIXTURES = [
53312
- { id: "php-laravel", dir: join12(ROOT6, "php-laravel"), expected_vulns: 4 },
53313
- { id: "js-express", dir: join12(ROOT6, "js-express"), expected_vulns: 2 },
53314
- { id: "py-django", dir: join12(ROOT6, "py-django"), expected_vulns: 2 },
53315
- { id: "java-spring", dir: join12(ROOT6, "java-spring"), expected_vulns: 2 },
53316
- { id: "wp-batch-route", dir: join12(ROOT6, "wp-batch-route"), expected_vulns: 1 },
53317
- { id: "php-complex", dir: join12(ROOT6, "php-complex"), expected_vulns: 4 }
53356
+ { id: "php-laravel", dir: join13(ROOT6, "php-laravel"), expected_vulns: 4 },
53357
+ { id: "js-express", dir: join13(ROOT6, "js-express"), expected_vulns: 2 },
53358
+ { id: "py-django", dir: join13(ROOT6, "py-django"), expected_vulns: 2 },
53359
+ { id: "java-spring", dir: join13(ROOT6, "java-spring"), expected_vulns: 2 },
53360
+ { id: "wp-batch-route", dir: join13(ROOT6, "wp-batch-route"), expected_vulns: 1 },
53361
+ { id: "php-complex", dir: join13(ROOT6, "php-complex"), expected_vulns: 4 }
53318
53362
  ];
53319
53363
  function sourceFiles(root) {
53320
53364
  const out = [];
@@ -53326,7 +53370,7 @@ function sourceFiles(root) {
53326
53370
  return;
53327
53371
  }
53328
53372
  for (const e of entries) {
53329
- const p = join12(dir, e);
53373
+ const p = join13(dir, e);
53330
53374
  let st;
53331
53375
  try {
53332
53376
  st = statSync3(p);
@@ -53533,7 +53577,7 @@ function createServer() {
53533
53577
  name: "blitzstrike",
53534
53578
  version: "1.0.0"
53535
53579
  }, {
53536
- instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "0. ROUTE BY TARGET. Classify the target automatically - no need to ask. A " + " URL - with or without http/https (example.com, 192.168.1.1, host:8080) - " + " is LIVE (scope-gated, then live_recon + active_scan + strike_verify). A " + " filesystem path (exists on disk, or a code extension) is SOURCE audit " + " (blitz_scan + eagle_eye2 + complex_scan/route_scan). If the scheme is " + " missing, prepend https://. run_engagement auto-detects both internally.\\n" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter gated alternative. Both re-check scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates). Beyond taint: route_scan (route confusion / dispatch abuse / batch forwarding) and complex_scan (deserialization->POP, type juggling, mass assignment) catch classes plain taint misses.\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL plus a BASELINE. The verdict is deterministic: marker " + " reflected AND control inert = confirmed; both reflected = false_positive; " + " marker not reflected = unconfirmed. Feed the verdict to strike_resolve to " + " advance the finding lifecycle (hypothesis->validating->confirmed). Use " + " read_tool_manual for the exploit tool's manual, payload_lookup for payloads, " + " detect_waf/tech_correlation for context.\\n" + "5. RECORD + REPORT. Create a canonical finding with finding_create, advance it " + " through the lifecycle with finding_transition, attach confidence_score, and " + " redact any secrets before persisting. Only report findings that survived " + " verification.\\n\\n" + "SUPPORTING LAYERS:\\n" + "- run_engagement: ONE call does the whole audit (no babysitting). Source path -> " + " full static pipeline + inline report (markdown/JSON/SARIF). URL target -> " + " autonomous live marker sweep + confirmed findings + report. Prefer this first.\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- read_playbook / list_playbooks: 17 engagement playbooks (web-app, api-security, " + " source-code-audit, active-directory, mobile, external-attack-surface, ...).\\n" + "- skill_lookup / read_skill / list_skills: 38 skills (6 bs-* agent doctrine + " + " technique playbooks: WP/PHP 0-day, AD, malware, mobile, cloud).\\n" + "- read_tool_manual / list_manuals: 317 tool manuals (sqlmap, nmap, commix, ...).\\n" + "- payload_lookup / read_payload: 66 payload collections (sqli, xss, ssti, xxe, lfi).\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE lookup + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "RESOURCE MAP (know what to reach for):\\n" + "- ORCHESTRATION = the engagement framework. orchestration() returns the " + " lifecycle (scope->recon->analyze->deep->verify->report, each with " + " entry/exit criteria) + sub-agent team + handoff contract + termination " + " criteria. Load it at engagement start.\\n" + "- TOOL = acts (scan, taint, verify, record, correlate). Call it to DO something.\\n" + "- PLAYBOOK = campaign plan for a target TYPE. read_playbook(web-application) " + " BEFORE a full engagement of that type.\\n" + "- SKILL = procedure doctrine. Load the bs-* skill for the phase you are on " + " (orchestrate, scope, source-audit, verify, review, report); load a technique " + " skill for the specific technique (WP 0-day, AD, malware).\\n" + "- MANUAL = deep reference for ONE tool. read_tool_manual right before using it.\\n" + "- PAYLOAD = attack payloads for a class. payload_lookup before crafting a probe.\\n" + "- INTELLIGENCE = knowledge. list_attack_vectors (34 categories) maps the " + " full surface during RECON; list_chains (57) turns a finding into impact; " + " chain_links composes chains for complex bugs; bypass_lookup when blocked; " + " payload_lookup when crafting a probe. Reach for them at that moment.\\n\\n" + "NO-BABYSITTING (while scanning + testing):\\n" + "- Scan the WHOLE surface before reporting - every file, entry point, and sink. " + " Do not stop after the first hit to ask; keep enumerating the full tree.\\n" + "- Test every hypothesis through its full verification cycle - vary probes on " + " failure, apply bypasses when blocked, keep going. Do not pause to ask.\\n" + "- Persist through partial results: a scan that surfaces few sinks is NOT done - " + " widen the surface, escalate detectors, dig into each reachable sink.\\n" + "- Only surface to the user when: scope/authorization is genuinely unclear, a " + " destructive or DoS action needs explicit consent, or the engagement is complete.\\n\\n" + "PERSISTENCE (complex bugs - do not give up):\\n" + "- On any failure (empty output, blocked probe, failed sub-agent/model), call " + " retry_guidance(signal) for concrete recovery actions before giving up.\\n" + "- One failed probe != not vulnerable. Vary: alternative sinks, other params, " + " encoding/case/whitespace bypasses, different injection context.\\n" + "- Empty output != no findings. Re-run (server may be stale after an upgrade - " + " /reload-mcp), or escalate to route_scan / complex_scan for classes taint " + " misses (POP chains, type juggling, mass assignment, route confusion).\\n" + "- A 'sanitized' sink may not be safe. Challenge the sanitizer: loose == vs ===, " + " missing EXTR_SKIP, int cast truncation, weak regex.\\n" + "- An unconfirmed hypothesis is KEPT as hypothesis - try alternate verification, " + " then let adversarial review decide.\\n" + "- Complex chains (deserialization->POP, LFI->RCE) need the FULL chain traced, " + ` not just the first sink.\\n
53580
+ instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "0. ROUTE BY TARGET. Classify the target automatically - no need to ask. A " + " URL - with or without http/https (example.com, 192.168.1.1, host:8080) - " + " is LIVE (scope-gated, then live_recon + active_scan + strike_verify). A " + " filesystem path (exists on disk, or a code extension) is SOURCE audit " + " (blitz_scan + eagle_eye2 + complex_scan/route_scan). If the scheme is " + " missing, prepend https://. run_engagement auto-detects both internally.\\n" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter gated alternative. Both re-check scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates). Beyond taint: route_scan (route confusion / dispatch abuse / batch forwarding) and complex_scan (deserialization->POP, type juggling, mass assignment) catch classes plain taint misses.\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL plus a BASELINE. The verdict is deterministic: marker " + " reflected AND control inert = confirmed; both reflected = false_positive; " + " marker not reflected = unconfirmed. Feed the verdict to strike_resolve to " + " advance the finding lifecycle (hypothesis->validating->confirmed). Use " + " read_tool_manual for the exploit tool's manual, payload_lookup for payloads, " + " detect_waf/tech_correlation for context.\\n" + "5. RECORD + REPORT. Create a canonical finding with finding_create, advance it " + " through the lifecycle with finding_transition, attach confidence_score, and " + " redact any secrets before persisting. Only report findings that survived " + " verification.\\n\\n" + "SUPPORTING LAYERS:\\n" + "- run_engagement: ONE call does the whole audit (no babysitting). Source path -> " + " full static pipeline + inline report (markdown/JSON/SARIF). URL target -> " + " autonomous live marker sweep + confirmed findings + report. Prefer this first.\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- read_playbook / list_playbooks: 17 engagement playbooks (web-app, api-security, " + " source-code-audit, active-directory, mobile, external-attack-surface, ...).\\n" + "- skill_lookup / read_skill / list_skills: 38 skills (6 bs-* agent doctrine + " + " technique playbooks: WP/PHP 0-day, AD, malware, mobile, cloud).\\n" + "- read_tool_manual / list_manuals: 317 tool manuals (sqlmap, nmap, commix, ...).\\n" + "- payload_lookup / read_payload: 66 payload collections (sqli, xss, ssti, xxe, lfi).\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE lookup + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "RESOURCE MAP (know what to reach for):\\n" + "- ORCHESTRATION = the engagement framework. orchestration() returns the " + " lifecycle (scope->recon->analyze->deep->verify->report, each with " + " entry/exit criteria) + sub-agent team + handoff contract + termination " + " criteria. Load it at engagement start.\\n" + "- TOOL = acts (scan, taint, verify, record, correlate). Call it to DO something.\\n" + "- PLAYBOOK = campaign plan for a target TYPE. read_playbook(web-application) " + " BEFORE a full engagement of that type.\\n" + "- SKILL = procedure doctrine. Load the bs-* skill for the phase you are on " + " (orchestrate, scope, source-audit, verify, review, report); load a technique " + " skill for the specific technique (WP 0-day, AD, malware).\\n" + "- MANUAL = deep reference for ONE tool. read_tool_manual right before using it.\\n" + "- CATALOG = external tools (130: sqlmap, nmap, nuclei, ffuf, ysoserial, ...). " + " Blitz Strike's OWN tools find + verify; the catalog is for deeper EXTERNAL " + " exploitation/enumeration. After your own analysis confirms a finding, run " + " tool_lookup -> ensure_tool (auto-install) -> read_tool_manual to drive the " + " external tool that demonstrates impact.\\n" + "- PAYLOAD = attack payloads for a class. payload_lookup before crafting a probe.\\n" + "- INTELLIGENCE = knowledge. list_attack_vectors (34 categories) maps the " + " full surface during RECON; list_chains (57) turns a finding into impact; " + " chain_links composes chains for complex bugs; bypass_lookup when blocked; " + " payload_lookup when crafting a probe. Reach for them at that moment.\\n\\n" + "NO-BABYSITTING (while scanning + testing):\\n" + "- Scan the WHOLE surface before reporting - every file, entry point, and sink. " + " Do not stop after the first hit to ask; keep enumerating the full tree.\\n" + "- Test every hypothesis through its full verification cycle - vary probes on " + " failure, apply bypasses when blocked, keep going. Do not pause to ask.\\n" + "- Persist through partial results: a scan that surfaces few sinks is NOT done - " + " widen the surface, escalate detectors, dig into each reachable sink.\\n" + "- Only surface to the user when: scope/authorization is genuinely unclear, a " + " destructive or DoS action needs explicit consent, or the engagement is complete.\\n\\n" + "PERSISTENCE (complex bugs - do not give up):\\n" + "- On any failure (empty output, blocked probe, failed sub-agent/model), call " + " retry_guidance(signal) for concrete recovery actions before giving up.\\n" + "- One failed probe != not vulnerable. Vary: alternative sinks, other params, " + " encoding/case/whitespace bypasses, different injection context.\\n" + "- Empty output != no findings. Re-run (server may be stale after an upgrade - " + " /reload-mcp), or escalate to route_scan / complex_scan for classes taint " + " misses (POP chains, type juggling, mass assignment, route confusion).\\n" + "- A 'sanitized' sink may not be safe. Challenge the sanitizer: loose == vs ===, " + " missing EXTR_SKIP, int cast truncation, weak regex.\\n" + "- An unconfirmed hypothesis is KEPT as hypothesis - try alternate verification, " + " then let adversarial review decide.\\n" + "- Complex chains (deserialization->POP, LFI->RCE) need the FULL chain traced, " + ` not just the first sink.\\n
53537
53581
  ` + "- When a probe is BLOCKED by a WAF/filter/sanitizer, call bypass_lookup(defense) " + " for concrete techniques (encoding, case, comment insertion, chunked, null byte, " + " double-encoding, verb tampering, header spoofing) and apply them - do not give up.\\n" + "- CHAIN findings: a low-severity finding may escalate — do not assume it is the " + " end of the story. list_chains / chain_links show whether it composes into a " + " multi-stage chain (e.g. SSRF->metadata, SQLi->RCE, LFI->RCE, upload->shell). " + " Check before concluding, and do not overclaim an escalation that is not there.\\n\\n" + "- Complex bugs chain ACROSS chains: after confirming one chain, chain_links " + " shows what follows (SSRF->metadata then default-creds->pivot via stolen IAM). " + " Trace the full composition, not just a single chain.\\n\\n" + "HYPOTHESIS EXPANSION (from each finding, enumerate + verify every follow-up):\\n" + "- A confirmed finding is the START, not the end. From each one, enumerate " + " the follow-ups: ESCALATE (list_chains + chain_links), CHAIN (chain_links " + " from->to), BYPASS (bypass_lookup), PIVOT (new endpoints/sinks now reachable).\\n" + "- Every follow-up (this might happen, this could chain, maybe bypassable) is " + " a HYPOTHESIS, not a finding. Verify EACH via strike_verify (marker + negative " + " control) before it enters the report.\\n" + "- Never report speculation: might / could / possibly live in the " + " hypothesis list, never in the report. Every report line is a verified fact.\\n\\n" + "PROFESSIONAL JUDGMENT (act like a senior tester, not a checklist runner):\\n" + "- PRIORITIZE by severity, computed deterministically with cvss_score (impact x " + " exploitability) and weighted by escalation potential (chain_links). Do NOT rank " + " by bug class - a critical stored XSS that yields account takeover outranks an " + " unreachable blind RCE. Weight by the engagement context (which asset/data is " + " most sensitive for THIS target).\\n" + "- Recognize dead ends: if a branch yields nothing after a few varied probes, " + " note it and move on. Do not tunnel-vision one sink at the expense of the rest.\\n" + "- Live-test with care: respect rate limits, do not lock accounts, do not " + " trigger DoS. Test non-destructively and prove impact with the smallest payload.\\n" + "- Know when done: stop when the termination criteria are met, not when you feel " + " tired. A thorough audit over a rushed one, every time.\\n\\n" + "IRON RULES:\\n" + "- Scope first; authorized targets only. Refuse out-of-scope or destructive/DoS work.\\n" + "- A hit is a hypothesis; a verified exploit (or proven data-flow) is the finding.\\n" + "- Prefer the one-call run_engagement path; fall back to granular tools when you " + " need finer control."
53538
53582
  });
53539
53583
  server.registerTool("blitz_scan", {
@@ -53899,7 +53943,7 @@ ${statusLine}` : statusLine },
53899
53943
  });
53900
53944
  server.registerTool("read_tool_manual", {
53901
53945
  title: "Read a tool manual",
53902
- description: "Read the full deep reference manual for a security tool (270+ manuals from kali-pentest, Apache-2.0).",
53946
+ description: "Read the full deep reference manual for a security tool (317 manuals from kali-pentest, Apache-2.0).",
53903
53947
  inputSchema: {
53904
53948
  name: string2().describe("Tool name (e.g. sqlmap, ffuf, nmap, ghidra)")
53905
53949
  }
@@ -54352,6 +54396,17 @@ ${statusLine}` : statusLine },
54352
54396
  }
54353
54397
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
54354
54398
  });
54399
+ server.registerTool("incremental_scan", {
54400
+ title: "Incremental scan (CI)",
54401
+ description: "EAGLE-EYE: analyze only the files changed between two git refs (default HEAD~1..HEAD) — the fast CI pass. Runs the taint + data-flow engine on just the changed source files and skips unchanged ones; the full-scan path (run_engagement / enrich_scan) remains authoritative.",
54402
+ inputSchema: {
54403
+ repo: string2().describe("Path to the git repo."),
54404
+ base: string2().optional().describe("Base ref (default HEAD~1)."),
54405
+ head: string2().optional().describe("Head ref (default HEAD).")
54406
+ }
54407
+ }, async ({ repo, base, head }) => {
54408
+ return { content: [{ type: "text", text: JSON.stringify(analyzeChangedFiles(repo, base ?? "HEAD~1", head ?? "HEAD")) }] };
54409
+ });
54355
54410
  server.registerTool("list_frameworks", {
54356
54411
  title: "List supported frameworks",
54357
54412
  description: "PHASE 5: list the web frameworks Blitz Strike understands for framework-aware security analysis (Laravel, WordPress, Express, Next.js, Django, FastAPI, Spring)."
@@ -54602,23 +54657,23 @@ async function serve() {
54602
54657
  }
54603
54658
 
54604
54659
  // src/cli.ts
54605
- import { readFileSync as readFileSync21, existsSync as existsSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
54606
- import { join as join16, dirname as dirname2 } from "node:path";
54660
+ import { readFileSync as readFileSync21, existsSync as existsSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "node:fs";
54661
+ import { join as join17, dirname as dirname2 } from "node:path";
54607
54662
  import { homedir as homedir5 } from "node:os";
54608
54663
  import { fileURLToPath as fileURLToPath11 } from "node:url";
54609
54664
  import { spawnSync as spawnSync3 } from "node:child_process";
54610
54665
 
54611
54666
  // src/release.ts
54612
54667
  import { readFileSync as readFileSync19, existsSync as existsSync8 } from "node:fs";
54613
- import { join as join14 } from "node:path";
54668
+ import { join as join15 } from "node:path";
54614
54669
  import { fileURLToPath as fileURLToPath9 } from "node:url";
54615
- import { execSync as execSync2 } from "node:child_process";
54616
- var ROOT8 = join14(fileURLToPath9(new URL(".", import.meta.url)), "..");
54670
+ import { execSync as execSync3 } from "node:child_process";
54671
+ var ROOT8 = join15(fileURLToPath9(new URL(".", import.meta.url)), "..");
54617
54672
  function validateRelease() {
54618
54673
  const checks = [];
54619
54674
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
54620
54675
  try {
54621
- const changelog = readFileSync19(join14(ROOT8, "CHANGELOG.md"), "utf8");
54676
+ const changelog = readFileSync19(join15(ROOT8, "CHANGELOG.md"), "utf8");
54622
54677
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
54623
54678
  const changelogVersion = m?.[1] ?? null;
54624
54679
  checks.push({
@@ -54630,13 +54685,13 @@ function validateRelease() {
54630
54685
  checks.push({ name: "CHANGELOG version", ok: false, detail: "CHANGELOG.md not found" });
54631
54686
  }
54632
54687
  try {
54633
- const tag = execSync2("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8" }).trim();
54688
+ const tag = execSync3("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8" }).trim();
54634
54689
  checks.push({ name: "Git tag", ok: tag === `v${VERSION}`, detail: tag, soft: true });
54635
54690
  } catch {
54636
54691
  checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git)", soft: true });
54637
54692
  }
54638
54693
  try {
54639
- const published = execSync2("npm view blitzstrike version", { encoding: "utf8", timeout: 8000 }).trim();
54694
+ const published = execSync3("npm view blitzstrike version", { encoding: "utf8", timeout: 8000 }).trim();
54640
54695
  checks.push({ name: "npm registry version", ok: published === VERSION, detail: published, soft: true });
54641
54696
  } catch {
54642
54697
  checks.push({ name: "npm registry version", ok: true, detail: "unavailable (offline)", soft: true });
@@ -54650,18 +54705,18 @@ function validateRelease() {
54650
54705
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
54651
54706
  }
54652
54707
  function lockfilePresent() {
54653
- return existsSync8(join14(ROOT8, "package-lock.json")) || existsSync8(join14(ROOT8, "bun.lock")) || existsSync8(join14(ROOT8, "bun.lockb"));
54708
+ return existsSync8(join15(ROOT8, "package-lock.json")) || existsSync8(join15(ROOT8, "bun.lock")) || existsSync8(join15(ROOT8, "bun.lockb"));
54654
54709
  }
54655
54710
 
54656
54711
  // src/dependency.ts
54657
54712
  import { readFileSync as readFileSync20 } from "node:fs";
54658
- import { join as join15 } from "node:path";
54713
+ import { join as join16 } from "node:path";
54659
54714
  import { fileURLToPath as fileURLToPath10 } from "node:url";
54660
- var ROOT9 = join15(fileURLToPath10(new URL(".", import.meta.url)), "..");
54715
+ var ROOT9 = join16(fileURLToPath10(new URL(".", import.meta.url)), "..");
54661
54716
  function checkDependencies() {
54662
54717
  let pkg = {};
54663
54718
  try {
54664
- pkg = JSON.parse(readFileSync20(join15(ROOT9, "package.json"), "utf8"));
54719
+ pkg = JSON.parse(readFileSync20(join16(ROOT9, "package.json"), "utf8"));
54665
54720
  } catch {
54666
54721
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
54667
54722
  }
@@ -54684,7 +54739,7 @@ function checkDependencies() {
54684
54739
  }
54685
54740
 
54686
54741
  // src/cli.ts
54687
- var ROOT10 = join16(fileURLToPath11(new URL(".", import.meta.url)), "..");
54742
+ var ROOT10 = join17(fileURLToPath11(new URL(".", import.meta.url)), "..");
54688
54743
  function which(cmd) {
54689
54744
  return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
54690
54745
  }
@@ -54719,7 +54774,7 @@ function runDoctor() {
54719
54774
  detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
54720
54775
  fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
54721
54776
  });
54722
- const dataOk = existsSync9(join16(ROOT10, "chains.json")) && existsSync9(join16(ROOT10, "tools-catalog.json"));
54777
+ const dataOk = existsSync9(join17(ROOT10, "chains.json")) && existsSync9(join17(ROOT10, "tools-catalog.json"));
54723
54778
  issues.push({
54724
54779
  name: "Data layers (chains + tools-catalog)",
54725
54780
  status: dataOk ? "ok" : "fail",
@@ -54761,14 +54816,14 @@ BlitzStrike — ${verdict}`);
54761
54816
  function resolveCommand() {
54762
54817
  if (which("blitzstrike"))
54763
54818
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
54764
- const src = join16(ROOT10, "src", "index.ts");
54819
+ const src = join17(ROOT10, "src", "index.ts");
54765
54820
  if (which("bun") && existsSync9(src))
54766
54821
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
54767
54822
  if (which("npx"))
54768
54823
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
54769
54824
  if (which("bunx"))
54770
54825
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
54771
- const dist = join16(ROOT10, "dist", "index.js");
54826
+ const dist = join17(ROOT10, "dist", "index.js");
54772
54827
  if (existsSync9(dist))
54773
54828
  return { command: "bun", args: [dist, "serve", "--mcp"] };
54774
54829
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
@@ -54790,7 +54845,7 @@ function jsonMcpServersWrite(p) {
54790
54845
  ...existing.mcpServers ?? {},
54791
54846
  blitzstrike: { command, args }
54792
54847
  };
54793
- writeFileSync5(p, JSON.stringify(existing, null, 2));
54848
+ writeFileSync6(p, JSON.stringify(existing, null, 2));
54794
54849
  };
54795
54850
  }
54796
54851
  function opencodeWrite(p) {
@@ -54803,22 +54858,22 @@ function opencodeWrite(p) {
54803
54858
  ...existing.mcp ?? {},
54804
54859
  blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
54805
54860
  };
54806
- writeFileSync5(p, JSON.stringify(existing, null, 2));
54861
+ writeFileSync6(p, JSON.stringify(existing, null, 2));
54807
54862
  copyOpenCodeAgents();
54808
54863
  };
54809
54864
  }
54810
54865
  function copyOpenCodeAgents() {
54811
- const srcDir = join16(ROOT10, "opencode-agents");
54812
- const dstDir = join16(homedir5(), ".config", "opencode", "agents");
54866
+ const srcDir = join17(ROOT10, "opencode-agents");
54867
+ const dstDir = join17(homedir5(), ".config", "opencode", "agents");
54813
54868
  if (!existsSync9(srcDir))
54814
54869
  return;
54815
54870
  if (!existsSync9(dstDir))
54816
54871
  mkdirSync4(dstDir, { recursive: true });
54817
54872
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
54818
- const src = join16(srcDir, f);
54819
- const dst = join16(dstDir, f);
54873
+ const src = join17(srcDir, f);
54874
+ const dst = join17(dstDir, f);
54820
54875
  if (existsSync9(src)) {
54821
- writeFileSync5(dst, readFileSync21(src, "utf8"));
54876
+ writeFileSync6(dst, readFileSync21(src, "utf8"));
54822
54877
  }
54823
54878
  }
54824
54879
  }
@@ -54838,7 +54893,7 @@ function codexWrite(p) {
54838
54893
  command = "${command}"
54839
54894
  args = ${JSON.stringify(args)}
54840
54895
  `;
54841
- writeFileSync5(p, existing);
54896
+ writeFileSync6(p, existing);
54842
54897
  };
54843
54898
  }
54844
54899
  function hermesWrite(p) {
@@ -54867,80 +54922,80 @@ mcp_servers:
54867
54922
  args: ${JSON.stringify(args)}
54868
54923
  `;
54869
54924
  }
54870
- writeFileSync5(p, existing);
54925
+ writeFileSync6(p, existing);
54871
54926
  };
54872
54927
  }
54873
54928
  function detectAgents() {
54874
54929
  const home = homedir5();
54875
54930
  const agents = [];
54876
- const claudeJson = join16(home, ".claude.json");
54931
+ const claudeJson = join17(home, ".claude.json");
54877
54932
  agents.push({
54878
54933
  name: "Claude Code",
54879
54934
  detectPath: claudeJson,
54880
54935
  installed: () => existsSync9(claudeJson) || which("claude"),
54881
54936
  write: jsonMcpServersWrite(claudeJson)
54882
54937
  });
54883
- const claudeDesktop = join16(home, ".config", "Claude", "claude_desktop_config.json");
54938
+ const claudeDesktop = join17(home, ".config", "Claude", "claude_desktop_config.json");
54884
54939
  agents.push({
54885
54940
  name: "Claude Desktop",
54886
54941
  detectPath: claudeDesktop,
54887
54942
  installed: () => existsSync9(claudeDesktop),
54888
54943
  write: jsonMcpServersWrite(claudeDesktop)
54889
54944
  });
54890
- const cursor = join16(home, ".cursor", "mcp.json");
54945
+ const cursor = join17(home, ".cursor", "mcp.json");
54891
54946
  agents.push({
54892
54947
  name: "Cursor",
54893
54948
  detectPath: cursor,
54894
- installed: () => existsSync9(join16(home, ".cursor")) || which("cursor"),
54949
+ installed: () => existsSync9(join17(home, ".cursor")) || which("cursor"),
54895
54950
  write: jsonMcpServersWrite(cursor)
54896
54951
  });
54897
- const opencode = join16(home, ".config", "opencode", "opencode.jsonc");
54952
+ const opencode = join17(home, ".config", "opencode", "opencode.jsonc");
54898
54953
  agents.push({
54899
54954
  name: "OpenCode",
54900
54955
  detectPath: opencode,
54901
- installed: () => existsSync9(join16(home, ".config", "opencode")) || which("opencode"),
54956
+ installed: () => existsSync9(join17(home, ".config", "opencode")) || which("opencode"),
54902
54957
  write: opencodeWrite(opencode)
54903
54958
  });
54904
- const codex = join16(home, ".codex", "config.toml");
54959
+ const codex = join17(home, ".codex", "config.toml");
54905
54960
  agents.push({
54906
54961
  name: "Codex",
54907
54962
  detectPath: codex,
54908
- installed: () => existsSync9(join16(home, ".codex")) || which("codex"),
54963
+ installed: () => existsSync9(join17(home, ".codex")) || which("codex"),
54909
54964
  write: codexWrite(codex)
54910
54965
  });
54911
- const hermes = join16(home, ".hermes", "config.yaml");
54966
+ const hermes = join17(home, ".hermes", "config.yaml");
54912
54967
  agents.push({
54913
54968
  name: "Hermes",
54914
54969
  detectPath: hermes,
54915
- installed: () => existsSync9(join16(home, ".hermes")) || which("hermes"),
54970
+ installed: () => existsSync9(join17(home, ".hermes")) || which("hermes"),
54916
54971
  write: hermesWrite(hermes)
54917
54972
  });
54918
- const gemini = join16(home, ".gemini", "settings.json");
54973
+ const gemini = join17(home, ".gemini", "settings.json");
54919
54974
  agents.push({
54920
54975
  name: "Gemini CLI",
54921
54976
  detectPath: gemini,
54922
- installed: () => existsSync9(join16(home, ".gemini")) || which("gemini"),
54977
+ installed: () => existsSync9(join17(home, ".gemini")) || which("gemini"),
54923
54978
  write: jsonMcpServersWrite(gemini)
54924
54979
  });
54925
- const windsurf = join16(home, ".codeium", "windsurf", "mcp_config.json");
54980
+ const windsurf = join17(home, ".codeium", "windsurf", "mcp_config.json");
54926
54981
  agents.push({
54927
54982
  name: "Windsurf",
54928
54983
  detectPath: windsurf,
54929
- installed: () => existsSync9(join16(home, ".codeium")) || which("windsurf"),
54984
+ installed: () => existsSync9(join17(home, ".codeium")) || which("windsurf"),
54930
54985
  write: jsonMcpServersWrite(windsurf)
54931
54986
  });
54932
- const copilot = join16(home, ".copilot", "mcp.json");
54987
+ const copilot = join17(home, ".copilot", "mcp.json");
54933
54988
  agents.push({
54934
54989
  name: "Copilot",
54935
54990
  detectPath: copilot,
54936
- installed: () => existsSync9(join16(home, ".copilot")),
54991
+ installed: () => existsSync9(join17(home, ".copilot")),
54937
54992
  write: jsonMcpServersWrite(copilot)
54938
54993
  });
54939
- const cline = join16(home, ".cline", "mcp_settings.json");
54994
+ const cline = join17(home, ".cline", "mcp_settings.json");
54940
54995
  agents.push({
54941
54996
  name: "Cline",
54942
54997
  detectPath: cline,
54943
- installed: () => existsSync9(join16(home, ".cline")),
54998
+ installed: () => existsSync9(join17(home, ".cline")),
54944
54999
  write: jsonMcpServersWrite(cline)
54945
55000
  });
54946
55001
  return agents;
@@ -54985,12 +55040,12 @@ Registered with ${ok}/${installed.length} agent(s).`);
54985
55040
 
54986
55041
  // src/index.ts
54987
55042
  import { readFileSync as readFileSync22, existsSync as existsSync10 } from "node:fs";
54988
- import { join as join17 } from "node:path";
55043
+ import { join as join18 } from "node:path";
54989
55044
  import { fileURLToPath as fileURLToPath12 } from "node:url";
54990
- var ROOT11 = join17(fileURLToPath12(new URL(".", import.meta.url)), "..");
55045
+ var ROOT11 = join18(fileURLToPath12(new URL(".", import.meta.url)), "..");
54991
55046
  function readVersion() {
54992
55047
  try {
54993
- const p = join17(ROOT11, "package.json");
55048
+ const p = join18(ROOT11, "package.json");
54994
55049
  if (existsSync10(p))
54995
55050
  return JSON.parse(readFileSync22(p, "utf8")).version ?? "1.0.0";
54996
55051
  } catch {}