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