blitzstrike 1.0.34 → 1.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/bench/corpus/java-cmd-vuln.md +8 -0
- package/bench/corpus/java-deser-vuln.md +8 -0
- package/bench/corpus/java-sqli-prep.md +9 -0
- package/bench/corpus/java-sqli-vuln.md +9 -0
- package/bench/corpus/java-ssrf-vuln.md +9 -0
- package/bench/corpus/java-xss-vuln.md +8 -0
- package/bench/corpus/java-xxe-vuln.md +10 -0
- package/bench/corpus/js-cmd-spawn.md +8 -0
- package/bench/corpus/js-cmd-vuln.md +8 -0
- package/bench/corpus/js-eval-vuln.md +8 -0
- package/bench/corpus/js-pathtraversal-vuln.md +8 -0
- package/bench/corpus/js-redirect-vuln.md +8 -0
- package/bench/corpus/js-sqli-param.md +8 -0
- package/bench/corpus/js-ssrf-vuln.md +8 -0
- package/bench/corpus/{js-xss-sanitized.md → js-xss-escape.md} +1 -1
- package/bench/corpus/js-xss-vuln.md +8 -0
- package/bench/corpus/php-cmd-authgated.md +8 -0
- package/bench/corpus/php-cmd-byref.md +11 -0
- package/bench/corpus/php-cmd-escaped.md +9 -0
- package/bench/corpus/php-cmd-escapeshellcmd.md +9 -0
- package/bench/corpus/php-cmd-intval.md +9 -0
- package/bench/corpus/php-cmd-passthru.md +9 -0
- package/bench/corpus/php-cmd-shell-exec.md +9 -0
- package/bench/corpus/php-cmd-transitive.md +10 -0
- package/bench/corpus/{php-command-injection-vuln.md → php-cmd-vuln.md} +1 -1
- package/bench/corpus/php-codeexec-assert.md +9 -0
- package/bench/corpus/php-codeexec-vuln.md +9 -0
- package/bench/corpus/php-fileupload-name.md +9 -0
- package/bench/corpus/php-fileupload-sanitized.md +9 -0
- package/bench/corpus/php-fileupload-vuln.md +9 -0
- package/bench/corpus/php-filewrite-vuln.md +9 -0
- package/bench/corpus/php-include-intval.md +9 -0
- package/bench/corpus/php-include-require.md +9 -0
- package/bench/corpus/php-include-vuln.md +9 -0
- package/bench/corpus/php-include-whitelist.md +10 -0
- package/bench/corpus/php-redirect-safe.md +9 -0
- package/bench/corpus/php-redirect-vuln.md +9 -0
- package/bench/corpus/php-sqli-authgated.md +9 -0
- package/bench/corpus/php-sqli-esc.md +9 -0
- package/bench/corpus/php-sqli-filter.md +9 -0
- package/bench/corpus/php-sqli-getvar.md +9 -0
- package/bench/corpus/php-sqli-interproc.md +10 -0
- package/bench/corpus/php-sqli-whereraw.md +9 -0
- package/bench/corpus/php-ssrf-curl.md +11 -0
- package/bench/corpus/php-ssrf-vuln.md +9 -0
- package/bench/corpus/php-ssrf-wpremote.md +9 -0
- package/bench/corpus/php-unserialize-vuln.md +9 -0
- package/bench/corpus/php-xss-attr.md +9 -0
- package/bench/corpus/php-xss-authgated.md +8 -0
- package/bench/corpus/php-xss-eschtml.md +9 -0
- package/bench/corpus/php-xss-htmlentities.md +9 -0
- package/bench/corpus/php-xss-return.md +9 -0
- package/bench/corpus/php-xss-ternary.md +9 -0
- package/bench/corpus/php-xss-vuln.md +9 -0
- package/bench/corpus/php-xxe-vuln.md +9 -0
- package/bench/corpus/php-zipslip-vuln.md +10 -0
- package/bench/corpus/py-cmd-subprocess.md +9 -0
- package/bench/corpus/{py-command-injection-vuln.md → py-cmd-vuln.md} +3 -3
- package/bench/corpus/py-deser-json.md +9 -0
- package/bench/corpus/py-deser-vuln.md +9 -0
- package/bench/corpus/py-eval-vuln.md +8 -0
- package/bench/corpus/py-pathtraversal-vuln.md +8 -0
- package/bench/corpus/py-sqli-param.md +9 -0
- package/bench/corpus/py-sqli-vuln.md +9 -0
- package/bench/corpus/py-ssrf-vuln.md +9 -0
- package/bench/corpus/py-xss-vuln.md +8 -0
- package/dist/index.js +1124 -86
- package/package.json +1 -1
- package/bench/corpus/java-command-injection-vuln.md +0 -7
- package/bench/corpus/php-file-upload-vuln.md +0 -9
- package/bench/corpus/py-command-injection-sanitized.md +0 -9
package/dist/index.js
CHANGED
|
@@ -14820,8 +14820,8 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14820
14820
|
});
|
|
14821
14821
|
|
|
14822
14822
|
// src/sync.ts
|
|
14823
|
-
import { mkdirSync as mkdirSync3, writeFileSync as
|
|
14824
|
-
import { join as
|
|
14823
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync15, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync3 } from "node:fs";
|
|
14824
|
+
import { join as join11 } from "node:path";
|
|
14825
14825
|
import { homedir as homedir4 } from "node:os";
|
|
14826
14826
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
14827
14827
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -14838,7 +14838,7 @@ function countFiles(dir) {
|
|
|
14838
14838
|
const walk = (d) => {
|
|
14839
14839
|
for (const e of readdirSync5(d, { withFileTypes: true })) {
|
|
14840
14840
|
if (e.isDirectory())
|
|
14841
|
-
walk(
|
|
14841
|
+
walk(join11(d, e.name));
|
|
14842
14842
|
else
|
|
14843
14843
|
n++;
|
|
14844
14844
|
}
|
|
@@ -14852,8 +14852,8 @@ function countFiles(dir) {
|
|
|
14852
14852
|
function syncData() {
|
|
14853
14853
|
if (!dirExists(DATA_ROOT2))
|
|
14854
14854
|
mkdirSync3(DATA_ROOT2, { recursive: true });
|
|
14855
|
-
const payloadsPath =
|
|
14856
|
-
const templatesPath =
|
|
14855
|
+
const payloadsPath = join11(DATA_ROOT2, "payloads");
|
|
14856
|
+
const templatesPath = join11(DATA_ROOT2, "templates");
|
|
14857
14857
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
14858
14858
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
14859
14859
|
if (hasPayloads && hasTemplates) {
|
|
@@ -14863,8 +14863,8 @@ function syncData() {
|
|
|
14863
14863
|
data_root: DATA_ROOT2
|
|
14864
14864
|
};
|
|
14865
14865
|
}
|
|
14866
|
-
const localPayloads =
|
|
14867
|
-
const localTemplates =
|
|
14866
|
+
const localPayloads = join11(ROOT5, "payloads");
|
|
14867
|
+
const localTemplates = join11(ROOT5, "templates");
|
|
14868
14868
|
let payloadsSynced = false;
|
|
14869
14869
|
let templatesSynced = false;
|
|
14870
14870
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -14877,23 +14877,23 @@ function syncData() {
|
|
|
14877
14877
|
}
|
|
14878
14878
|
const gitOk = spawnSync2("git", ["--version"]).status === 0;
|
|
14879
14879
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
14880
|
-
const tmp =
|
|
14880
|
+
const tmp = join11(DATA_ROOT2, ".tmp-clone");
|
|
14881
14881
|
if (dirExists(tmp))
|
|
14882
|
-
|
|
14882
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
14883
14883
|
const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
14884
14884
|
if (r.status === 0) {
|
|
14885
14885
|
const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
14886
14886
|
if (sparse.status === 0) {
|
|
14887
14887
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
14888
|
-
copyDir(
|
|
14888
|
+
copyDir(join11(tmp, "payloads"), payloadsPath);
|
|
14889
14889
|
payloadsSynced = true;
|
|
14890
14890
|
}
|
|
14891
14891
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
14892
|
-
copyDir(
|
|
14892
|
+
copyDir(join11(tmp, "templates"), templatesPath);
|
|
14893
14893
|
templatesSynced = true;
|
|
14894
14894
|
}
|
|
14895
14895
|
}
|
|
14896
|
-
|
|
14896
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
14897
14897
|
}
|
|
14898
14898
|
}
|
|
14899
14899
|
return {
|
|
@@ -14911,18 +14911,18 @@ function copyDir(src, dst) {
|
|
|
14911
14911
|
function manualCopy(src, dst) {
|
|
14912
14912
|
mkdirSync3(dst, { recursive: true });
|
|
14913
14913
|
for (const e of readdirSync5(src, { withFileTypes: true })) {
|
|
14914
|
-
const s =
|
|
14915
|
-
const d =
|
|
14914
|
+
const s = join11(src, e.name);
|
|
14915
|
+
const d = join11(dst, e.name);
|
|
14916
14916
|
if (e.isDirectory())
|
|
14917
14917
|
manualCopy(s, d);
|
|
14918
14918
|
else
|
|
14919
|
-
|
|
14919
|
+
writeFileSync4(d, readFileSync15(s));
|
|
14920
14920
|
}
|
|
14921
14921
|
}
|
|
14922
14922
|
var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
14923
14923
|
var init_sync = __esm(() => {
|
|
14924
|
-
ROOT5 =
|
|
14925
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
14924
|
+
ROOT5 = join11(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
14925
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join11(homedir4(), ".blitzstrike", "data");
|
|
14926
14926
|
});
|
|
14927
14927
|
|
|
14928
14928
|
// src/update.ts
|
|
@@ -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
|
-
[
|
|
31071
|
-
[
|
|
31072
|
-
[
|
|
31073
|
-
[
|
|
31074
|
-
[
|
|
31075
|
-
[
|
|
31076
|
-
[
|
|
31077
|
-
[/echo\s|print\s
|
|
31078
|
-
[
|
|
31079
|
-
[
|
|
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\(|\bcurl_setopt\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
|
|
31076
|
+
[/\bheader\(|\bwp_redirect\(/, { id: "redirect", category: "Redirect handling", cwe: "CWE-601" }],
|
|
31077
|
+
[/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,775 @@ 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
|
+
function reachableFrom(g, entry) {
|
|
31781
|
+
const seen = new Set;
|
|
31782
|
+
const stack = [entry];
|
|
31783
|
+
while (stack.length) {
|
|
31784
|
+
const cur = stack.pop();
|
|
31785
|
+
if (seen.has(cur))
|
|
31786
|
+
continue;
|
|
31787
|
+
seen.add(cur);
|
|
31788
|
+
const n = g.nodes.get(cur);
|
|
31789
|
+
if (!n)
|
|
31790
|
+
continue;
|
|
31791
|
+
for (const c of n.callees)
|
|
31792
|
+
stack.push(c);
|
|
31793
|
+
}
|
|
31794
|
+
return seen;
|
|
31795
|
+
}
|
|
31796
|
+
function entryPoints(g) {
|
|
31797
|
+
const entries = [];
|
|
31798
|
+
for (const [id, n] of g.nodes) {
|
|
31799
|
+
if (n.callers.size === 0)
|
|
31800
|
+
entries.push(id);
|
|
31801
|
+
}
|
|
31802
|
+
return entries;
|
|
31803
|
+
}
|
|
31804
|
+
|
|
31805
|
+
// src/eagle2.ts
|
|
31806
|
+
var parser3 = new import_php_parser3.Engine({
|
|
31807
|
+
parser: { extractDoc: false, php7: true },
|
|
31808
|
+
ast: { withPositions: true, withSource: false }
|
|
31809
|
+
});
|
|
31810
|
+
function isNode3(v) {
|
|
31811
|
+
return !!v && typeof v === "object" && typeof v.kind === "string";
|
|
31812
|
+
}
|
|
31813
|
+
function children3(node) {
|
|
31814
|
+
const out = [];
|
|
31815
|
+
for (const k of Object.keys(node)) {
|
|
31816
|
+
if (k === "loc")
|
|
31817
|
+
continue;
|
|
31818
|
+
const v = node[k];
|
|
31819
|
+
if (Array.isArray(v)) {
|
|
31820
|
+
for (const c of v)
|
|
31821
|
+
if (isNode3(c))
|
|
31822
|
+
out.push(c);
|
|
31823
|
+
} else if (isNode3(v)) {
|
|
31824
|
+
out.push(v);
|
|
31825
|
+
}
|
|
31826
|
+
}
|
|
31827
|
+
return out;
|
|
31828
|
+
}
|
|
31829
|
+
function findKind2(node, kind, out = []) {
|
|
31830
|
+
if (!isNode3(node))
|
|
31831
|
+
return out;
|
|
31832
|
+
if (node.kind === kind)
|
|
31833
|
+
out.push(node);
|
|
31834
|
+
for (const c of children3(node))
|
|
31835
|
+
findKind2(c, kind, out);
|
|
31836
|
+
return out;
|
|
31837
|
+
}
|
|
31838
|
+
function nameOf2(node) {
|
|
31839
|
+
if (!isNode3(node))
|
|
31840
|
+
return null;
|
|
31841
|
+
if (typeof node.name === "string")
|
|
31842
|
+
return node.name;
|
|
31843
|
+
if (isNode3(node.name))
|
|
31844
|
+
return nameOf2(node.name);
|
|
31845
|
+
return null;
|
|
31846
|
+
}
|
|
31847
|
+
function rootVarName2(node) {
|
|
31848
|
+
if (!isNode3(node))
|
|
31849
|
+
return null;
|
|
31850
|
+
if (node.kind === "variable")
|
|
31851
|
+
return node.name ?? null;
|
|
31852
|
+
if (node.kind === "name")
|
|
31853
|
+
return typeof node.name === "string" ? node.name : node.name?.name ?? null;
|
|
31854
|
+
if (node.kind === "offsetlookup" || node.kind === "propertylookup")
|
|
31855
|
+
return rootVarName2(node.what);
|
|
31856
|
+
return null;
|
|
31857
|
+
}
|
|
31858
|
+
function sourceOf2(node) {
|
|
31859
|
+
if (!isNode3(node))
|
|
31860
|
+
return null;
|
|
31861
|
+
if (node.kind === "offsetlookup" && node.what?.kind === "variable") {
|
|
31862
|
+
const n = node.what.name;
|
|
31863
|
+
if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
|
|
31864
|
+
return "$" + n;
|
|
31865
|
+
}
|
|
31866
|
+
if (node.kind === "variable") {
|
|
31867
|
+
const n = node.name;
|
|
31868
|
+
if (["_GET", "_POST", "_REQUEST", "_COOKIE", "_FILES", "_SERVER"].includes(n))
|
|
31869
|
+
return "$" + n;
|
|
31870
|
+
}
|
|
31871
|
+
if (node.kind === "call") {
|
|
31872
|
+
const n = rootVarName2(node.what);
|
|
31873
|
+
if (n === "getallheaders")
|
|
31874
|
+
return "getallheaders";
|
|
31875
|
+
if (n === "file_get_contents" && JSON.stringify(node).includes("php://input"))
|
|
31876
|
+
return "php://input";
|
|
31877
|
+
}
|
|
31878
|
+
return null;
|
|
31879
|
+
}
|
|
31880
|
+
function sinkClassOf2(call) {
|
|
31881
|
+
const what = call.what;
|
|
31882
|
+
if (!isNode3(what))
|
|
31883
|
+
return null;
|
|
31884
|
+
let token = "";
|
|
31885
|
+
if (what.kind === "propertylookup") {
|
|
31886
|
+
const baseNode = what.what;
|
|
31887
|
+
const base = rootVarName2(baseNode);
|
|
31888
|
+
const method = what.offset?.name ?? what.offset?.value;
|
|
31889
|
+
const baseStr = isNode3(baseNode) && (baseNode.kind === "variable" || baseNode.kind === "offsetlookup") ? `$${base}` : base;
|
|
31890
|
+
token = `${baseStr}->${method}(`;
|
|
31891
|
+
} else if (what.kind === "name") {
|
|
31892
|
+
token = `${what.name}(`;
|
|
31893
|
+
}
|
|
31894
|
+
if (!token)
|
|
31895
|
+
return null;
|
|
31896
|
+
const cls = classifySink2(token) ?? classifySink2(what.name ?? "");
|
|
31897
|
+
if (!cls)
|
|
31898
|
+
return null;
|
|
31899
|
+
return { token, cls };
|
|
31900
|
+
}
|
|
31901
|
+
var CLEAN = { tainted: false, sources: [], sanitizers: [] };
|
|
31902
|
+
function mergeTaint(...states) {
|
|
31903
|
+
const taintedStates = states.filter((s) => s.tainted);
|
|
31904
|
+
if (taintedStates.length === 0)
|
|
31905
|
+
return CLEAN;
|
|
31906
|
+
const sources = [...new Set(taintedStates.flatMap((s) => s.sources))];
|
|
31907
|
+
let sanitizers = taintedStates[0].sanitizers;
|
|
31908
|
+
for (const s of taintedStates.slice(1)) {
|
|
31909
|
+
sanitizers = sanitizers.filter((san) => s.sanitizers.some((x) => x.id === san.id));
|
|
31910
|
+
}
|
|
31911
|
+
return {
|
|
31912
|
+
tainted: true,
|
|
31913
|
+
sources,
|
|
31914
|
+
sanitizers,
|
|
31915
|
+
sourceLine: taintedStates.find((s) => s.sourceLine !== undefined)?.sourceLine
|
|
31916
|
+
};
|
|
31917
|
+
}
|
|
31918
|
+
function summarizeFunction(fn, id, name, summaries) {
|
|
31919
|
+
const params = (fn.arguments ?? []).map((a) => nameOf2(a) ?? "?");
|
|
31920
|
+
const byrefParams = new Set;
|
|
31921
|
+
(fn.arguments ?? []).forEach((a, i) => {
|
|
31922
|
+
if (a.byref === true)
|
|
31923
|
+
byrefParams.add(params[i]);
|
|
31924
|
+
});
|
|
31925
|
+
const summary = { id, name, params, byrefParams, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
|
|
31926
|
+
const bodyStmts = fn.body?.kind === "block" ? fn.body.children ?? [] : [fn.body];
|
|
31927
|
+
for (const p of params) {
|
|
31928
|
+
const env = new Map;
|
|
31929
|
+
for (const q of params)
|
|
31930
|
+
env.set(q, CLEAN);
|
|
31931
|
+
env.set(p, { tainted: true, sources: [`arg:${p}`], sanitizers: [] });
|
|
31932
|
+
const ctx = { sinks: [], returnsTainted: false, calls: [] };
|
|
31933
|
+
analyzeBody(bodyStmts, env, summary, ctx, p, new Map, [], summaries);
|
|
31934
|
+
summary.paramSinks.set(p, ctx.sinks);
|
|
31935
|
+
if (ctx.returnsTainted)
|
|
31936
|
+
summary.paramReturns.add(p);
|
|
31937
|
+
summary.paramCalls.set(p, ctx.calls);
|
|
31938
|
+
if (byrefParams.has(p)) {
|
|
31939
|
+
const finalTaint = env.get(p);
|
|
31940
|
+
const written = finalTaint?.sources.filter((s) => !s.startsWith("arg:")) ?? [];
|
|
31941
|
+
if (written.length > 0)
|
|
31942
|
+
summary.paramWrites.set(p, written);
|
|
31943
|
+
}
|
|
31944
|
+
}
|
|
31945
|
+
return summary;
|
|
31946
|
+
}
|
|
31947
|
+
function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
|
|
31948
|
+
for (const stmt of statements) {
|
|
31949
|
+
if (!isNode3(stmt))
|
|
31950
|
+
continue;
|
|
31951
|
+
const kind = stmt.kind;
|
|
31952
|
+
if (kind === "expressionstatement") {
|
|
31953
|
+
analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31954
|
+
} else if (kind === "if") {
|
|
31955
|
+
const condGates = authGatesOf(stmt.test);
|
|
31956
|
+
const branchAuth = [...activeAuth, ...condGates];
|
|
31957
|
+
const condValidating = validationGatesOf(stmt.test);
|
|
31958
|
+
const thenStmts = stmt.body?.kind === "block" ? stmt.body.children ?? [] : [stmt.body];
|
|
31959
|
+
const thenEnv = cloneEnv(env);
|
|
31960
|
+
const thenCtx = { sinks: [], returnsTainted: false, calls: [] };
|
|
31961
|
+
analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries, validating || condValidating);
|
|
31962
|
+
mergeCtx(ctx, thenCtx);
|
|
31963
|
+
if (stmt.alternate) {
|
|
31964
|
+
const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children ?? [] : [stmt.alternate];
|
|
31965
|
+
const altEnv = cloneEnv(env);
|
|
31966
|
+
const altCtx = { sinks: [], returnsTainted: false, calls: [] };
|
|
31967
|
+
analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries, validating);
|
|
31968
|
+
mergeCtx(ctx, altCtx);
|
|
31969
|
+
for (const [k, v] of thenEnv) {
|
|
31970
|
+
const alt = altEnv.get(k);
|
|
31971
|
+
env.set(k, alt ? mergeTaint(v, alt) : v);
|
|
31972
|
+
}
|
|
31973
|
+
} else {
|
|
31974
|
+
for (const [k, v] of thenEnv)
|
|
31975
|
+
env.set(k, v);
|
|
31976
|
+
}
|
|
31977
|
+
} else if (kind === "block") {
|
|
31978
|
+
analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31979
|
+
} else if (kind === "return") {
|
|
31980
|
+
const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31981
|
+
if (t.tainted)
|
|
31982
|
+
ctx.returnsTainted = true;
|
|
31983
|
+
} else if (kind === "echo" || kind === "print") {
|
|
31984
|
+
analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31985
|
+
} else {
|
|
31986
|
+
for (const c of children3(stmt)) {
|
|
31987
|
+
if (c.kind === "block" || c.kind === "if") {
|
|
31988
|
+
analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31989
|
+
} else {
|
|
31990
|
+
analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31991
|
+
}
|
|
31992
|
+
}
|
|
31993
|
+
}
|
|
31994
|
+
}
|
|
31995
|
+
}
|
|
31996
|
+
function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
|
|
31997
|
+
if (!isNode3(node))
|
|
31998
|
+
return CLEAN;
|
|
31999
|
+
const src = sourceOf2(node);
|
|
32000
|
+
if (src)
|
|
32001
|
+
return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
|
|
32002
|
+
if (node.kind === "assign") {
|
|
32003
|
+
const byref = node.operator === "=&";
|
|
32004
|
+
const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32005
|
+
const lhs = node.left;
|
|
32006
|
+
if (isNode3(lhs) && lhs.kind === "variable") {
|
|
32007
|
+
const varName = lhs.name;
|
|
32008
|
+
if (byref) {
|
|
32009
|
+
const refVar = rootVarName2(node.right);
|
|
32010
|
+
if (refVar) {
|
|
32011
|
+
const refTaint = env.get(refVar) ?? rhs;
|
|
32012
|
+
env.set(varName, refTaint);
|
|
32013
|
+
env.set(refVar, refTaint);
|
|
32014
|
+
} else {
|
|
32015
|
+
env.set(varName, rhs);
|
|
32016
|
+
}
|
|
32017
|
+
} else {
|
|
32018
|
+
env.set(varName, rhs);
|
|
32019
|
+
}
|
|
32020
|
+
} else if (isNode3(lhs) && lhs.kind === "propertylookup") {
|
|
32021
|
+
const objName = rootVarName2(lhs.what);
|
|
32022
|
+
const propName = lhs.offset?.name ?? "?";
|
|
32023
|
+
if (objName) {
|
|
32024
|
+
let oprops = props.get(objName);
|
|
32025
|
+
if (!oprops) {
|
|
32026
|
+
oprops = new Map;
|
|
32027
|
+
props.set(objName, oprops);
|
|
32028
|
+
}
|
|
32029
|
+
oprops.set(propName, rhs);
|
|
32030
|
+
}
|
|
32031
|
+
}
|
|
32032
|
+
return rhs;
|
|
32033
|
+
}
|
|
32034
|
+
if (node.kind === "call") {
|
|
32035
|
+
const sink = sinkClassOf2(node);
|
|
32036
|
+
const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
|
|
32037
|
+
const taintedArgs = argTaints.filter((t) => t.tainted);
|
|
32038
|
+
if (sink) {
|
|
32039
|
+
if (taintedArgs.length > 0) {
|
|
32040
|
+
const san = [...taintedArgs.flatMap((t) => t.sanitizers), ...findSanitizers(JSON.stringify(node))];
|
|
32041
|
+
const sanitized = isSanitized(san, sink.cls.id);
|
|
32042
|
+
const gates = activeAuth;
|
|
32043
|
+
ctx.sinks.push({ sink: sink.token, line: node.loc?.start?.line ?? 0, sanitized, authGated: gates.length > 0, authGates: gates });
|
|
32044
|
+
}
|
|
32045
|
+
for (const a of node.arguments)
|
|
32046
|
+
analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32047
|
+
return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
|
|
32048
|
+
}
|
|
32049
|
+
const fnName = rootVarName2(node.what);
|
|
32050
|
+
if (fnName) {
|
|
32051
|
+
const san = findSanitizers(fnName + "(");
|
|
32052
|
+
if (san.length > 0) {
|
|
32053
|
+
if (taintedArgs.length > 0) {
|
|
32054
|
+
return mergeTaint(...taintedArgs.map((t) => ({ ...t, sanitizers: [...t.sanitizers, ...san] })));
|
|
32055
|
+
}
|
|
32056
|
+
return CLEAN;
|
|
32057
|
+
}
|
|
32058
|
+
for (let i = 0;i < node.arguments.length; i++) {
|
|
32059
|
+
const argVar = rootVarName2(node.arguments[i]);
|
|
32060
|
+
if (argVar === taintedParam) {
|
|
32061
|
+
ctx.calls.push({ callee: fnName, argIndex: i, line: node.loc?.start?.line ?? 0 });
|
|
32062
|
+
}
|
|
32063
|
+
}
|
|
32064
|
+
const helperId = resolveFuncId(fnName, summaries);
|
|
32065
|
+
const helperSum = helperId ? summaries.get(helperId) : undefined;
|
|
32066
|
+
if (helperSum && taintedArgs.length > 0) {
|
|
32067
|
+
for (let i = 0;i < node.arguments.length; i++) {
|
|
32068
|
+
if (argTaints[i]?.tainted) {
|
|
32069
|
+
const paramName = helperSum.params[i];
|
|
32070
|
+
if (paramName && helperSum.paramReturns.has(paramName)) {
|
|
32071
|
+
return mergeTaint(...taintedArgs);
|
|
32072
|
+
}
|
|
32073
|
+
}
|
|
32074
|
+
}
|
|
32075
|
+
}
|
|
32076
|
+
if (helperSum && helperSum.paramWrites.size > 0) {
|
|
32077
|
+
for (let i = 0;i < node.arguments.length; i++) {
|
|
32078
|
+
const paramName = helperSum.params[i];
|
|
32079
|
+
if (!paramName || !helperSum.paramWrites.has(paramName))
|
|
32080
|
+
continue;
|
|
32081
|
+
const argVar = rootVarName2(node.arguments[i]);
|
|
32082
|
+
const written = helperSum.paramWrites.get(paramName);
|
|
32083
|
+
if (argVar) {
|
|
32084
|
+
env.set(argVar, { tainted: true, sources: written, sanitizers: [] });
|
|
32085
|
+
}
|
|
32086
|
+
}
|
|
32087
|
+
}
|
|
32088
|
+
}
|
|
32089
|
+
if (isNode3(node.what) && node.what.kind === "propertylookup") {
|
|
32090
|
+
analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32091
|
+
}
|
|
32092
|
+
return CLEAN;
|
|
32093
|
+
}
|
|
32094
|
+
if (node.kind === "variable") {
|
|
32095
|
+
const t = env.get(node.name);
|
|
32096
|
+
return t ? { ...t } : CLEAN;
|
|
32097
|
+
}
|
|
32098
|
+
if (node.kind === "offsetlookup") {
|
|
32099
|
+
const root = rootVarName2(node);
|
|
32100
|
+
if (root) {
|
|
32101
|
+
const t = env.get(root);
|
|
32102
|
+
if (t)
|
|
32103
|
+
return { ...t };
|
|
32104
|
+
}
|
|
32105
|
+
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32106
|
+
}
|
|
32107
|
+
if (node.kind === "propertylookup") {
|
|
32108
|
+
const objName = rootVarName2(node.what);
|
|
32109
|
+
const propName = node.offset?.name ?? "?";
|
|
32110
|
+
if (objName) {
|
|
32111
|
+
const pt = props.get(objName)?.get(propName);
|
|
32112
|
+
if (pt)
|
|
32113
|
+
return { ...pt };
|
|
32114
|
+
}
|
|
32115
|
+
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32116
|
+
}
|
|
32117
|
+
if (node.kind === "retif") {
|
|
32118
|
+
const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32119
|
+
const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating) : CLEAN;
|
|
32120
|
+
return mergeTaint(t, f);
|
|
32121
|
+
}
|
|
32122
|
+
if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
|
|
32123
|
+
const parts = children3(node);
|
|
32124
|
+
const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
|
|
32125
|
+
return mergeTaint(...taints);
|
|
32126
|
+
}
|
|
32127
|
+
if (node.kind === "encapsedpart") {
|
|
32128
|
+
return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32129
|
+
}
|
|
32130
|
+
if (node.kind === "echo" || node.kind === "print") {
|
|
32131
|
+
const exprs = node.expressions ?? [node.expression];
|
|
32132
|
+
const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
|
|
32133
|
+
let ret = CLEAN;
|
|
32134
|
+
for (const e of exprs) {
|
|
32135
|
+
const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32136
|
+
if (t.tainted && cls) {
|
|
32137
|
+
const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(e))];
|
|
32138
|
+
ctx.sinks.push({ sink: node.kind + " ", line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32139
|
+
}
|
|
32140
|
+
ret = mergeTaint(ret, t);
|
|
32141
|
+
}
|
|
32142
|
+
return ret;
|
|
32143
|
+
}
|
|
32144
|
+
if (node.kind === "include") {
|
|
32145
|
+
const base = node.require ? "require" : "include";
|
|
32146
|
+
const suffix = node.once ? "_once" : "";
|
|
32147
|
+
const sinkToken = base + suffix + "(";
|
|
32148
|
+
const cls = classifySink2(sinkToken);
|
|
32149
|
+
const t = analyzeExpr(node.target, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32150
|
+
if (t.tainted && cls) {
|
|
32151
|
+
const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(node))];
|
|
32152
|
+
ctx.sinks.push({ sink: sinkToken, line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32153
|
+
}
|
|
32154
|
+
return t;
|
|
32155
|
+
}
|
|
32156
|
+
if (node.kind === "eval") {
|
|
32157
|
+
const cls = classifySink2("eval(");
|
|
32158
|
+
const t = analyzeExpr(node.source, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32159
|
+
if (t.tainted && cls) {
|
|
32160
|
+
ctx.sinks.push({ sink: "eval(", line: node.loc?.start?.line ?? 0, sanitized: false, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32161
|
+
}
|
|
32162
|
+
return t;
|
|
32163
|
+
}
|
|
32164
|
+
return CLEAN;
|
|
32165
|
+
}
|
|
32166
|
+
function cloneEnv(env) {
|
|
32167
|
+
return new Map([...env.entries()].map(([k, v]) => [k, { ...v, sources: [...v.sources], sanitizers: [...v.sanitizers] }]));
|
|
32168
|
+
}
|
|
32169
|
+
function cloneProps(props) {
|
|
32170
|
+
const out = new Map;
|
|
32171
|
+
for (const [k, v] of props)
|
|
32172
|
+
out.set(k, new Map(v));
|
|
32173
|
+
return out;
|
|
32174
|
+
}
|
|
32175
|
+
function mergeCtx(ctx, other) {
|
|
32176
|
+
ctx.sinks.push(...other.sinks);
|
|
32177
|
+
if (other.returnsTainted)
|
|
32178
|
+
ctx.returnsTainted = true;
|
|
32179
|
+
ctx.calls.push(...other.calls);
|
|
32180
|
+
}
|
|
32181
|
+
function authGatesOf(node) {
|
|
32182
|
+
if (!isNode3(node))
|
|
32183
|
+
return [];
|
|
32184
|
+
return AUTH_GATES.filter((g) => JSON.stringify(node).includes(g));
|
|
32185
|
+
}
|
|
32186
|
+
var VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
|
|
32187
|
+
function validationGatesOf(node) {
|
|
32188
|
+
if (!isNode3(node))
|
|
32189
|
+
return false;
|
|
32190
|
+
const text = JSON.stringify(node);
|
|
32191
|
+
return VALIDATION_GATES.some((g) => text.includes(g));
|
|
32192
|
+
}
|
|
32193
|
+
function collectDecls(ast, out) {
|
|
32194
|
+
walkDecls(ast, out);
|
|
32195
|
+
}
|
|
32196
|
+
function walkDecls(node, out, className) {
|
|
32197
|
+
if (!isNode3(node))
|
|
32198
|
+
return;
|
|
32199
|
+
if (node.kind === "function") {
|
|
32200
|
+
const name = nameOf2(node.name);
|
|
32201
|
+
if (name)
|
|
32202
|
+
out.push({ id: `func:${name}`, name, node, kind: "function" });
|
|
32203
|
+
} else if (node.kind === "class") {
|
|
32204
|
+
const cn = nameOf2(node.name);
|
|
32205
|
+
for (const c of children3(node)) {
|
|
32206
|
+
if (c.kind === "method") {
|
|
32207
|
+
const mn = nameOf2(c.name);
|
|
32208
|
+
if (mn && cn)
|
|
32209
|
+
out.push({ id: `method:${cn}::${mn}`, name: mn, node: c, kind: "method" });
|
|
32210
|
+
}
|
|
32211
|
+
}
|
|
32212
|
+
return;
|
|
32213
|
+
} else if (node.kind === "method") {
|
|
32214
|
+
const mn = nameOf2(node.name);
|
|
32215
|
+
if (mn && className)
|
|
32216
|
+
out.push({ id: `method:${className}::${mn}`, name: mn, node, kind: "method" });
|
|
32217
|
+
}
|
|
32218
|
+
const childCtx = node.kind === "class" ? nameOf2(node.name) ?? className : className;
|
|
32219
|
+
for (const c of children3(node))
|
|
32220
|
+
walkDecls(c, out, childCtx);
|
|
32221
|
+
}
|
|
32222
|
+
function resolveFuncId(name, summaries) {
|
|
32223
|
+
if (summaries.has(`func:${name}`))
|
|
32224
|
+
return `func:${name}`;
|
|
32225
|
+
for (const [id, s] of summaries) {
|
|
32226
|
+
if (s.name === name && id.startsWith("method:"))
|
|
32227
|
+
return id;
|
|
32228
|
+
}
|
|
32229
|
+
return null;
|
|
32230
|
+
}
|
|
32231
|
+
function analyzeDataFlow2(path) {
|
|
32232
|
+
const cg = buildCallGraph(path);
|
|
32233
|
+
const entries = entryPoints(cg);
|
|
32234
|
+
const reachable = new Set;
|
|
32235
|
+
for (const e of entries)
|
|
32236
|
+
for (const n of reachableFrom(cg, e))
|
|
32237
|
+
reachable.add(n);
|
|
32238
|
+
const unreachable = [...cg.nodes.keys()].filter((id) => !reachable.has(id));
|
|
32239
|
+
const result = {
|
|
32240
|
+
file: path,
|
|
32241
|
+
findings: [],
|
|
32242
|
+
suppressed: 0,
|
|
32243
|
+
summary: { functions: 0, methods: 0, edges: cg.edges.length, entry_points: entries, unreachable }
|
|
32244
|
+
};
|
|
32245
|
+
let code;
|
|
32246
|
+
try {
|
|
32247
|
+
code = readFileSync12(path, "utf8");
|
|
32248
|
+
} catch {
|
|
32249
|
+
return result;
|
|
32250
|
+
}
|
|
32251
|
+
let ast;
|
|
32252
|
+
try {
|
|
32253
|
+
ast = parser3.parseCode(code, path);
|
|
32254
|
+
} catch {
|
|
32255
|
+
return result;
|
|
32256
|
+
}
|
|
32257
|
+
const summaries = new Map;
|
|
32258
|
+
const decls = [];
|
|
32259
|
+
collectDecls(ast, decls);
|
|
32260
|
+
for (const d of decls) {
|
|
32261
|
+
summaries.set(d.id, summarizeFunction(d.node, d.id, d.name, summaries));
|
|
32262
|
+
if (d.kind === "function")
|
|
32263
|
+
result.summary.functions += 1;
|
|
32264
|
+
else
|
|
32265
|
+
result.summary.methods += 1;
|
|
32266
|
+
}
|
|
32267
|
+
const topStmts = [];
|
|
32268
|
+
for (const s of ast.children ?? []) {
|
|
32269
|
+
if (isNode3(s) && s.kind !== "function" && s.kind !== "class")
|
|
32270
|
+
topStmts.push(s);
|
|
32271
|
+
}
|
|
32272
|
+
const mainSummary = { id: "main", name: "main", params: [], byrefParams: new Set, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
|
|
32273
|
+
const mainEnv = new Map;
|
|
32274
|
+
const mainCtx = { sinks: [], returnsTainted: false, calls: [] };
|
|
32275
|
+
const mainProps = new Map;
|
|
32276
|
+
analyzeBody(topStmts, mainEnv, mainSummary, mainCtx, "", mainProps, [], summaries);
|
|
32277
|
+
for (const hit of mainCtx.sinks) {
|
|
32278
|
+
const cls = classifySink2(hit.sink);
|
|
32279
|
+
if (!cls)
|
|
32280
|
+
continue;
|
|
32281
|
+
if (hit.sanitized) {
|
|
32282
|
+
result.suppressed += 1;
|
|
32283
|
+
continue;
|
|
32284
|
+
}
|
|
32285
|
+
result.findings.push({
|
|
32286
|
+
sink: hit.sink,
|
|
32287
|
+
sink_line: hit.line,
|
|
32288
|
+
category: cls.category,
|
|
32289
|
+
cwe: cls.cwe,
|
|
32290
|
+
sanitized: false,
|
|
32291
|
+
auth_gated: hit.authGated,
|
|
32292
|
+
auth_gates: hit.authGates,
|
|
32293
|
+
interprocedural: false,
|
|
32294
|
+
via_return: false
|
|
32295
|
+
});
|
|
32296
|
+
}
|
|
32297
|
+
const queue = [];
|
|
32298
|
+
const seen = new Set;
|
|
32299
|
+
const mainCalls = topStmts.flatMap((s) => findKind2(s, "call"));
|
|
32300
|
+
for (const call of mainCalls) {
|
|
32301
|
+
const fnName = rootVarName2(call.what);
|
|
32302
|
+
if (!fnName || sinkClassOf2(call))
|
|
32303
|
+
continue;
|
|
32304
|
+
const target = resolveFuncId(fnName, summaries);
|
|
32305
|
+
if (!target)
|
|
32306
|
+
continue;
|
|
32307
|
+
for (let i = 0;i < call.arguments.length; i++) {
|
|
32308
|
+
const argTaint = analyzeExpr(call.arguments[i], mainEnv, mainSummary, mainCtx, "", mainProps, [], summaries);
|
|
32309
|
+
if (argTaint.tainted) {
|
|
32310
|
+
const param = summaries.get(target).params[i];
|
|
32311
|
+
if (param)
|
|
32312
|
+
queue.push({ funcId: target, param, source: argTaint.sources[0], sourceLine: argTaint.sourceLine, viaReturn: false });
|
|
32313
|
+
}
|
|
32314
|
+
}
|
|
32315
|
+
}
|
|
32316
|
+
while (queue.length) {
|
|
32317
|
+
const item = queue.shift();
|
|
32318
|
+
const key = `${item.funcId}:${item.param}`;
|
|
32319
|
+
if (seen.has(key))
|
|
32320
|
+
continue;
|
|
32321
|
+
seen.add(key);
|
|
32322
|
+
const sum = summaries.get(item.funcId);
|
|
32323
|
+
if (!sum)
|
|
32324
|
+
continue;
|
|
32325
|
+
for (const hit of sum.paramSinks.get(item.param) ?? []) {
|
|
32326
|
+
const cls = classifySink2(hit.sink);
|
|
32327
|
+
if (!cls)
|
|
32328
|
+
continue;
|
|
32329
|
+
if (hit.sanitized) {
|
|
32330
|
+
result.suppressed += 1;
|
|
32331
|
+
continue;
|
|
32332
|
+
}
|
|
32333
|
+
result.findings.push({
|
|
32334
|
+
sink: hit.sink,
|
|
32335
|
+
sink_line: hit.line,
|
|
32336
|
+
category: cls.category,
|
|
32337
|
+
cwe: cls.cwe,
|
|
32338
|
+
source: item.source,
|
|
32339
|
+
source_line: item.sourceLine,
|
|
32340
|
+
sanitized: false,
|
|
32341
|
+
auth_gated: hit.authGated,
|
|
32342
|
+
auth_gates: hit.authGates,
|
|
32343
|
+
interprocedural: true,
|
|
32344
|
+
via_return: item.viaReturn
|
|
32345
|
+
});
|
|
32346
|
+
}
|
|
32347
|
+
for (const c of sum.paramCalls.get(item.param) ?? []) {
|
|
32348
|
+
const calleeId = resolveFuncId(c.callee, summaries);
|
|
32349
|
+
if (!calleeId)
|
|
32350
|
+
continue;
|
|
32351
|
+
const calleeParam = summaries.get(calleeId).params[c.argIndex];
|
|
32352
|
+
if (calleeParam)
|
|
32353
|
+
queue.push({ funcId: calleeId, param: calleeParam, source: item.source, sourceLine: item.sourceLine, viaReturn: false });
|
|
32354
|
+
}
|
|
32355
|
+
}
|
|
32356
|
+
return result;
|
|
32357
|
+
}
|
|
32358
|
+
|
|
32359
|
+
// src/differential.ts
|
|
32360
|
+
import { writeFileSync as writeFileSync2, rmSync, mkdtempSync, readFileSync as readFileSync13 } from "node:fs";
|
|
32361
|
+
import { join as join9 } from "node:path";
|
|
32362
|
+
import { tmpdir } from "node:os";
|
|
32363
|
+
import { execSync } from "node:child_process";
|
|
32364
|
+
|
|
31596
32365
|
// src/universal-taint.ts
|
|
31597
32366
|
function runTaintEngine(adapter, code, file) {
|
|
31598
32367
|
const result = { file, language: adapter.language, findings: [], suppressed: 0 };
|
|
@@ -31694,7 +32463,7 @@ function analyzeTaintUniversal(code, file) {
|
|
|
31694
32463
|
}
|
|
31695
32464
|
|
|
31696
32465
|
// src/adapters.ts
|
|
31697
|
-
var
|
|
32466
|
+
var import_php_parser4 = __toESM(require_src(), 1);
|
|
31698
32467
|
|
|
31699
32468
|
// node_modules/@babel/parser/lib/index.js
|
|
31700
32469
|
class Position {
|
|
@@ -49473,7 +50242,7 @@ var pythonHighlighting = styleTags({
|
|
|
49473
50242
|
", ;": tags.separator
|
|
49474
50243
|
});
|
|
49475
50244
|
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
|
|
50245
|
+
var parser4 = LRParser.deserialize({
|
|
49477
50246
|
version: 14,
|
|
49478
50247
|
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
50248
|
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 +50267,7 @@ var parser2 = LRParser.deserialize({
|
|
|
49498
50267
|
});
|
|
49499
50268
|
|
|
49500
50269
|
// src/adapters.ts
|
|
49501
|
-
var phpEngine = new
|
|
50270
|
+
var phpEngine = new import_php_parser4.Engine({ parser: { extractDoc: false, php7: true }, ast: { withPositions: true, withSource: false } });
|
|
49502
50271
|
var PHP_SOURCES = [
|
|
49503
50272
|
["_GET", "http_parameter"],
|
|
49504
50273
|
["_POST", "http_body"],
|
|
@@ -49646,8 +50415,13 @@ var JS_SINKS = [
|
|
|
49646
50415
|
[/document\.write/i, "html_render", "CWE-79"],
|
|
49647
50416
|
[/fs\.writeFile/i, "file_operations", "CWE-434"],
|
|
49648
50417
|
[/fs\.appendFile/i, "file_operations", "CWE-434"],
|
|
50418
|
+
[/fs\.readFile/i, "path_traversal", "CWE-22"],
|
|
50419
|
+
[/fs\.readFileSync/i, "path_traversal", "CWE-22"],
|
|
50420
|
+
[/fs\.unlink/i, "file_operations", "CWE-434"],
|
|
49649
50421
|
[/require\s*\(/i, "file_inclusion", "CWE-98"],
|
|
49650
50422
|
[/fetch\s*\(/i, "http_request", "CWE-918"],
|
|
50423
|
+
[/http\.get|https\.get|axios/i, "http_request", "CWE-918"],
|
|
50424
|
+
[/http\.request|https\.request/i, "http_request", "CWE-918"],
|
|
49651
50425
|
[/redirect\s*\(/i, "redirect", "CWE-601"],
|
|
49652
50426
|
[/res\.redirect/i, "redirect", "CWE-601"]
|
|
49653
50427
|
];
|
|
@@ -49734,6 +50508,8 @@ var jsAdapter = {
|
|
|
49734
50508
|
argVars.push(c.name);
|
|
49735
50509
|
for (const [re, kind, cwe] of JS_SINKS) {
|
|
49736
50510
|
if (re.test(fullCallText) || re.test(calleeText + "(")) {
|
|
50511
|
+
if (kind === "sql_execution" && /\?/.test(fullCallText) && /,\s*\[/.test(fullCallText))
|
|
50512
|
+
continue;
|
|
49737
50513
|
for (const v of argVars)
|
|
49738
50514
|
sinks.push({ variable: v, kind, line: n.loc?.start?.line ?? 0, cwe });
|
|
49739
50515
|
}
|
|
@@ -49772,6 +50548,8 @@ var PY_SINKS = [
|
|
|
49772
50548
|
[/\.save\s*\(/i, "file_operations", "CWE-434"],
|
|
49773
50549
|
[/render_template/i, "html_render", "CWE-79"],
|
|
49774
50550
|
[/redirect\s*\(/i, "redirect", "CWE-601"],
|
|
50551
|
+
[/requests\.(get|post|put|head|request)\s*\(/i, "http_request", "CWE-918"],
|
|
50552
|
+
[/urllib\.request/i, "http_request", "CWE-918"],
|
|
49775
50553
|
[/pickle\.loads\s*\(/i, "deserialization", "CWE-502"],
|
|
49776
50554
|
[/yaml\.load\s*\(/i, "deserialization", "CWE-502"],
|
|
49777
50555
|
[/os\.popen\s*\(/i, "command_execution", "CWE-78"],
|
|
@@ -49791,7 +50569,7 @@ var pythonAdapter = {
|
|
|
49791
50569
|
language: "python",
|
|
49792
50570
|
extensions: [".py", ".pyw"],
|
|
49793
50571
|
parse(code, _file) {
|
|
49794
|
-
const tree =
|
|
50572
|
+
const tree = parser4.parse(code);
|
|
49795
50573
|
const sources = [];
|
|
49796
50574
|
const sinks = [];
|
|
49797
50575
|
const sanitizers = [];
|
|
@@ -49856,9 +50634,14 @@ var pythonAdapter = {
|
|
|
49856
50634
|
if (m[1] !== callee.trim().split(".").pop())
|
|
49857
50635
|
argVars.push(m[1]);
|
|
49858
50636
|
for (const [re, kind, cwe] of PY_SINKS) {
|
|
49859
|
-
if (re.test(full))
|
|
49860
|
-
|
|
49861
|
-
|
|
50637
|
+
if (!re.test(full))
|
|
50638
|
+
continue;
|
|
50639
|
+
if (kind === "sql_execution" && /\?/.test(full) && /,\s*\(/.test(full))
|
|
50640
|
+
continue;
|
|
50641
|
+
if (kind === "command_execution" && /subprocess/.test(full) && /\[/.test(full))
|
|
50642
|
+
continue;
|
|
50643
|
+
for (const v of argVars)
|
|
50644
|
+
sinks.push({ variable: v, kind, line: lineOf(n), cwe });
|
|
49862
50645
|
}
|
|
49863
50646
|
for (const [re, kinds] of PY_SANITIZERS) {
|
|
49864
50647
|
if (re.test(full))
|
|
@@ -49894,6 +50677,10 @@ var JAVA_SINKS = [
|
|
|
49894
50677
|
[/readObject\s*\(/i, "deserialization", "CWE-502"],
|
|
49895
50678
|
[/new File\s*\(/i, "file_operations", "CWE-434"],
|
|
49896
50679
|
[/\.getInputStream\s*\(/i, "http_request", "CWE-918"],
|
|
50680
|
+
[/new URL\s*\(/i, "http_request", "CWE-918"],
|
|
50681
|
+
[/openConnection\s*\(/i, "http_request", "CWE-918"],
|
|
50682
|
+
[/DocumentBuilder|SAXParser|XMLReader/i, "xml_processing", "CWE-611"],
|
|
50683
|
+
[/\.parse\s*\(/i, "xml_processing", "CWE-611"],
|
|
49897
50684
|
[/sendRedirect\s*\(/i, "redirect", "CWE-601"],
|
|
49898
50685
|
[/\.forward\s*\(/i, "redirect", "CWE-601"]
|
|
49899
50686
|
];
|
|
@@ -49961,6 +50748,195 @@ registerLanguage(jsAdapter);
|
|
|
49961
50748
|
registerLanguage(pythonAdapter);
|
|
49962
50749
|
registerLanguage(javaAdapter);
|
|
49963
50750
|
|
|
50751
|
+
// src/differential.ts
|
|
50752
|
+
init_scanner();
|
|
50753
|
+
function runEngine(code, language, file) {
|
|
50754
|
+
if (language === "php") {
|
|
50755
|
+
const dir = mkdtempSync(join9(tmpdir(), "blitz-diff-"));
|
|
50756
|
+
const tmp = join9(dir, "old.php");
|
|
50757
|
+
writeFileSync2(tmp, code);
|
|
50758
|
+
try {
|
|
50759
|
+
const r = analyzeDataFlow2(tmp);
|
|
50760
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
|
|
50761
|
+
} finally {
|
|
50762
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50763
|
+
}
|
|
50764
|
+
}
|
|
50765
|
+
const extMap = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" };
|
|
50766
|
+
const probe = file.includes(".") ? file : `target${extMap[language] ?? ""}`;
|
|
50767
|
+
const r = analyzeTaintUniversal(code, probe);
|
|
50768
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.sink, source: f.source_kind, cwe: f.cwe }));
|
|
50769
|
+
}
|
|
50770
|
+
function diffLines(oldText, newText) {
|
|
50771
|
+
const a = oldText.split(`
|
|
50772
|
+
`);
|
|
50773
|
+
const b = newText.split(`
|
|
50774
|
+
`);
|
|
50775
|
+
const m = a.length;
|
|
50776
|
+
const n = b.length;
|
|
50777
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
50778
|
+
for (let i = m - 1;i >= 0; i--) {
|
|
50779
|
+
for (let j = n - 1;j >= 0; j--) {
|
|
50780
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
50781
|
+
}
|
|
50782
|
+
}
|
|
50783
|
+
const added = [];
|
|
50784
|
+
const removed = [];
|
|
50785
|
+
let i = 0;
|
|
50786
|
+
let j = 0;
|
|
50787
|
+
while (i < m && j < n) {
|
|
50788
|
+
if (a[i] === b[j]) {
|
|
50789
|
+
i++;
|
|
50790
|
+
j++;
|
|
50791
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
50792
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50793
|
+
i++;
|
|
50794
|
+
} else {
|
|
50795
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50796
|
+
j++;
|
|
50797
|
+
}
|
|
50798
|
+
}
|
|
50799
|
+
while (i < m) {
|
|
50800
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50801
|
+
i++;
|
|
50802
|
+
}
|
|
50803
|
+
while (j < n) {
|
|
50804
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50805
|
+
j++;
|
|
50806
|
+
}
|
|
50807
|
+
return { added, removed };
|
|
50808
|
+
}
|
|
50809
|
+
function analyzeDifferential(oldCode, newCode, language, file = "target") {
|
|
50810
|
+
const changes = [];
|
|
50811
|
+
const oldFindings = runEngine(oldCode, language, file);
|
|
50812
|
+
const newFindings = runEngine(newCode, language, file);
|
|
50813
|
+
const key = (f) => `${f.category}|${f.sink}|${f.sink_line}`;
|
|
50814
|
+
const oldKeys = new Set(oldFindings.map(key));
|
|
50815
|
+
const newKeys = new Set(newFindings.map(key));
|
|
50816
|
+
for (const f of newFindings) {
|
|
50817
|
+
if (!oldKeys.has(key(f))) {
|
|
50818
|
+
changes.push({
|
|
50819
|
+
kind: "new_vulnerability",
|
|
50820
|
+
severity: "high",
|
|
50821
|
+
sink: f.sink,
|
|
50822
|
+
category: f.category,
|
|
50823
|
+
source: f.source,
|
|
50824
|
+
cwe: f.cwe,
|
|
50825
|
+
line: f.sink_line,
|
|
50826
|
+
detail: `new ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50827
|
+
});
|
|
50828
|
+
}
|
|
50829
|
+
}
|
|
50830
|
+
for (const f of oldFindings) {
|
|
50831
|
+
if (!newKeys.has(key(f))) {
|
|
50832
|
+
changes.push({
|
|
50833
|
+
kind: "fixed_vulnerability",
|
|
50834
|
+
severity: "low",
|
|
50835
|
+
sink: f.sink,
|
|
50836
|
+
category: f.category,
|
|
50837
|
+
source: f.source,
|
|
50838
|
+
cwe: f.cwe,
|
|
50839
|
+
line: f.sink_line,
|
|
50840
|
+
detail: `removed ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50841
|
+
});
|
|
50842
|
+
}
|
|
50843
|
+
}
|
|
50844
|
+
const { added, removed } = diffLines(oldCode, newCode);
|
|
50845
|
+
for (const l of added) {
|
|
50846
|
+
const sink = classifySink2(l.text);
|
|
50847
|
+
if (sink) {
|
|
50848
|
+
changes.push({
|
|
50849
|
+
kind: "new_sink",
|
|
50850
|
+
severity: "medium",
|
|
50851
|
+
sink: l.text.trim(),
|
|
50852
|
+
category: sink.category,
|
|
50853
|
+
cwe: sink.cwe,
|
|
50854
|
+
line: l.line,
|
|
50855
|
+
detail: `added sink ${l.text.trim()} (${sink.category})`
|
|
50856
|
+
});
|
|
50857
|
+
}
|
|
50858
|
+
const source = classifySource(l.text);
|
|
50859
|
+
if (source) {
|
|
50860
|
+
changes.push({
|
|
50861
|
+
kind: "new_source",
|
|
50862
|
+
severity: source.attacker_controlled ? "medium" : "low",
|
|
50863
|
+
source: source.label,
|
|
50864
|
+
line: l.line,
|
|
50865
|
+
detail: `added source ${l.text.trim()} (${source.label})`
|
|
50866
|
+
});
|
|
50867
|
+
}
|
|
50868
|
+
}
|
|
50869
|
+
for (const l of removed) {
|
|
50870
|
+
const san = findSanitizers(l.text);
|
|
50871
|
+
if (san.length > 0) {
|
|
50872
|
+
changes.push({
|
|
50873
|
+
kind: "sanitizer_removed",
|
|
50874
|
+
severity: "high",
|
|
50875
|
+
line: l.line,
|
|
50876
|
+
detail: `removed sanitizer ${san.map((s) => s.label).join(", ")} (${l.text.trim()})`
|
|
50877
|
+
});
|
|
50878
|
+
}
|
|
50879
|
+
const gates = AUTH_GATES.filter((g) => l.text.includes(g));
|
|
50880
|
+
if (gates.length > 0) {
|
|
50881
|
+
changes.push({
|
|
50882
|
+
kind: "authorization_removed",
|
|
50883
|
+
severity: "high",
|
|
50884
|
+
line: l.line,
|
|
50885
|
+
detail: `removed authorization gate ${gates.join(", ")} (${l.text.trim()})`
|
|
50886
|
+
});
|
|
50887
|
+
}
|
|
50888
|
+
}
|
|
50889
|
+
const rank = { high: 0, medium: 1, low: 2 };
|
|
50890
|
+
changes.sort((x, y) => rank[x.severity] - rank[y.severity] || x.line - y.line);
|
|
50891
|
+
const count = (k) => changes.filter((c) => c.kind === k).length;
|
|
50892
|
+
return {
|
|
50893
|
+
language,
|
|
50894
|
+
changes,
|
|
50895
|
+
summary: {
|
|
50896
|
+
new_vulnerabilities: count("new_vulnerability"),
|
|
50897
|
+
fixed_vulnerabilities: count("fixed_vulnerability"),
|
|
50898
|
+
new_sinks: count("new_sink"),
|
|
50899
|
+
new_sources: count("new_source"),
|
|
50900
|
+
sanitizers_removed: count("sanitizer_removed"),
|
|
50901
|
+
authorizations_removed: count("authorization_removed")
|
|
50902
|
+
}
|
|
50903
|
+
};
|
|
50904
|
+
}
|
|
50905
|
+
function languageOf(file) {
|
|
50906
|
+
const adapter = detectLanguage(file);
|
|
50907
|
+
return adapter?.language ?? "php";
|
|
50908
|
+
}
|
|
50909
|
+
function analyzeDifferentialFiles(oldPath, newPath, language) {
|
|
50910
|
+
const oldCode = readFileSync13(oldPath, "utf8");
|
|
50911
|
+
const newCode = readFileSync13(newPath, "utf8");
|
|
50912
|
+
const lang = language ?? languageOf(newPath);
|
|
50913
|
+
return analyzeDifferential(oldCode, newCode, lang, newPath);
|
|
50914
|
+
}
|
|
50915
|
+
var SOURCE_EXTS = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
|
|
50916
|
+
function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
50917
|
+
const git = (args) => {
|
|
50918
|
+
try {
|
|
50919
|
+
return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
|
|
50920
|
+
} catch {
|
|
50921
|
+
return "";
|
|
50922
|
+
}
|
|
50923
|
+
};
|
|
50924
|
+
const changed = git(`diff --name-only ${base} ${head}`).split(`
|
|
50925
|
+
`).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
|
|
50926
|
+
const files = [];
|
|
50927
|
+
for (const file of changed) {
|
|
50928
|
+
const oldCode = git(`show ${base}:${file}`);
|
|
50929
|
+
const newCode = git(`show ${head}:${file}`);
|
|
50930
|
+
if (!newCode)
|
|
50931
|
+
continue;
|
|
50932
|
+
const lang = languageOf(file);
|
|
50933
|
+
files.push({ file, result: analyzeDifferential(oldCode || "", newCode, lang, file) });
|
|
50934
|
+
}
|
|
50935
|
+
const total_changes = files.reduce((n, f) => n + f.result.changes.length, 0);
|
|
50936
|
+
const security_sensitive = files.reduce((n, f) => n + f.result.changes.filter((c) => c.severity === "high").length, 0);
|
|
50937
|
+
return { repo, base, head, files, total_changes, security_sensitive };
|
|
50938
|
+
}
|
|
50939
|
+
|
|
49964
50940
|
// src/guidance.ts
|
|
49965
50941
|
var SINK_DIRECTIVES = {
|
|
49966
50942
|
sql_execution: "Validate the SQL injection live with strike_verify (use a unique MARKER and a NEGATIVE CONTROL that stays inert), or read_tool_manual for sqlmap. Only a reflected marker + inert control = confirmed.",
|
|
@@ -50774,16 +51750,16 @@ function reportJson(findings, opts = {}) {
|
|
|
50774
51750
|
}
|
|
50775
51751
|
|
|
50776
51752
|
// src/benchmark.ts
|
|
50777
|
-
import { readFileSync as
|
|
50778
|
-
import { join as
|
|
51753
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
|
|
51754
|
+
import { join as join10 } from "node:path";
|
|
50779
51755
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
50780
|
-
import { tmpdir } from "node:os";
|
|
50781
|
-
var ROOT4 =
|
|
50782
|
-
var CORPUS =
|
|
51756
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
51757
|
+
var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
51758
|
+
var CORPUS = join10(ROOT4, "bench", "corpus");
|
|
50783
51759
|
function parseCorpusFile(path) {
|
|
50784
51760
|
let text;
|
|
50785
51761
|
try {
|
|
50786
|
-
text =
|
|
51762
|
+
text = readFileSync14(path, "utf8");
|
|
50787
51763
|
} catch {
|
|
50788
51764
|
return null;
|
|
50789
51765
|
}
|
|
@@ -50833,7 +51809,7 @@ function loadCorpus() {
|
|
|
50833
51809
|
for (const f of readdirSync4(CORPUS)) {
|
|
50834
51810
|
if (!f.endsWith(".md"))
|
|
50835
51811
|
continue;
|
|
50836
|
-
const e = parseCorpusFile(
|
|
51812
|
+
const e = parseCorpusFile(join10(CORPUS, f));
|
|
50837
51813
|
if (e)
|
|
50838
51814
|
entries.push(e);
|
|
50839
51815
|
}
|
|
@@ -50845,10 +51821,10 @@ function loadCorpus() {
|
|
|
50845
51821
|
function detect(entry) {
|
|
50846
51822
|
const lang = entry.language;
|
|
50847
51823
|
if (lang === "php") {
|
|
50848
|
-
const tmp =
|
|
50849
|
-
|
|
50850
|
-
const r =
|
|
50851
|
-
|
|
51824
|
+
const tmp = join10(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
51825
|
+
writeFileSync3(tmp, entry.code);
|
|
51826
|
+
const r = analyzeDataFlow2(tmp);
|
|
51827
|
+
rmSync2(tmp, { force: true });
|
|
50852
51828
|
if (r.findings.length > 0)
|
|
50853
51829
|
return { detected: true, sinkKind: r.findings[0].sink };
|
|
50854
51830
|
return { detected: false };
|
|
@@ -51631,6 +52607,68 @@ function createServer() {
|
|
|
51631
52607
|
});
|
|
51632
52608
|
return { content: [{ type: "text", text: JSON.stringify({ ...r, next_steps: guidance }) }] };
|
|
51633
52609
|
});
|
|
52610
|
+
server.registerTool("eagle_eye2", {
|
|
52611
|
+
title: "Whole-program interprocedural taint (EAGLE-EYE 2.0)",
|
|
52612
|
+
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.",
|
|
52613
|
+
inputSchema: { path: string2().describe("Source file path (PHP)") }
|
|
52614
|
+
}, async ({ path }) => {
|
|
52615
|
+
const r = analyzeDataFlow2(path);
|
|
52616
|
+
const taintedSinks = [...new Set(r.findings.map((f) => f.sink))];
|
|
52617
|
+
const authGated = r.findings.filter((f) => f.auth_gated).length;
|
|
52618
|
+
const guidance = nextSteps({
|
|
52619
|
+
tier: "eagle-eye",
|
|
52620
|
+
hasFindings: r.findings.length > 0,
|
|
52621
|
+
sanitizedCount: r.suppressed,
|
|
52622
|
+
taintedSinks
|
|
52623
|
+
});
|
|
52624
|
+
return {
|
|
52625
|
+
content: [{
|
|
52626
|
+
type: "text",
|
|
52627
|
+
text: JSON.stringify({
|
|
52628
|
+
file: r.file,
|
|
52629
|
+
findings: r.findings.length,
|
|
52630
|
+
suppressed: r.suppressed,
|
|
52631
|
+
auth_gated: authGated,
|
|
52632
|
+
interprocedural: r.findings.filter((f) => f.interprocedural).length,
|
|
52633
|
+
functions: r.summary.functions,
|
|
52634
|
+
methods: r.summary.methods,
|
|
52635
|
+
call_edges: r.summary.edges,
|
|
52636
|
+
entry_points: r.summary.entry_points,
|
|
52637
|
+
unreachable: r.summary.unreachable,
|
|
52638
|
+
results: r.findings,
|
|
52639
|
+
next_steps: guidance
|
|
52640
|
+
})
|
|
52641
|
+
}]
|
|
52642
|
+
};
|
|
52643
|
+
});
|
|
52644
|
+
server.registerTool("diff_analyze", {
|
|
52645
|
+
title: "Differential security analysis",
|
|
52646
|
+
description: "PHASE 4: compare two versions of code (files or a git commit range) and surface only security-sensitive changes — new vulnerabilities, fixed vulnerabilities, new sinks/sources, removed sanitizers, and removed authorization gates.",
|
|
52647
|
+
inputSchema: {
|
|
52648
|
+
old_path: string2().optional().describe("Old file path"),
|
|
52649
|
+
new_path: string2().optional().describe("New file path"),
|
|
52650
|
+
old_code: string2().optional().describe("Old source code (when not using paths)"),
|
|
52651
|
+
new_code: string2().optional().describe("New source code (when not using paths)"),
|
|
52652
|
+
language: string2().optional().describe("Language (php/javascript/python/java); auto-detected from path if omitted"),
|
|
52653
|
+
repo: string2().optional().describe("Git repo path (for git-diff mode)"),
|
|
52654
|
+
base: string2().optional().describe("Git base ref (default HEAD~1)"),
|
|
52655
|
+
head: string2().optional().describe("Git head ref (default HEAD)")
|
|
52656
|
+
}
|
|
52657
|
+
}, async ({ old_path, new_path, old_code, new_code, language, repo, base, head }) => {
|
|
52658
|
+
if (repo) {
|
|
52659
|
+
const r = analyzeGitDiff(repo, base ?? "HEAD~1", head ?? "HEAD");
|
|
52660
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }] };
|
|
52661
|
+
}
|
|
52662
|
+
let result;
|
|
52663
|
+
if (old_path && new_path) {
|
|
52664
|
+
result = analyzeDifferentialFiles(old_path, new_path, language);
|
|
52665
|
+
} else if (old_code !== undefined && new_code !== undefined) {
|
|
52666
|
+
result = analyzeDifferential(old_code, new_code, language ?? "php", new_path ?? "target");
|
|
52667
|
+
} else {
|
|
52668
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "provide (old_path+new_path), (old_code+new_code), or (repo)" }) }] };
|
|
52669
|
+
}
|
|
52670
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
52671
|
+
});
|
|
51634
52672
|
server.registerTool("taint_tree", {
|
|
51635
52673
|
title: "Whole-tree taint scan",
|
|
51636
52674
|
description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
|
|
@@ -51784,12 +52822,12 @@ async function serve() {
|
|
|
51784
52822
|
}
|
|
51785
52823
|
|
|
51786
52824
|
// src/cli.ts
|
|
51787
|
-
import { readFileSync as
|
|
51788
|
-
import { join as
|
|
52825
|
+
import { readFileSync as readFileSync16, existsSync as existsSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
52826
|
+
import { join as join12, dirname as dirname2 } from "node:path";
|
|
51789
52827
|
import { homedir as homedir5 } from "node:os";
|
|
51790
52828
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
51791
52829
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
51792
|
-
var ROOT6 =
|
|
52830
|
+
var ROOT6 = join12(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
51793
52831
|
function which(cmd) {
|
|
51794
52832
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
51795
52833
|
}
|
|
@@ -51824,7 +52862,7 @@ function runDoctor() {
|
|
|
51824
52862
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
51825
52863
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
51826
52864
|
});
|
|
51827
|
-
const dataOk = existsSync6(
|
|
52865
|
+
const dataOk = existsSync6(join12(ROOT6, "chains.json")) && existsSync6(join12(ROOT6, "tools-catalog.json"));
|
|
51828
52866
|
issues.push({
|
|
51829
52867
|
name: "Data layers (chains + tools-catalog)",
|
|
51830
52868
|
status: dataOk ? "ok" : "fail",
|
|
@@ -51852,21 +52890,21 @@ BlitzStrike — ${verdict}`);
|
|
|
51852
52890
|
function resolveCommand() {
|
|
51853
52891
|
if (which("blitzstrike"))
|
|
51854
52892
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
51855
|
-
const src =
|
|
52893
|
+
const src = join12(ROOT6, "src", "index.ts");
|
|
51856
52894
|
if (which("bun") && existsSync6(src))
|
|
51857
52895
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
51858
52896
|
if (which("npx"))
|
|
51859
52897
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51860
52898
|
if (which("bunx"))
|
|
51861
52899
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
51862
|
-
const dist =
|
|
52900
|
+
const dist = join12(ROOT6, "dist", "index.js");
|
|
51863
52901
|
if (existsSync6(dist))
|
|
51864
52902
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
51865
52903
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51866
52904
|
}
|
|
51867
52905
|
function readJson(p) {
|
|
51868
52906
|
try {
|
|
51869
|
-
return JSON.parse(
|
|
52907
|
+
return JSON.parse(readFileSync16(p, "utf8"));
|
|
51870
52908
|
} catch {
|
|
51871
52909
|
return null;
|
|
51872
52910
|
}
|
|
@@ -51881,7 +52919,7 @@ function jsonMcpServersWrite(p) {
|
|
|
51881
52919
|
...existing.mcpServers ?? {},
|
|
51882
52920
|
blitzstrike: { command, args }
|
|
51883
52921
|
};
|
|
51884
|
-
|
|
52922
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
51885
52923
|
};
|
|
51886
52924
|
}
|
|
51887
52925
|
function opencodeWrite(p) {
|
|
@@ -51894,22 +52932,22 @@ function opencodeWrite(p) {
|
|
|
51894
52932
|
...existing.mcp ?? {},
|
|
51895
52933
|
blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
|
|
51896
52934
|
};
|
|
51897
|
-
|
|
52935
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
51898
52936
|
copyOpenCodeAgents();
|
|
51899
52937
|
};
|
|
51900
52938
|
}
|
|
51901
52939
|
function copyOpenCodeAgents() {
|
|
51902
|
-
const srcDir =
|
|
51903
|
-
const dstDir =
|
|
52940
|
+
const srcDir = join12(ROOT6, "opencode-agents");
|
|
52941
|
+
const dstDir = join12(homedir5(), ".config", "opencode", "agents");
|
|
51904
52942
|
if (!existsSync6(srcDir))
|
|
51905
52943
|
return;
|
|
51906
52944
|
if (!existsSync6(dstDir))
|
|
51907
52945
|
mkdirSync4(dstDir, { recursive: true });
|
|
51908
52946
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
51909
|
-
const src =
|
|
51910
|
-
const dst =
|
|
52947
|
+
const src = join12(srcDir, f);
|
|
52948
|
+
const dst = join12(dstDir, f);
|
|
51911
52949
|
if (existsSync6(src)) {
|
|
51912
|
-
|
|
52950
|
+
writeFileSync5(dst, readFileSync16(src, "utf8"));
|
|
51913
52951
|
}
|
|
51914
52952
|
}
|
|
51915
52953
|
}
|
|
@@ -51918,7 +52956,7 @@ function codexWrite(p) {
|
|
|
51918
52956
|
const dir = dirname2(p);
|
|
51919
52957
|
if (!existsSync6(dir))
|
|
51920
52958
|
mkdirSync4(dir, { recursive: true });
|
|
51921
|
-
let existing = existsSync6(p) ?
|
|
52959
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
51922
52960
|
if (!existing.trimEnd().endsWith(`
|
|
51923
52961
|
`))
|
|
51924
52962
|
existing += `
|
|
@@ -51929,7 +52967,7 @@ function codexWrite(p) {
|
|
|
51929
52967
|
command = "${command}"
|
|
51930
52968
|
args = ${JSON.stringify(args)}
|
|
51931
52969
|
`;
|
|
51932
|
-
|
|
52970
|
+
writeFileSync5(p, existing);
|
|
51933
52971
|
};
|
|
51934
52972
|
}
|
|
51935
52973
|
function hermesWrite(p) {
|
|
@@ -51937,7 +52975,7 @@ function hermesWrite(p) {
|
|
|
51937
52975
|
const dir = dirname2(p);
|
|
51938
52976
|
if (!existsSync6(dir))
|
|
51939
52977
|
mkdirSync4(dir, { recursive: true });
|
|
51940
|
-
let existing = existsSync6(p) ?
|
|
52978
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
51941
52979
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
51942
52980
|
if (!existing.trimEnd().endsWith(`
|
|
51943
52981
|
`))
|
|
@@ -51958,80 +52996,80 @@ mcp_servers:
|
|
|
51958
52996
|
args: ${JSON.stringify(args)}
|
|
51959
52997
|
`;
|
|
51960
52998
|
}
|
|
51961
|
-
|
|
52999
|
+
writeFileSync5(p, existing);
|
|
51962
53000
|
};
|
|
51963
53001
|
}
|
|
51964
53002
|
function detectAgents() {
|
|
51965
53003
|
const home = homedir5();
|
|
51966
53004
|
const agents = [];
|
|
51967
|
-
const claudeJson =
|
|
53005
|
+
const claudeJson = join12(home, ".claude.json");
|
|
51968
53006
|
agents.push({
|
|
51969
53007
|
name: "Claude Code",
|
|
51970
53008
|
detectPath: claudeJson,
|
|
51971
53009
|
installed: () => existsSync6(claudeJson) || which("claude"),
|
|
51972
53010
|
write: jsonMcpServersWrite(claudeJson)
|
|
51973
53011
|
});
|
|
51974
|
-
const claudeDesktop =
|
|
53012
|
+
const claudeDesktop = join12(home, ".config", "Claude", "claude_desktop_config.json");
|
|
51975
53013
|
agents.push({
|
|
51976
53014
|
name: "Claude Desktop",
|
|
51977
53015
|
detectPath: claudeDesktop,
|
|
51978
53016
|
installed: () => existsSync6(claudeDesktop),
|
|
51979
53017
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
51980
53018
|
});
|
|
51981
|
-
const cursor =
|
|
53019
|
+
const cursor = join12(home, ".cursor", "mcp.json");
|
|
51982
53020
|
agents.push({
|
|
51983
53021
|
name: "Cursor",
|
|
51984
53022
|
detectPath: cursor,
|
|
51985
|
-
installed: () => existsSync6(
|
|
53023
|
+
installed: () => existsSync6(join12(home, ".cursor")) || which("cursor"),
|
|
51986
53024
|
write: jsonMcpServersWrite(cursor)
|
|
51987
53025
|
});
|
|
51988
|
-
const opencode =
|
|
53026
|
+
const opencode = join12(home, ".config", "opencode", "opencode.jsonc");
|
|
51989
53027
|
agents.push({
|
|
51990
53028
|
name: "OpenCode",
|
|
51991
53029
|
detectPath: opencode,
|
|
51992
|
-
installed: () => existsSync6(
|
|
53030
|
+
installed: () => existsSync6(join12(home, ".config", "opencode")) || which("opencode"),
|
|
51993
53031
|
write: opencodeWrite(opencode)
|
|
51994
53032
|
});
|
|
51995
|
-
const codex =
|
|
53033
|
+
const codex = join12(home, ".codex", "config.toml");
|
|
51996
53034
|
agents.push({
|
|
51997
53035
|
name: "Codex",
|
|
51998
53036
|
detectPath: codex,
|
|
51999
|
-
installed: () => existsSync6(
|
|
53037
|
+
installed: () => existsSync6(join12(home, ".codex")) || which("codex"),
|
|
52000
53038
|
write: codexWrite(codex)
|
|
52001
53039
|
});
|
|
52002
|
-
const hermes =
|
|
53040
|
+
const hermes = join12(home, ".hermes", "config.yaml");
|
|
52003
53041
|
agents.push({
|
|
52004
53042
|
name: "Hermes",
|
|
52005
53043
|
detectPath: hermes,
|
|
52006
|
-
installed: () => existsSync6(
|
|
53044
|
+
installed: () => existsSync6(join12(home, ".hermes")) || which("hermes"),
|
|
52007
53045
|
write: hermesWrite(hermes)
|
|
52008
53046
|
});
|
|
52009
|
-
const gemini =
|
|
53047
|
+
const gemini = join12(home, ".gemini", "settings.json");
|
|
52010
53048
|
agents.push({
|
|
52011
53049
|
name: "Gemini CLI",
|
|
52012
53050
|
detectPath: gemini,
|
|
52013
|
-
installed: () => existsSync6(
|
|
53051
|
+
installed: () => existsSync6(join12(home, ".gemini")) || which("gemini"),
|
|
52014
53052
|
write: jsonMcpServersWrite(gemini)
|
|
52015
53053
|
});
|
|
52016
|
-
const windsurf =
|
|
53054
|
+
const windsurf = join12(home, ".codeium", "windsurf", "mcp_config.json");
|
|
52017
53055
|
agents.push({
|
|
52018
53056
|
name: "Windsurf",
|
|
52019
53057
|
detectPath: windsurf,
|
|
52020
|
-
installed: () => existsSync6(
|
|
53058
|
+
installed: () => existsSync6(join12(home, ".codeium")) || which("windsurf"),
|
|
52021
53059
|
write: jsonMcpServersWrite(windsurf)
|
|
52022
53060
|
});
|
|
52023
|
-
const copilot =
|
|
53061
|
+
const copilot = join12(home, ".copilot", "mcp.json");
|
|
52024
53062
|
agents.push({
|
|
52025
53063
|
name: "Copilot",
|
|
52026
53064
|
detectPath: copilot,
|
|
52027
|
-
installed: () => existsSync6(
|
|
53065
|
+
installed: () => existsSync6(join12(home, ".copilot")),
|
|
52028
53066
|
write: jsonMcpServersWrite(copilot)
|
|
52029
53067
|
});
|
|
52030
|
-
const cline =
|
|
53068
|
+
const cline = join12(home, ".cline", "mcp_settings.json");
|
|
52031
53069
|
agents.push({
|
|
52032
53070
|
name: "Cline",
|
|
52033
53071
|
detectPath: cline,
|
|
52034
|
-
installed: () => existsSync6(
|
|
53072
|
+
installed: () => existsSync6(join12(home, ".cline")),
|
|
52035
53073
|
write: jsonMcpServersWrite(cline)
|
|
52036
53074
|
});
|
|
52037
53075
|
return agents;
|
|
@@ -52075,15 +53113,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
52075
53113
|
}
|
|
52076
53114
|
|
|
52077
53115
|
// src/index.ts
|
|
52078
|
-
import { readFileSync as
|
|
52079
|
-
import { join as
|
|
53116
|
+
import { readFileSync as readFileSync17, existsSync as existsSync7 } from "node:fs";
|
|
53117
|
+
import { join as join13 } from "node:path";
|
|
52080
53118
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
52081
|
-
var ROOT7 =
|
|
53119
|
+
var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
52082
53120
|
function readVersion() {
|
|
52083
53121
|
try {
|
|
52084
|
-
const p =
|
|
53122
|
+
const p = join13(ROOT7, "package.json");
|
|
52085
53123
|
if (existsSync7(p))
|
|
52086
|
-
return JSON.parse(
|
|
53124
|
+
return JSON.parse(readFileSync17(p, "utf8")).version ?? "1.0.0";
|
|
52087
53125
|
} catch {}
|
|
52088
53126
|
return "1.0.0";
|
|
52089
53127
|
}
|