blitzstrike 1.0.34 → 1.0.35

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 +27 -0
  2. package/dist/index.js +757 -25
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ All notable changes to Blitz Strike are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [1.0.35] — 2026-09-13
9
+
10
+ ### Added
11
+
12
+ - **Phase 2 — EAGLE-EYE 2.0** (`src/callgraph.ts` + `src/eagle2.ts`):
13
+ - **Call graph** — sound (over-approximate) function/method resolution on a
14
+ PHP AST: name calls, `$this->m()`, static calls, and Class Hierarchy
15
+ Analysis (CHA) for `$obj->m()`.
16
+ - **Whole-program interprocedural taint** — call-graph worklist propagates
17
+ taint transitively across function boundaries (`outer -> inner -> sink`).
18
+ - **Return propagation** — `$x = helper($tainted)` taints `$x` iff the helper
19
+ returns a value derived from its parameter.
20
+ - **By-reference alias tracking** — `&$out` output params that write a source
21
+ taint the caller's variable.
22
+ - **Conditional flow** — ternary (`retif`) + if/else branches are merged
23
+ (sources unioned, sanitizers intersected — sound).
24
+ - **Authorization flow** — sinks guarded by a capability/nonce check are
25
+ flagged `auth_gated` with the *specific* gate name.
26
+ - New MCP tool `eagle_eye2` — prefer over `taint_scan` for accurate
27
+ cross-function reachability.
28
+
29
+ ### Fixed
30
+
31
+ - Sink classification false positives: bare function-name sinks (`system(`, `exec(`, …)
32
+ now use `\b` word boundaries, so `outer_exec(...)` is no longer misclassified
33
+ as command execution.
34
+
8
35
  ## [1.0.33] — 2026-09-13
9
36
 
10
37
  ### Added
package/dist/index.js CHANGED
@@ -14820,7 +14820,7 @@ var require_src = __commonJS(function(exports, module) {
14820
14820
  });
14821
14821
 
14822
14822
  // src/sync.ts
14823
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync12, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync2 } from "node:fs";
14823
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync2 } from "node:fs";
14824
14824
  import { join as join10 } from "node:path";
14825
14825
  import { homedir as homedir4 } from "node:os";
14826
14826
  import { fileURLToPath as fileURLToPath7 } from "node:url";
@@ -14916,7 +14916,7 @@ function manualCopy(src, dst) {
14916
14916
  if (e.isDirectory())
14917
14917
  manualCopy(s, d);
14918
14918
  else
14919
- writeFileSync3(d, readFileSync12(s));
14919
+ writeFileSync3(d, readFileSync14(s));
14920
14920
  }
14921
14921
  }
14922
14922
  var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
@@ -31067,16 +31067,16 @@ function classifySource(line) {
31067
31067
  }
31068
31068
  var SINK_CLASS_MAP = [
31069
31069
  [/->query\(|\$wpdb->query|\$wpdb->get_var|\$wpdb->get_results|->whereRaw|->selectRaw/, { id: "sql_execution", category: "SQL execution", cwe: "CWE-89" }],
31070
- [/eval\(|assert\(|create_function\(|call_user_func\(|preg_replace\(/, { id: "code_execution", category: "Dynamic evaluation / code execution", cwe: "CWE-94" }],
31071
- [/system\(|exec\(|shell_exec\(|passthru\(|proc_open\(|popen\(/, { id: "command_execution", category: "Command execution", cwe: "CWE-78" }],
31072
- [/move_uploaded_file\(|file_put_contents\(|fwrite\(|fopen\(|unlink\(/, { id: "file_operations", category: "File operations", cwe: "CWE-434" }],
31073
- [/include\(|require\(|include_once\(|require_once\(/, { id: "file_inclusion", category: "File inclusion", cwe: "CWE-98" }],
31074
- [/unserialize\(|maybe_unserialize\(/, { id: "deserialization", category: "Deserialization", cwe: "CWE-502" }],
31075
- [/wp_remote_get\(|wp_remote_post\(|file_get_contents\(|curl_exec\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
31076
- [/header\(|wp_redirect\(|wp_safe_redirect\(/, { id: "redirect", category: "Redirect handling", cwe: "CWE-601" }],
31077
- [/echo\s|print\s|printf\(/, { id: "html_render", category: "HTML rendering (XSS)", cwe: "CWE-79" }],
31078
- [/simplexml_load_string\(|new SimpleXMLElement|DOMDocument/, { id: "xml_processing", category: "XML processing (XXE)", cwe: "CWE-611" }],
31079
- [/ZipArchive|PharData|->extractTo\(/, { id: "archive_extraction", category: "Archive extraction (zip slip)", cwe: "CWE-22" }]
31070
+ [/\beval\(|\bassert\(|\bcreate_function\(|\bcall_user_func\(|\bpreg_replace\(/, { id: "code_execution", category: "Dynamic evaluation / code execution", cwe: "CWE-94" }],
31071
+ [/\bsystem\(|\bexec\(|\bshell_exec\(|\bpassthru\(|\bproc_open\(|\bpopen\(/, { id: "command_execution", category: "Command execution", cwe: "CWE-78" }],
31072
+ [/\bmove_uploaded_file\(|\bfile_put_contents\(|\bfwrite\(|\bfopen\(|\bunlink\(/, { id: "file_operations", category: "File operations", cwe: "CWE-434" }],
31073
+ [/\binclude\(|\brequire\(|\binclude_once\(|\brequire_once\(/, { id: "file_inclusion", category: "File inclusion", cwe: "CWE-98" }],
31074
+ [/\bunserialize\(|\bmaybe_unserialize\(/, { id: "deserialization", category: "Deserialization", cwe: "CWE-502" }],
31075
+ [/\bwp_remote_get\(|\bwp_remote_post\(|\bfile_get_contents\(|\bcurl_exec\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
31076
+ [/\bheader\(|\bwp_redirect\(|\bwp_safe_redirect\(/, { id: "redirect", category: "Redirect handling", cwe: "CWE-601" }],
31077
+ [/echo\s|print\s|\bprintf\(/, { id: "html_render", category: "HTML rendering (XSS)", cwe: "CWE-79" }],
31078
+ [/\bsimplexml_load_string\(|new SimpleXMLElement|DOMDocument/, { id: "xml_processing", category: "XML processing (XXE)", cwe: "CWE-611" }],
31079
+ [/\bZipArchive|\bPharData|->extractTo\(/, { id: "archive_extraction", category: "Archive extraction (zip slip)", cwe: "CWE-22" }]
31080
31080
  ];
31081
31081
  function classifySink2(sink) {
31082
31082
  for (const [re, cls] of SINK_CLASS_MAP) {
@@ -31593,6 +31593,706 @@ function taintTree(root, maxFiles = 2000) {
31593
31593
  return files.map((f) => analyzeTaint(f));
31594
31594
  }
31595
31595
 
31596
+ // src/eagle2.ts
31597
+ var import_php_parser3 = __toESM(require_src(), 1);
31598
+ import { readFileSync as readFileSync12 } from "node:fs";
31599
+ init_scanner();
31600
+
31601
+ // src/callgraph.ts
31602
+ var import_php_parser2 = __toESM(require_src(), 1);
31603
+ import { readFileSync as readFileSync11 } from "node:fs";
31604
+ var parser2 = new import_php_parser2.Engine({
31605
+ parser: { extractDoc: false, php7: true },
31606
+ ast: { withPositions: true, withSource: false }
31607
+ });
31608
+ function isNode2(v) {
31609
+ return !!v && typeof v === "object" && typeof v.kind === "string";
31610
+ }
31611
+ function children2(node) {
31612
+ const out = [];
31613
+ for (const k of Object.keys(node)) {
31614
+ if (k === "loc")
31615
+ continue;
31616
+ const v = node[k];
31617
+ if (Array.isArray(v)) {
31618
+ for (const c of v)
31619
+ if (isNode2(c))
31620
+ out.push(c);
31621
+ } else if (isNode2(v)) {
31622
+ out.push(v);
31623
+ }
31624
+ }
31625
+ return out;
31626
+ }
31627
+ function nameOf(node) {
31628
+ if (!isNode2(node))
31629
+ return null;
31630
+ if (typeof node.name === "string")
31631
+ return node.name;
31632
+ if (isNode2(node.name))
31633
+ return nameOf(node.name);
31634
+ return null;
31635
+ }
31636
+ function nodeId(kind, name, cls) {
31637
+ if (kind === "function")
31638
+ return `func:${name}`;
31639
+ if (kind === "method")
31640
+ return `method:${cls ?? "?"}::${name}`;
31641
+ return "main";
31642
+ }
31643
+ function ensureNode(g, id, name, kind, file, line, cls) {
31644
+ let n = g.nodes.get(id);
31645
+ if (!n) {
31646
+ n = { id, name, kind, class: cls, file, line, callees: new Set, callers: new Set };
31647
+ g.nodes.set(id, n);
31648
+ }
31649
+ return n;
31650
+ }
31651
+ function addEdge(g, caller, callee, name, line, file) {
31652
+ g.edges.push({ caller, callee, name, line, file });
31653
+ const c = g.nodes.get(caller);
31654
+ const t = g.nodes.get(callee);
31655
+ if (c)
31656
+ c.callees.add(callee);
31657
+ if (t)
31658
+ t.callers.add(caller);
31659
+ }
31660
+ function buildCallGraph(path) {
31661
+ const g = { nodes: new Map, edges: [] };
31662
+ let code;
31663
+ try {
31664
+ code = readFileSync11(path, "utf8");
31665
+ } catch {
31666
+ return g;
31667
+ }
31668
+ let ast;
31669
+ try {
31670
+ ast = parser2.parseCode(code, path);
31671
+ } catch {
31672
+ return g;
31673
+ }
31674
+ const mainId = nodeId("main", "main");
31675
+ ensureNode(g, mainId, "<main>", "main", path, 0);
31676
+ const funcs = [];
31677
+ const methods = [];
31678
+ walk(ast, path, (n, ctx) => {
31679
+ if (n.kind === "function") {
31680
+ const name = nameOf(n.name);
31681
+ if (name)
31682
+ funcs.push({ name, node: n });
31683
+ } else if (n.kind === "method" && ctx.className) {
31684
+ const mname = nameOf(n.name);
31685
+ if (mname)
31686
+ methods.push({ cls: ctx.className, name: mname, node: n });
31687
+ }
31688
+ });
31689
+ for (const f of funcs) {
31690
+ const id = nodeId("function", f.name);
31691
+ ensureNode(g, id, f.name, "function", path, f.node.loc?.start?.line ?? 0);
31692
+ }
31693
+ for (const m of methods) {
31694
+ const id = nodeId("method", m.name, m.cls);
31695
+ ensureNode(g, id, m.name, "method", path, m.node.loc?.start?.line ?? 0, m.cls);
31696
+ }
31697
+ walk(ast, path, (n, ctx) => {
31698
+ if (n.kind !== "call")
31699
+ return;
31700
+ const line = n.loc?.start?.line ?? 0;
31701
+ const callerId = ctx.enclosing ?? mainId;
31702
+ resolveCall(g, n, callerId, ctx, path, line);
31703
+ });
31704
+ return g;
31705
+ }
31706
+ function walk(node, file, visit, ctx = {}) {
31707
+ if (!isNode2(node))
31708
+ return;
31709
+ let childCtx = ctx;
31710
+ if (node.kind === "class") {
31711
+ childCtx = { ...ctx, className: nameOf(node.name) ?? undefined };
31712
+ } else if (node.kind === "function") {
31713
+ const name = nameOf(node.name);
31714
+ childCtx = { ...ctx, enclosing: name ? nodeId("function", name) : ctx.enclosing };
31715
+ } else if (node.kind === "method") {
31716
+ const name = nameOf(node.name);
31717
+ childCtx = { ...ctx, enclosing: name ? nodeId("method", name, ctx.className) : ctx.enclosing };
31718
+ }
31719
+ visit(node, ctx);
31720
+ for (const c of children2(node))
31721
+ walk(c, file, visit, childCtx);
31722
+ }
31723
+ function resolveCall(g, call, callerId, ctx, file, line) {
31724
+ const what = call.what;
31725
+ if (!isNode2(what))
31726
+ return;
31727
+ if (what.kind === "name") {
31728
+ const fn = nameOf(what);
31729
+ if (!fn)
31730
+ return;
31731
+ const id = nodeId("function", fn);
31732
+ if (g.nodes.has(id)) {
31733
+ addEdge(g, callerId, id, fn, line, file);
31734
+ } else {
31735
+ addEdge(g, callerId, `unknown:${fn}`, fn, line, file);
31736
+ }
31737
+ return;
31738
+ }
31739
+ if (what.kind === "staticlookup") {
31740
+ const base = what.what;
31741
+ const baseName = nameOf(base) ?? (isNode2(base) && base.kind === "variable" ? base.name : null);
31742
+ const method = nameOf(what.offset);
31743
+ if (!method)
31744
+ return;
31745
+ let cls = baseName;
31746
+ if (cls === "self" || cls === "static")
31747
+ cls = ctx.className ?? "?";
31748
+ if (cls && cls !== "?") {
31749
+ const id = nodeId("method", method, cls);
31750
+ if (g.nodes.has(id))
31751
+ addEdge(g, callerId, id, method, line, file);
31752
+ }
31753
+ return;
31754
+ }
31755
+ if (what.kind === "propertylookup") {
31756
+ const base = what.what;
31757
+ const method = nameOf(what.offset);
31758
+ if (!method)
31759
+ return;
31760
+ if (isNode2(base) && base.kind === "variable" && base.name === "this") {
31761
+ if (ctx.className) {
31762
+ const id = nodeId("method", method, ctx.className);
31763
+ if (g.nodes.has(id))
31764
+ addEdge(g, callerId, id, method, line, file);
31765
+ }
31766
+ return;
31767
+ }
31768
+ let matched = false;
31769
+ for (const [id, n] of g.nodes) {
31770
+ if (n.kind === "method" && n.name === method) {
31771
+ addEdge(g, callerId, id, method, line, file);
31772
+ matched = true;
31773
+ }
31774
+ }
31775
+ if (!matched)
31776
+ addEdge(g, callerId, `unknown:${method}`, method, line, file);
31777
+ return;
31778
+ }
31779
+ }
31780
+
31781
+ // src/eagle2.ts
31782
+ var parser3 = new import_php_parser3.Engine({
31783
+ parser: { extractDoc: false, php7: true },
31784
+ ast: { withPositions: true, withSource: false }
31785
+ });
31786
+ function isNode3(v) {
31787
+ return !!v && typeof v === "object" && typeof v.kind === "string";
31788
+ }
31789
+ function children3(node) {
31790
+ const out = [];
31791
+ for (const k of Object.keys(node)) {
31792
+ if (k === "loc")
31793
+ continue;
31794
+ const v = node[k];
31795
+ if (Array.isArray(v)) {
31796
+ for (const c of v)
31797
+ if (isNode3(c))
31798
+ out.push(c);
31799
+ } else if (isNode3(v)) {
31800
+ out.push(v);
31801
+ }
31802
+ }
31803
+ return out;
31804
+ }
31805
+ function findKind2(node, kind, out = []) {
31806
+ if (!isNode3(node))
31807
+ return out;
31808
+ if (node.kind === kind)
31809
+ out.push(node);
31810
+ for (const c of children3(node))
31811
+ findKind2(c, kind, out);
31812
+ return out;
31813
+ }
31814
+ function nameOf2(node) {
31815
+ if (!isNode3(node))
31816
+ return null;
31817
+ if (typeof node.name === "string")
31818
+ return node.name;
31819
+ if (isNode3(node.name))
31820
+ return nameOf2(node.name);
31821
+ return null;
31822
+ }
31823
+ function rootVarName2(node) {
31824
+ if (!isNode3(node))
31825
+ return null;
31826
+ if (node.kind === "variable")
31827
+ return node.name ?? null;
31828
+ if (node.kind === "name")
31829
+ return typeof node.name === "string" ? node.name : node.name?.name ?? null;
31830
+ if (node.kind === "offsetlookup" || node.kind === "propertylookup")
31831
+ return rootVarName2(node.what);
31832
+ return null;
31833
+ }
31834
+ function sourceOf2(node) {
31835
+ if (!isNode3(node))
31836
+ return null;
31837
+ if (node.kind === "offsetlookup" && node.what?.kind === "variable") {
31838
+ const n = node.what.name;
31839
+ if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
31840
+ return "$" + n;
31841
+ }
31842
+ if (node.kind === "variable") {
31843
+ const n = node.name;
31844
+ if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
31845
+ return "$" + n;
31846
+ }
31847
+ if (node.kind === "call") {
31848
+ const n = rootVarName2(node.what);
31849
+ if (n === "getallheaders")
31850
+ return "getallheaders";
31851
+ if (n === "file_get_contents" && JSON.stringify(node).includes("php://input"))
31852
+ return "php://input";
31853
+ }
31854
+ return null;
31855
+ }
31856
+ function sinkClassOf2(call) {
31857
+ const what = call.what;
31858
+ if (!isNode3(what))
31859
+ return null;
31860
+ let token = "";
31861
+ if (what.kind === "propertylookup") {
31862
+ const base = rootVarName2(what.what);
31863
+ const method = what.offset?.name ?? what.offset?.value;
31864
+ token = `${base}->${method}(`;
31865
+ } else if (what.kind === "name") {
31866
+ token = `${what.name}(`;
31867
+ }
31868
+ if (!token)
31869
+ return null;
31870
+ const cls = classifySink2(token) ?? classifySink2(what.name ?? "");
31871
+ if (!cls)
31872
+ return null;
31873
+ return { token, cls };
31874
+ }
31875
+ var CLEAN = { tainted: false, sources: [], sanitizers: [] };
31876
+ function mergeTaint(...states) {
31877
+ const taintedStates = states.filter((s) => s.tainted);
31878
+ if (taintedStates.length === 0)
31879
+ return CLEAN;
31880
+ const sources = [...new Set(taintedStates.flatMap((s) => s.sources))];
31881
+ let sanitizers = taintedStates[0].sanitizers;
31882
+ for (const s of taintedStates.slice(1)) {
31883
+ sanitizers = sanitizers.filter((san) => s.sanitizers.some((x) => x.id === san.id));
31884
+ }
31885
+ return {
31886
+ tainted: true,
31887
+ sources,
31888
+ sanitizers,
31889
+ sourceLine: taintedStates.find((s) => s.sourceLine !== undefined)?.sourceLine
31890
+ };
31891
+ }
31892
+ function summarizeFunction(fn, id, name, summaries) {
31893
+ const params = (fn.arguments ?? []).map((a) => nameOf2(a) ?? "?");
31894
+ const byrefParams = new Set;
31895
+ (fn.arguments ?? []).forEach((a, i) => {
31896
+ if (a.byref === true)
31897
+ byrefParams.add(params[i]);
31898
+ });
31899
+ const summary = { id, name, params, byrefParams, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
31900
+ const bodyStmts = fn.body?.kind === "block" ? fn.body.children ?? [] : [fn.body];
31901
+ for (const p of params) {
31902
+ const env = new Map;
31903
+ for (const q of params)
31904
+ env.set(q, CLEAN);
31905
+ env.set(p, { tainted: true, sources: [`arg:${p}`], sanitizers: [] });
31906
+ const ctx = { sinks: [], returnsTainted: false, calls: [] };
31907
+ analyzeBody(bodyStmts, env, summary, ctx, p, new Map, [], summaries);
31908
+ summary.paramSinks.set(p, ctx.sinks);
31909
+ if (ctx.returnsTainted)
31910
+ summary.paramReturns.add(p);
31911
+ summary.paramCalls.set(p, ctx.calls);
31912
+ if (byrefParams.has(p)) {
31913
+ const finalTaint = env.get(p);
31914
+ const written = finalTaint?.sources.filter((s) => !s.startsWith("arg:")) ?? [];
31915
+ if (written.length > 0)
31916
+ summary.paramWrites.set(p, written);
31917
+ }
31918
+ }
31919
+ return summary;
31920
+ }
31921
+ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
31922
+ for (const stmt of statements) {
31923
+ if (!isNode3(stmt))
31924
+ continue;
31925
+ const kind = stmt.kind;
31926
+ if (kind === "expressionstatement") {
31927
+ analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31928
+ } else if (kind === "if") {
31929
+ const condGates = authGatesOf(stmt.test);
31930
+ const branchAuth = [...activeAuth, ...condGates];
31931
+ const thenStmts = stmt.body?.kind === "block" ? stmt.body.children ?? [] : [stmt.body];
31932
+ const thenEnv = cloneEnv(env);
31933
+ const thenCtx = { sinks: [], returnsTainted: false, calls: [] };
31934
+ analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries);
31935
+ mergeCtx(ctx, thenCtx);
31936
+ if (stmt.alternate) {
31937
+ const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children ?? [] : [stmt.alternate];
31938
+ const altEnv = cloneEnv(env);
31939
+ const altCtx = { sinks: [], returnsTainted: false, calls: [] };
31940
+ analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries);
31941
+ mergeCtx(ctx, altCtx);
31942
+ for (const [k, v] of thenEnv) {
31943
+ const alt = altEnv.get(k);
31944
+ env.set(k, alt ? mergeTaint(v, alt) : v);
31945
+ }
31946
+ } else {
31947
+ for (const [k, v] of thenEnv)
31948
+ env.set(k, v);
31949
+ }
31950
+ } else if (kind === "block") {
31951
+ analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries);
31952
+ } else if (kind === "return") {
31953
+ const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31954
+ if (t.tainted)
31955
+ ctx.returnsTainted = true;
31956
+ } else if (kind === "echo" || kind === "print") {
31957
+ analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31958
+ } else {
31959
+ for (const c of children3(stmt)) {
31960
+ if (c.kind === "block" || c.kind === "if") {
31961
+ analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries);
31962
+ } else {
31963
+ analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31964
+ }
31965
+ }
31966
+ }
31967
+ }
31968
+ }
31969
+ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
31970
+ if (!isNode3(node))
31971
+ return CLEAN;
31972
+ const src = sourceOf2(node);
31973
+ if (src)
31974
+ return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
31975
+ if (node.kind === "assign") {
31976
+ const byref = node.operator === "=&";
31977
+ const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries);
31978
+ const lhs = node.left;
31979
+ if (isNode3(lhs) && lhs.kind === "variable") {
31980
+ const varName = lhs.name;
31981
+ if (byref) {
31982
+ const refVar = rootVarName2(node.right);
31983
+ if (refVar) {
31984
+ const refTaint = env.get(refVar) ?? rhs;
31985
+ env.set(varName, refTaint);
31986
+ env.set(refVar, refTaint);
31987
+ } else {
31988
+ env.set(varName, rhs);
31989
+ }
31990
+ } else {
31991
+ env.set(varName, rhs);
31992
+ }
31993
+ } else if (isNode3(lhs) && lhs.kind === "propertylookup") {
31994
+ const objName = rootVarName2(lhs.what);
31995
+ const propName = lhs.offset?.name ?? "?";
31996
+ if (objName) {
31997
+ let oprops = props.get(objName);
31998
+ if (!oprops) {
31999
+ oprops = new Map;
32000
+ props.set(objName, oprops);
32001
+ }
32002
+ oprops.set(propName, rhs);
32003
+ }
32004
+ }
32005
+ return rhs;
32006
+ }
32007
+ if (node.kind === "call") {
32008
+ const sink = sinkClassOf2(node);
32009
+ const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries));
32010
+ const taintedArgs = argTaints.filter((t) => t.tainted);
32011
+ if (sink) {
32012
+ if (taintedArgs.length > 0) {
32013
+ const san = [...taintedArgs.flatMap((t) => t.sanitizers), ...findSanitizers(JSON.stringify(node))];
32014
+ const sanitized = isSanitized(san, sink.cls.id);
32015
+ const gates = activeAuth;
32016
+ ctx.sinks.push({ sink: sink.token, line: node.loc?.start?.line ?? 0, sanitized, authGated: gates.length > 0, authGates: gates });
32017
+ }
32018
+ for (const a of node.arguments)
32019
+ analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32020
+ return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
32021
+ }
32022
+ const fnName = rootVarName2(node.what);
32023
+ if (fnName) {
32024
+ const san = findSanitizers(fnName + "(");
32025
+ if (san.length > 0) {
32026
+ if (taintedArgs.length > 0) {
32027
+ return mergeTaint(...taintedArgs.map((t) => ({ ...t, sanitizers: [...t.sanitizers, ...san] })));
32028
+ }
32029
+ return CLEAN;
32030
+ }
32031
+ for (let i = 0;i < node.arguments.length; i++) {
32032
+ const argVar = rootVarName2(node.arguments[i]);
32033
+ if (argVar === taintedParam) {
32034
+ ctx.calls.push({ callee: fnName, argIndex: i, line: node.loc?.start?.line ?? 0 });
32035
+ }
32036
+ }
32037
+ const helperId = resolveFuncId(fnName, summaries);
32038
+ const helperSum = helperId ? summaries.get(helperId) : undefined;
32039
+ if (helperSum && taintedArgs.length > 0) {
32040
+ for (let i = 0;i < node.arguments.length; i++) {
32041
+ if (argTaints[i]?.tainted) {
32042
+ const paramName = helperSum.params[i];
32043
+ if (paramName && helperSum.paramReturns.has(paramName)) {
32044
+ return mergeTaint(...taintedArgs);
32045
+ }
32046
+ }
32047
+ }
32048
+ }
32049
+ if (helperSum && helperSum.paramWrites.size > 0) {
32050
+ for (let i = 0;i < node.arguments.length; i++) {
32051
+ const paramName = helperSum.params[i];
32052
+ if (!paramName || !helperSum.paramWrites.has(paramName))
32053
+ continue;
32054
+ const argVar = rootVarName2(node.arguments[i]);
32055
+ const written = helperSum.paramWrites.get(paramName);
32056
+ if (argVar) {
32057
+ env.set(argVar, { tainted: true, sources: written, sanitizers: [] });
32058
+ }
32059
+ }
32060
+ }
32061
+ }
32062
+ return CLEAN;
32063
+ }
32064
+ if (node.kind === "variable") {
32065
+ const t = env.get(node.name);
32066
+ return t ? { ...t } : CLEAN;
32067
+ }
32068
+ if (node.kind === "offsetlookup") {
32069
+ const root = rootVarName2(node);
32070
+ if (root) {
32071
+ const t = env.get(root);
32072
+ if (t)
32073
+ return { ...t };
32074
+ }
32075
+ return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32076
+ }
32077
+ if (node.kind === "propertylookup") {
32078
+ const objName = rootVarName2(node.what);
32079
+ const propName = node.offset?.name ?? "?";
32080
+ if (objName) {
32081
+ const pt = props.get(objName)?.get(propName);
32082
+ if (pt)
32083
+ return { ...pt };
32084
+ }
32085
+ return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32086
+ }
32087
+ if (node.kind === "retif") {
32088
+ const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32089
+ const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries) : CLEAN;
32090
+ return mergeTaint(t, f);
32091
+ }
32092
+ if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
32093
+ const parts = children3(node);
32094
+ const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries));
32095
+ return mergeTaint(...taints);
32096
+ }
32097
+ if (node.kind === "encapsedpart") {
32098
+ return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32099
+ }
32100
+ if (node.kind === "echo" || node.kind === "print") {
32101
+ const exprs = node.expressions ?? [node.expression];
32102
+ const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
32103
+ let ret = CLEAN;
32104
+ for (const e of exprs) {
32105
+ const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries);
32106
+ if (t.tainted && cls) {
32107
+ const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(e))];
32108
+ ctx.sinks.push({ sink: node.kind + " ", line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id), authGated: activeAuth.length > 0, authGates: activeAuth });
32109
+ }
32110
+ ret = mergeTaint(ret, t);
32111
+ }
32112
+ return ret;
32113
+ }
32114
+ return CLEAN;
32115
+ }
32116
+ function cloneEnv(env) {
32117
+ return new Map([...env.entries()].map(([k, v]) => [k, { ...v, sources: [...v.sources], sanitizers: [...v.sanitizers] }]));
32118
+ }
32119
+ function cloneProps(props) {
32120
+ const out = new Map;
32121
+ for (const [k, v] of props)
32122
+ out.set(k, new Map(v));
32123
+ return out;
32124
+ }
32125
+ function mergeCtx(ctx, other) {
32126
+ ctx.sinks.push(...other.sinks);
32127
+ if (other.returnsTainted)
32128
+ ctx.returnsTainted = true;
32129
+ ctx.calls.push(...other.calls);
32130
+ }
32131
+ function authGatesOf(node) {
32132
+ if (!isNode3(node))
32133
+ return [];
32134
+ return AUTH_GATES.filter((g) => JSON.stringify(node).includes(g));
32135
+ }
32136
+ function collectDecls(ast, out) {
32137
+ walkDecls(ast, out);
32138
+ }
32139
+ function walkDecls(node, out, className) {
32140
+ if (!isNode3(node))
32141
+ return;
32142
+ if (node.kind === "function") {
32143
+ const name = nameOf2(node.name);
32144
+ if (name)
32145
+ out.push({ id: `func:${name}`, name, node, kind: "function" });
32146
+ } else if (node.kind === "class") {
32147
+ const cn = nameOf2(node.name);
32148
+ for (const c of children3(node)) {
32149
+ if (c.kind === "method") {
32150
+ const mn = nameOf2(c.name);
32151
+ if (mn && cn)
32152
+ out.push({ id: `method:${cn}::${mn}`, name: mn, node: c, kind: "method" });
32153
+ }
32154
+ }
32155
+ return;
32156
+ } else if (node.kind === "method") {
32157
+ const mn = nameOf2(node.name);
32158
+ if (mn && className)
32159
+ out.push({ id: `method:${className}::${mn}`, name: mn, node, kind: "method" });
32160
+ }
32161
+ const childCtx = node.kind === "class" ? nameOf2(node.name) ?? className : className;
32162
+ for (const c of children3(node))
32163
+ walkDecls(c, out, childCtx);
32164
+ }
32165
+ function resolveFuncId(name, summaries) {
32166
+ if (summaries.has(`func:${name}`))
32167
+ return `func:${name}`;
32168
+ for (const [id, s] of summaries) {
32169
+ if (s.name === name && id.startsWith("method:"))
32170
+ return id;
32171
+ }
32172
+ return null;
32173
+ }
32174
+ function analyzeDataFlow2(path) {
32175
+ const cg = buildCallGraph(path);
32176
+ const result = {
32177
+ file: path,
32178
+ findings: [],
32179
+ suppressed: 0,
32180
+ summary: { functions: 0, methods: 0, edges: cg.edges.length, call_graph: cg }
32181
+ };
32182
+ let code;
32183
+ try {
32184
+ code = readFileSync12(path, "utf8");
32185
+ } catch {
32186
+ return result;
32187
+ }
32188
+ let ast;
32189
+ try {
32190
+ ast = parser3.parseCode(code, path);
32191
+ } catch {
32192
+ return result;
32193
+ }
32194
+ const summaries = new Map;
32195
+ const decls = [];
32196
+ collectDecls(ast, decls);
32197
+ for (const d of decls) {
32198
+ summaries.set(d.id, summarizeFunction(d.node, d.id, d.name, summaries));
32199
+ if (d.kind === "function")
32200
+ result.summary.functions += 1;
32201
+ else
32202
+ result.summary.methods += 1;
32203
+ }
32204
+ const topStmts = [];
32205
+ for (const s of ast.children ?? []) {
32206
+ if (isNode3(s) && s.kind !== "function" && s.kind !== "class")
32207
+ topStmts.push(s);
32208
+ }
32209
+ const mainSummary = { id: "main", name: "main", params: [], byrefParams: new Set, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
32210
+ const mainEnv = new Map;
32211
+ const mainCtx = { sinks: [], returnsTainted: false, calls: [] };
32212
+ const mainProps = new Map;
32213
+ analyzeBody(topStmts, mainEnv, mainSummary, mainCtx, "", mainProps, [], summaries);
32214
+ for (const hit of mainCtx.sinks) {
32215
+ const cls = classifySink2(hit.sink);
32216
+ if (!cls)
32217
+ continue;
32218
+ if (hit.sanitized) {
32219
+ result.suppressed += 1;
32220
+ continue;
32221
+ }
32222
+ result.findings.push({
32223
+ sink: hit.sink,
32224
+ sink_line: hit.line,
32225
+ category: cls.category,
32226
+ cwe: cls.cwe,
32227
+ sanitized: false,
32228
+ auth_gated: hit.authGated,
32229
+ auth_gates: hit.authGates,
32230
+ interprocedural: false,
32231
+ via_return: false
32232
+ });
32233
+ }
32234
+ const queue = [];
32235
+ const seen = new Set;
32236
+ const mainCalls = topStmts.flatMap((s) => findKind2(s, "call"));
32237
+ for (const call of mainCalls) {
32238
+ const fnName = rootVarName2(call.what);
32239
+ if (!fnName || sinkClassOf2(call))
32240
+ continue;
32241
+ const target = resolveFuncId(fnName, summaries);
32242
+ if (!target)
32243
+ continue;
32244
+ for (let i = 0;i < call.arguments.length; i++) {
32245
+ const argTaint = analyzeExpr(call.arguments[i], mainEnv, mainSummary, mainCtx, "", mainProps, [], summaries);
32246
+ if (argTaint.tainted) {
32247
+ const param = summaries.get(target).params[i];
32248
+ if (param)
32249
+ queue.push({ funcId: target, param, source: argTaint.sources[0], sourceLine: argTaint.sourceLine, viaReturn: false });
32250
+ }
32251
+ }
32252
+ }
32253
+ while (queue.length) {
32254
+ const item = queue.shift();
32255
+ const key = `${item.funcId}:${item.param}`;
32256
+ if (seen.has(key))
32257
+ continue;
32258
+ seen.add(key);
32259
+ const sum = summaries.get(item.funcId);
32260
+ if (!sum)
32261
+ continue;
32262
+ for (const hit of sum.paramSinks.get(item.param) ?? []) {
32263
+ const cls = classifySink2(hit.sink);
32264
+ if (!cls)
32265
+ continue;
32266
+ if (hit.sanitized) {
32267
+ result.suppressed += 1;
32268
+ continue;
32269
+ }
32270
+ result.findings.push({
32271
+ sink: hit.sink,
32272
+ sink_line: hit.line,
32273
+ category: cls.category,
32274
+ cwe: cls.cwe,
32275
+ source: item.source,
32276
+ source_line: item.sourceLine,
32277
+ sanitized: false,
32278
+ auth_gated: hit.authGated,
32279
+ auth_gates: hit.authGates,
32280
+ interprocedural: true,
32281
+ via_return: item.viaReturn
32282
+ });
32283
+ }
32284
+ for (const c of sum.paramCalls.get(item.param) ?? []) {
32285
+ const calleeId = resolveFuncId(c.callee, summaries);
32286
+ if (!calleeId)
32287
+ continue;
32288
+ const calleeParam = summaries.get(calleeId).params[c.argIndex];
32289
+ if (calleeParam)
32290
+ queue.push({ funcId: calleeId, param: calleeParam, source: item.source, sourceLine: item.sourceLine, viaReturn: false });
32291
+ }
32292
+ }
32293
+ return result;
32294
+ }
32295
+
31596
32296
  // src/universal-taint.ts
31597
32297
  function runTaintEngine(adapter, code, file) {
31598
32298
  const result = { file, language: adapter.language, findings: [], suppressed: 0 };
@@ -31694,7 +32394,7 @@ function analyzeTaintUniversal(code, file) {
31694
32394
  }
31695
32395
 
31696
32396
  // src/adapters.ts
31697
- var import_php_parser2 = __toESM(require_src(), 1);
32397
+ var import_php_parser4 = __toESM(require_src(), 1);
31698
32398
 
31699
32399
  // node_modules/@babel/parser/lib/index.js
31700
32400
  class Position {
@@ -49473,7 +50173,7 @@ var pythonHighlighting = styleTags({
49473
50173
  ", ;": tags.separator
49474
50174
  });
49475
50175
  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 };
49476
- var parser2 = LRParser.deserialize({
50176
+ var parser4 = LRParser.deserialize({
49477
50177
  version: 14,
49478
50178
  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",
49479
50179
  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~",
@@ -49498,7 +50198,7 @@ var parser2 = LRParser.deserialize({
49498
50198
  });
49499
50199
 
49500
50200
  // src/adapters.ts
49501
- var phpEngine = new import_php_parser2.Engine({ parser: { extractDoc: false, php7: true }, ast: { withPositions: true, withSource: false } });
50201
+ var phpEngine = new import_php_parser4.Engine({ parser: { extractDoc: false, php7: true }, ast: { withPositions: true, withSource: false } });
49502
50202
  var PHP_SOURCES = [
49503
50203
  ["_GET", "http_parameter"],
49504
50204
  ["_POST", "http_body"],
@@ -49791,7 +50491,7 @@ var pythonAdapter = {
49791
50491
  language: "python",
49792
50492
  extensions: [".py", ".pyw"],
49793
50493
  parse(code, _file) {
49794
- const tree = parser2.parse(code);
50494
+ const tree = parser4.parse(code);
49795
50495
  const sources = [];
49796
50496
  const sinks = [];
49797
50497
  const sanitizers = [];
@@ -50774,7 +51474,7 @@ function reportJson(findings, opts = {}) {
50774
51474
  }
50775
51475
 
50776
51476
  // src/benchmark.ts
50777
- import { readFileSync as readFileSync11, readdirSync as readdirSync4, writeFileSync as writeFileSync2, rmSync } from "node:fs";
51477
+ import { readFileSync as readFileSync13, readdirSync as readdirSync4, writeFileSync as writeFileSync2, rmSync } from "node:fs";
50778
51478
  import { join as join9 } from "node:path";
50779
51479
  import { fileURLToPath as fileURLToPath6 } from "node:url";
50780
51480
  import { tmpdir } from "node:os";
@@ -50783,7 +51483,7 @@ var CORPUS = join9(ROOT4, "bench", "corpus");
50783
51483
  function parseCorpusFile(path) {
50784
51484
  let text;
50785
51485
  try {
50786
- text = readFileSync11(path, "utf8");
51486
+ text = readFileSync13(path, "utf8");
50787
51487
  } catch {
50788
51488
  return null;
50789
51489
  }
@@ -51631,6 +52331,38 @@ function createServer() {
51631
52331
  });
51632
52332
  return { content: [{ type: "text", text: JSON.stringify({ ...r, next_steps: guidance }) }] };
51633
52333
  });
52334
+ server.registerTool("eagle_eye2", {
52335
+ title: "Whole-program interprocedural taint (EAGLE-EYE 2.0)",
52336
+ description: "EAGLE-EYE 2.0: whole-program data-flow on a PHP AST — call-graph-aware interprocedural taint with return propagation, by-reference alias tracking, ternary/conditional merge, context-sensitive sanitizers, and authorization-gate detection. Prefer this over taint_scan for accurate cross-function reachability.",
52337
+ inputSchema: { path: string2().describe("Source file path (PHP)") }
52338
+ }, async ({ path }) => {
52339
+ const r = analyzeDataFlow2(path);
52340
+ const taintedSinks = [...new Set(r.findings.map((f) => f.sink))];
52341
+ const authGated = r.findings.filter((f) => f.auth_gated).length;
52342
+ const guidance = nextSteps({
52343
+ tier: "eagle-eye",
52344
+ hasFindings: r.findings.length > 0,
52345
+ sanitizedCount: r.suppressed,
52346
+ taintedSinks
52347
+ });
52348
+ return {
52349
+ content: [{
52350
+ type: "text",
52351
+ text: JSON.stringify({
52352
+ file: r.file,
52353
+ findings: r.findings.length,
52354
+ suppressed: r.suppressed,
52355
+ auth_gated: authGated,
52356
+ interprocedural: r.findings.filter((f) => f.interprocedural).length,
52357
+ functions: r.summary.functions,
52358
+ methods: r.summary.methods,
52359
+ call_edges: r.summary.edges,
52360
+ results: r.findings,
52361
+ next_steps: guidance
52362
+ })
52363
+ }]
52364
+ };
52365
+ });
51634
52366
  server.registerTool("taint_tree", {
51635
52367
  title: "Whole-tree taint scan",
51636
52368
  description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
@@ -51784,7 +52516,7 @@ async function serve() {
51784
52516
  }
51785
52517
 
51786
52518
  // src/cli.ts
51787
- import { readFileSync as readFileSync13, existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "node:fs";
52519
+ import { readFileSync as readFileSync15, existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "node:fs";
51788
52520
  import { join as join11, dirname as dirname2 } from "node:path";
51789
52521
  import { homedir as homedir5 } from "node:os";
51790
52522
  import { fileURLToPath as fileURLToPath8 } from "node:url";
@@ -51866,7 +52598,7 @@ function resolveCommand() {
51866
52598
  }
51867
52599
  function readJson(p) {
51868
52600
  try {
51869
- return JSON.parse(readFileSync13(p, "utf8"));
52601
+ return JSON.parse(readFileSync15(p, "utf8"));
51870
52602
  } catch {
51871
52603
  return null;
51872
52604
  }
@@ -51909,7 +52641,7 @@ function copyOpenCodeAgents() {
51909
52641
  const src = join11(srcDir, f);
51910
52642
  const dst = join11(dstDir, f);
51911
52643
  if (existsSync6(src)) {
51912
- writeFileSync4(dst, readFileSync13(src, "utf8"));
52644
+ writeFileSync4(dst, readFileSync15(src, "utf8"));
51913
52645
  }
51914
52646
  }
51915
52647
  }
@@ -51918,7 +52650,7 @@ function codexWrite(p) {
51918
52650
  const dir = dirname2(p);
51919
52651
  if (!existsSync6(dir))
51920
52652
  mkdirSync4(dir, { recursive: true });
51921
- let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
52653
+ let existing = existsSync6(p) ? readFileSync15(p, "utf8") : "";
51922
52654
  if (!existing.trimEnd().endsWith(`
51923
52655
  `))
51924
52656
  existing += `
@@ -51937,7 +52669,7 @@ function hermesWrite(p) {
51937
52669
  const dir = dirname2(p);
51938
52670
  if (!existsSync6(dir))
51939
52671
  mkdirSync4(dir, { recursive: true });
51940
- let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
52672
+ let existing = existsSync6(p) ? readFileSync15(p, "utf8") : "";
51941
52673
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
51942
52674
  if (!existing.trimEnd().endsWith(`
51943
52675
  `))
@@ -52075,7 +52807,7 @@ Registered with ${ok}/${installed.length} agent(s).`);
52075
52807
  }
52076
52808
 
52077
52809
  // src/index.ts
52078
- import { readFileSync as readFileSync14, existsSync as existsSync7 } from "node:fs";
52810
+ import { readFileSync as readFileSync16, existsSync as existsSync7 } from "node:fs";
52079
52811
  import { join as join12 } from "node:path";
52080
52812
  import { fileURLToPath as fileURLToPath9 } from "node:url";
52081
52813
  var ROOT7 = join12(fileURLToPath9(new URL(".", import.meta.url)), "..");
@@ -52083,7 +52815,7 @@ function readVersion() {
52083
52815
  try {
52084
52816
  const p = join12(ROOT7, "package.json");
52085
52817
  if (existsSync7(p))
52086
- return JSON.parse(readFileSync14(p, "utf8")).version ?? "1.0.0";
52818
+ return JSON.parse(readFileSync16(p, "utf8")).version ?? "1.0.0";
52087
52819
  } catch {}
52088
52820
  return "1.0.0";
52089
52821
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
5
5
  "type": "module",
6
6
  "bin": {