@patchstack/connect 0.5.15 → 0.5.16
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/AGENT-INSTALL.md +37 -11
- package/dist/cli.js +654 -174
- package/dist/cli.js.map +1 -1
- package/dist/protect/templates/demo-rules.json +2 -2
- package/dist/protect.cjs +1384 -747
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +110 -16
- package/dist/protect.d.ts +110 -16
- package/dist/protect.edge.js +1372 -735
- package/dist/protect.edge.js.map +3 -3
- package/dist/protect.js +1372 -735
- package/dist/protect.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3119,6 +3119,16 @@ function rootIdentifier(node, ts) {
|
|
|
3119
3119
|
}
|
|
3120
3120
|
return void 0;
|
|
3121
3121
|
}
|
|
3122
|
+
function rootIdentifierNode(node, ts) {
|
|
3123
|
+
let cur = node;
|
|
3124
|
+
while (cur) {
|
|
3125
|
+
if (ts.isIdentifier(cur)) return cur;
|
|
3126
|
+
if (ts.isPropertyAccessExpression(cur) || ts.isElementAccessExpression(cur) || ts.isCallExpression(cur) || ts.isNewExpression(cur) || ts.isNonNullExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isAwaitExpression(cur)) {
|
|
3127
|
+
cur = cur.expression;
|
|
3128
|
+
} else return void 0;
|
|
3129
|
+
}
|
|
3130
|
+
return void 0;
|
|
3131
|
+
}
|
|
3122
3132
|
function spanOf(node) {
|
|
3123
3133
|
const out = { line: lineOf(node) };
|
|
3124
3134
|
try {
|
|
@@ -3223,6 +3233,15 @@ function opCallOf(propAccess, ts) {
|
|
|
3223
3233
|
const p = propAccess?.parent;
|
|
3224
3234
|
return p && ts.isCallExpression(p) && p.expression === propAccess ? p : propAccess;
|
|
3225
3235
|
}
|
|
3236
|
+
function localCallIdentifiers(node, ts) {
|
|
3237
|
+
const out = [];
|
|
3238
|
+
const visit = (n) => {
|
|
3239
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) out.push(n.expression);
|
|
3240
|
+
ts.forEachChild(n, visit);
|
|
3241
|
+
};
|
|
3242
|
+
visit(node);
|
|
3243
|
+
return out;
|
|
3244
|
+
}
|
|
3226
3245
|
function localCalls(node, ts) {
|
|
3227
3246
|
const names = [];
|
|
3228
3247
|
const visit = (n) => {
|
|
@@ -3259,8 +3278,9 @@ function routeFromFilePath(relFile) {
|
|
|
3259
3278
|
segs = dirs.slice(at("routes") + 1);
|
|
3260
3279
|
} else if (at("pages") !== -1) {
|
|
3261
3280
|
segs = [...dirs.slice(at("pages") + 1), ...base === "index" ? [] : [base]];
|
|
3262
|
-
} else if (
|
|
3263
|
-
|
|
3281
|
+
} else if (nuxtRoot(dirs) !== void 0) {
|
|
3282
|
+
const root = nuxtRoot(dirs);
|
|
3283
|
+
segs = [...root, ...base === "index" ? [] : [base.replace(/\.(get|post|put|patch|delete|head|options)$/i, "")]];
|
|
3264
3284
|
}
|
|
3265
3285
|
if (!segs) return {};
|
|
3266
3286
|
segs = segs.filter((s) => !(s.startsWith("(") && s.endsWith(")")) && !s.startsWith("@") && !s.startsWith("_"));
|
|
@@ -3276,6 +3296,21 @@ function routeFromFilePath(relFile) {
|
|
|
3276
3296
|
const route = "/" + mapped.join("/");
|
|
3277
3297
|
return { route: route.length > 1 ? route.replace(/\/+$/, "") : "/", dynamic };
|
|
3278
3298
|
}
|
|
3299
|
+
function isPagesApiFile(relFile) {
|
|
3300
|
+
const dirs = relFile.split(/[\\/]/).filter(Boolean).slice(0, -1);
|
|
3301
|
+
const i = dirs.lastIndexOf("pages");
|
|
3302
|
+
return i !== -1 && dirs[i + 1] === "api";
|
|
3303
|
+
}
|
|
3304
|
+
function nuxtRoot(dirs) {
|
|
3305
|
+
const i = dirs.lastIndexOf("server");
|
|
3306
|
+
if (i === -1) return void 0;
|
|
3307
|
+
if (dirs[i + 1] === "api") return dirs.slice(i + 1);
|
|
3308
|
+
if (dirs[i + 1] === "routes") return dirs.slice(i + 2);
|
|
3309
|
+
return void 0;
|
|
3310
|
+
}
|
|
3311
|
+
function isRoutePath(route) {
|
|
3312
|
+
return route.startsWith("/") || route === "*";
|
|
3313
|
+
}
|
|
3279
3314
|
function routeFromChain(expr, ts) {
|
|
3280
3315
|
let cur = expr;
|
|
3281
3316
|
while (cur && ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) {
|
|
@@ -3336,7 +3371,35 @@ function detectFramework(cwd) {
|
|
|
3336
3371
|
}
|
|
3337
3372
|
return "unknown";
|
|
3338
3373
|
}
|
|
3339
|
-
var isSourceFile = (name) => /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(name) && !name.endsWith(".d.ts");
|
|
3374
|
+
var isSourceFile = (name) => /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(name) && !name.endsWith(".d.ts") || isComponentFile(name);
|
|
3375
|
+
var isComponentFile = (name) => /\.(vue|svelte|astro)$/.test(name);
|
|
3376
|
+
function componentScript(text, file) {
|
|
3377
|
+
const keep = [];
|
|
3378
|
+
if (file.endsWith(".astro")) {
|
|
3379
|
+
const open2 = /^?\s*---[^\S\r\n]*\r?\n/.exec(text);
|
|
3380
|
+
if (open2) {
|
|
3381
|
+
const start = open2[0].length;
|
|
3382
|
+
const close = /\r?\n---[^\S\r\n]*(\r?\n|$)/g;
|
|
3383
|
+
close.lastIndex = start - 1;
|
|
3384
|
+
const end = close.exec(text);
|
|
3385
|
+
if (!end) return null;
|
|
3386
|
+
if (end.index > start) keep.push([start, end.index]);
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
const opening = /<script\b[^>]*>/gi;
|
|
3390
|
+
for (let m = opening.exec(text); m; m = opening.exec(text)) {
|
|
3391
|
+
const start = m.index + m[0].length;
|
|
3392
|
+
const closing = /<\/script\s*>/gi;
|
|
3393
|
+
closing.lastIndex = start;
|
|
3394
|
+
const end = closing.exec(text);
|
|
3395
|
+
if (!end) return null;
|
|
3396
|
+
keep.push([start, end.index]);
|
|
3397
|
+
opening.lastIndex = end.index + end[0].length;
|
|
3398
|
+
}
|
|
3399
|
+
let out = text.replace(/[^\r\n]/g, " ");
|
|
3400
|
+
for (const [start, end] of keep) out = out.slice(0, start) + text.slice(start, end) + out.slice(end);
|
|
3401
|
+
return out;
|
|
3402
|
+
}
|
|
3340
3403
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3341
3404
|
"node_modules",
|
|
3342
3405
|
"dist",
|
|
@@ -7017,6 +7080,97 @@ function npmPackageOf(spec) {
|
|
|
7017
7080
|
|
|
7018
7081
|
// src/map/sinks.ts
|
|
7019
7082
|
import { createHash as createHash4 } from "crypto";
|
|
7083
|
+
|
|
7084
|
+
// src/map/scope.ts
|
|
7085
|
+
var scopeCache = /* @__PURE__ */ new WeakMap();
|
|
7086
|
+
function declarationOf(id, ts) {
|
|
7087
|
+
if (!id || !ts.isIdentifier(id)) return void 0;
|
|
7088
|
+
const name = id.text;
|
|
7089
|
+
for (let cur = id.parent; cur; cur = cur.parent) {
|
|
7090
|
+
const declared = declaredIn(cur, ts);
|
|
7091
|
+
if (declared?.has(name)) return declared.get(name);
|
|
7092
|
+
}
|
|
7093
|
+
return void 0;
|
|
7094
|
+
}
|
|
7095
|
+
function isGlobal(node, name, ts) {
|
|
7096
|
+
if (ts.isIdentifier(node)) return node.text === name && declarationOf(node, ts) === void 0;
|
|
7097
|
+
return ts.isPropertyAccessExpression(node) && node.name.text === name && ts.isIdentifier(node.expression) && node.expression.text === "globalThis" && declarationOf(node.expression, ts) === void 0;
|
|
7098
|
+
}
|
|
7099
|
+
function boundIdentifiers(name, ts) {
|
|
7100
|
+
if (!name) return [];
|
|
7101
|
+
if (ts.isIdentifier(name)) return [name];
|
|
7102
|
+
if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
7103
|
+
return name.elements.flatMap((el) => ts.isBindingElement(el) ? boundIdentifiers(el.name, ts) : []);
|
|
7104
|
+
}
|
|
7105
|
+
return [];
|
|
7106
|
+
}
|
|
7107
|
+
function isFunctionScope(n, ts) {
|
|
7108
|
+
return ts.isFunctionDeclaration(n) || ts.isFunctionExpression(n) || ts.isArrowFunction(n) || ts.isMethodDeclaration(n) || ts.isConstructorDeclaration(n) || ts.isGetAccessorDeclaration(n) || ts.isSetAccessorDeclaration(n) || ts.isSourceFile(n);
|
|
7109
|
+
}
|
|
7110
|
+
function declaredIn(n, ts) {
|
|
7111
|
+
const cached2 = scopeCache.get(n);
|
|
7112
|
+
if (cached2) return cached2;
|
|
7113
|
+
const out = /* @__PURE__ */ new Map();
|
|
7114
|
+
const add = (ids) => {
|
|
7115
|
+
for (const i of ids) if (!out.has(i.text)) out.set(i.text, i);
|
|
7116
|
+
};
|
|
7117
|
+
if (isFunctionScope(n, ts)) {
|
|
7118
|
+
if ((ts.isFunctionExpression(n) || ts.isClassExpression?.(n)) && n.name) add([n.name]);
|
|
7119
|
+
for (const p of n.parameters ?? []) add(boundIdentifiers(p.name, ts));
|
|
7120
|
+
collectVarScoped(ts.isSourceFile(n) ? n : n.body, ts, add);
|
|
7121
|
+
if (ts.isSourceFile(n)) collectLexical(n.statements, ts, add, true);
|
|
7122
|
+
} else if (ts.isBlock(n) || ts.isModuleBlock(n) || ts.isCaseBlock(n)) {
|
|
7123
|
+
const statements = ts.isCaseBlock(n) ? n.clauses.flatMap((c) => [...c.statements]) : n.statements;
|
|
7124
|
+
collectLexical(statements, ts, add, false);
|
|
7125
|
+
} else if (ts.isForStatement(n) || ts.isForInStatement(n) || ts.isForOfStatement(n)) {
|
|
7126
|
+
const init = n.initializer;
|
|
7127
|
+
if (init && ts.isVariableDeclarationList(init) && isLexicalList(init, ts)) {
|
|
7128
|
+
for (const d of init.declarations) add(boundIdentifiers(d.name, ts));
|
|
7129
|
+
}
|
|
7130
|
+
} else if (ts.isCatchClause(n)) {
|
|
7131
|
+
if (n.variableDeclaration) add(boundIdentifiers(n.variableDeclaration.name, ts));
|
|
7132
|
+
} else if (ts.isClassExpression?.(n) && n.name) {
|
|
7133
|
+
add([n.name]);
|
|
7134
|
+
} else {
|
|
7135
|
+
return void 0;
|
|
7136
|
+
}
|
|
7137
|
+
scopeCache.set(n, out);
|
|
7138
|
+
return out;
|
|
7139
|
+
}
|
|
7140
|
+
function isLexicalList(list, ts) {
|
|
7141
|
+
return (list.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
7142
|
+
}
|
|
7143
|
+
function collectLexical(statements, ts, add, topLevel) {
|
|
7144
|
+
for (const s of statements ?? []) {
|
|
7145
|
+
if (ts.isVariableStatement(s) && isLexicalList(s.declarationList, ts)) {
|
|
7146
|
+
for (const d of s.declarationList.declarations) add(boundIdentifiers(d.name, ts));
|
|
7147
|
+
} else if ((ts.isClassDeclaration(s) || ts.isFunctionDeclaration(s) || ts.isEnumDeclaration(s)) && s.name) {
|
|
7148
|
+
add([s.name]);
|
|
7149
|
+
} else if (topLevel && ts.isImportDeclaration(s) && s.importClause) {
|
|
7150
|
+
const clause = s.importClause;
|
|
7151
|
+
if (clause.name) add([clause.name]);
|
|
7152
|
+
const bindings = clause.namedBindings;
|
|
7153
|
+
if (bindings && ts.isNamespaceImport(bindings)) add([bindings.name]);
|
|
7154
|
+
if (bindings && ts.isNamedImports(bindings)) add(bindings.elements.map((e) => e.name));
|
|
7155
|
+
} else if (topLevel && ts.isImportEqualsDeclaration(s)) {
|
|
7156
|
+
add([s.name]);
|
|
7157
|
+
}
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
function collectVarScoped(node, ts, add) {
|
|
7161
|
+
const visit = (n) => {
|
|
7162
|
+
if (!n) return;
|
|
7163
|
+
if (ts.isVariableDeclarationList(n) && !isLexicalList(n, ts)) {
|
|
7164
|
+
for (const d of n.declarations) add(boundIdentifiers(d.name, ts));
|
|
7165
|
+
}
|
|
7166
|
+
if (n !== node && isFunctionScope(n, ts)) return;
|
|
7167
|
+
if (ts.isClassDeclaration(n) || ts.isClassExpression?.(n)) return;
|
|
7168
|
+
ts.forEachChild(n, visit);
|
|
7169
|
+
};
|
|
7170
|
+
visit(node);
|
|
7171
|
+
}
|
|
7172
|
+
|
|
7173
|
+
// src/map/sinks.ts
|
|
7020
7174
|
var DB_OPS = /* @__PURE__ */ new Set(["insert", "update", "delete", "select", "upsert", "rpc"]);
|
|
7021
7175
|
var PRISMA_OPS = /* @__PURE__ */ new Set(["create", "createMany", "update", "updateMany", "delete", "deleteMany", "upsert", "findFirst", "findUnique", "findMany", "count", "aggregate"]);
|
|
7022
7176
|
var FS_CALLS = /^(readFile|writeFile|readFileSync|writeFileSync|appendFile|createReadStream|createWriteStream|unlink|rm|rmSync|mkdir|readdir|stat|open)$/;
|
|
@@ -7059,29 +7213,39 @@ function recognizedSinkKinds(pkg) {
|
|
|
7059
7213
|
return kinds;
|
|
7060
7214
|
}
|
|
7061
7215
|
function collectLocalSinks(sf, ts, bindings, ctx) {
|
|
7062
|
-
const
|
|
7063
|
-
const
|
|
7064
|
-
|
|
7216
|
+
const byDeclaration = /* @__PURE__ */ new Map();
|
|
7217
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7218
|
+
const record = (name, body, topLevel) => {
|
|
7219
|
+
const sinks = directSinks(body, ts, bindings, ctx);
|
|
7220
|
+
byDeclaration.set(name, sinks);
|
|
7221
|
+
if (topLevel || !byName.has(name.text)) byName.set(name.text, sinks);
|
|
7222
|
+
};
|
|
7223
|
+
const visit = (node, depth) => {
|
|
7224
|
+
const topLevel = depth === 0;
|
|
7225
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.body) record(node.name, node.body, topLevel);
|
|
7065
7226
|
else if (ts.isVariableStatement(node)) {
|
|
7066
7227
|
for (const decl of node.declarationList.declarations) {
|
|
7067
7228
|
if (ts.isIdentifier(decl.name) && decl.initializer && isFnLike(decl.initializer, ts)) {
|
|
7068
|
-
|
|
7229
|
+
record(decl.name, decl.initializer.body, topLevel);
|
|
7069
7230
|
}
|
|
7070
7231
|
}
|
|
7071
7232
|
}
|
|
7072
|
-
ts.forEachChild(node, visit);
|
|
7233
|
+
ts.forEachChild(node, (child) => visit(child, depth + (ts.isSourceFile(node) ? 0 : 1)));
|
|
7073
7234
|
};
|
|
7074
|
-
visit(sf);
|
|
7075
|
-
return
|
|
7235
|
+
visit(sf, 0);
|
|
7236
|
+
return { byDeclaration, byName };
|
|
7076
7237
|
}
|
|
7077
7238
|
function sinksFrom(arrowOrNode, ts, localSinks, bindings, ctx) {
|
|
7078
7239
|
if (!arrowOrNode) return [];
|
|
7079
7240
|
const body = arrowOrNode.isSyntheticBody ? arrowOrNode.body : isFnLike(arrowOrNode, ts) ? arrowOrNode.body : arrowOrNode;
|
|
7080
7241
|
if (!body) return [];
|
|
7081
7242
|
const sinks = directSinks(body, ts, bindings, ctx);
|
|
7082
|
-
for (const
|
|
7083
|
-
|
|
7084
|
-
|
|
7243
|
+
for (const callee of localCallIdentifiers(body, ts)) {
|
|
7244
|
+
const called = callee.text;
|
|
7245
|
+
const declaration = declarationOf(callee, ts);
|
|
7246
|
+
const helper = declaration === void 0 ? void 0 : localSinks.byDeclaration.get(declaration);
|
|
7247
|
+
for (const s of helper ?? []) sinks.push(s);
|
|
7248
|
+
if (ctx && (declaration === void 0 || isModuleLevel(declaration, ts))) {
|
|
7085
7249
|
const spec = bindings.resolve(called);
|
|
7086
7250
|
if (spec && spec.startsWith(".")) {
|
|
7087
7251
|
for (const s of ctx.graph.importedSinks(ctx.file, spec, bindings.exportNameOf(called) ?? called)) sinks.push(s);
|
|
@@ -7098,6 +7262,22 @@ function sinksFrom(arrowOrNode, ts, localSinks, bindings, ctx) {
|
|
|
7098
7262
|
}
|
|
7099
7263
|
return dedupeSinks(sinks, ctx?.owner ?? "");
|
|
7100
7264
|
}
|
|
7265
|
+
function isContextRequest(node, ts) {
|
|
7266
|
+
if (!ts.isPropertyAccessExpression(node) || node.name.text !== "req" || !ts.isIdentifier(node.expression)) return false;
|
|
7267
|
+
const declaration = declarationOf(node.expression, ts);
|
|
7268
|
+
for (let cur = declaration?.parent; cur; cur = cur.parent) {
|
|
7269
|
+
if (ts.isParameter(cur)) return true;
|
|
7270
|
+
if (!ts.isBindingElement(cur) && !ts.isObjectBindingPattern(cur) && !ts.isArrayBindingPattern(cur)) return false;
|
|
7271
|
+
}
|
|
7272
|
+
return false;
|
|
7273
|
+
}
|
|
7274
|
+
function isModuleLevel(declaration, ts) {
|
|
7275
|
+
for (let cur = declaration?.parent; cur; cur = cur.parent) {
|
|
7276
|
+
if (ts.isSourceFile(cur)) return true;
|
|
7277
|
+
if (ts.isFunctionLike(cur) || ts.isBlock(cur) || ts.isClassLike(cur)) return false;
|
|
7278
|
+
}
|
|
7279
|
+
return false;
|
|
7280
|
+
}
|
|
7101
7281
|
function namespaceMemberCalls(node, ts) {
|
|
7102
7282
|
const out = [];
|
|
7103
7283
|
const visit = (n) => {
|
|
@@ -7158,7 +7338,7 @@ function directSinks(node, ts, bindings, ctx) {
|
|
|
7158
7338
|
const prismaLikely = b.pkg === "@prisma/client" || !b.pkg && (bindings.imports.has("@prisma/client") || /prisma/i.test(b.root ?? ""));
|
|
7159
7339
|
if (prismaLikely) push({ kind: "db", ...b.pkg ? { provider: "prisma" } : {}, package: "@prisma/client", table: callee.expression.name.text, op: method, attribution: b.pkg ? "import" : "inferred", ...spanOf(n) });
|
|
7160
7340
|
}
|
|
7161
|
-
if (method === "query" || method === "execute") {
|
|
7341
|
+
if ((method === "query" || method === "execute") && !isContextRequest(callee.expression, ts)) {
|
|
7162
7342
|
const pkg = b.pkg ?? infer("db");
|
|
7163
7343
|
const attribution = attributionOf(b, pkg);
|
|
7164
7344
|
push({ kind: "db", ...dbApi(pkg, attribution), package: pkg, op: method, attribution, ...spanOf(n) });
|
|
@@ -7298,114 +7478,6 @@ function dedupeSinks(sinks, owner) {
|
|
|
7298
7478
|
return out;
|
|
7299
7479
|
}
|
|
7300
7480
|
|
|
7301
|
-
// src/map/module-graph.ts
|
|
7302
|
-
import { readFileSync as readFileSync14, realpathSync as realpathSync5, statSync as statSync7 } from "fs";
|
|
7303
|
-
import { dirname as dirname8, join as join16, relative as relative5, resolve as resolvePath } from "path";
|
|
7304
|
-
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
7305
|
-
function createModuleGraph(ts, opts) {
|
|
7306
|
-
const cache = /* @__PURE__ */ new Map();
|
|
7307
|
-
const load = (file) => {
|
|
7308
|
-
if (cache.has(file)) return cache.get(file) ?? null;
|
|
7309
|
-
let entry = null;
|
|
7310
|
-
try {
|
|
7311
|
-
const text = readFileSync14(file, "utf8");
|
|
7312
|
-
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
7313
|
-
const bindings = buildModuleBindings(sf, ts);
|
|
7314
|
-
entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
|
|
7315
|
-
} catch {
|
|
7316
|
-
entry = null;
|
|
7317
|
-
}
|
|
7318
|
-
cache.set(file, entry);
|
|
7319
|
-
return entry;
|
|
7320
|
-
};
|
|
7321
|
-
const resolveInProject = (fromFile, specifier) => {
|
|
7322
|
-
const target = resolveRelativeModule(fromFile, specifier);
|
|
7323
|
-
if (!target) return void 0;
|
|
7324
|
-
if (!opts.followOutside) {
|
|
7325
|
-
let real = target;
|
|
7326
|
-
try {
|
|
7327
|
-
real = realpathSync5(target);
|
|
7328
|
-
} catch {
|
|
7329
|
-
}
|
|
7330
|
-
if (!isInside(real, opts.boundary)) return void 0;
|
|
7331
|
-
}
|
|
7332
|
-
return target;
|
|
7333
|
-
};
|
|
7334
|
-
return {
|
|
7335
|
-
resolveLocal: resolveInProject,
|
|
7336
|
-
importedSinks(fromFile, specifier, exportName) {
|
|
7337
|
-
const target = resolveInProject(fromFile, specifier);
|
|
7338
|
-
if (!target) return [];
|
|
7339
|
-
const mod = load(target);
|
|
7340
|
-
if (!mod) return [];
|
|
7341
|
-
const collected = [...mod.fnSinks.get(exportName) ?? []];
|
|
7342
|
-
for (const callee of mod.calleesOf.get(exportName) ?? []) {
|
|
7343
|
-
for (const s of mod.fnSinks.get(callee) ?? []) collected.push(s);
|
|
7344
|
-
}
|
|
7345
|
-
const rel = relative5(opts.cwd, target);
|
|
7346
|
-
return collected.map((s) => ({ ...s, file: rel }));
|
|
7347
|
-
},
|
|
7348
|
-
// A different question about the same module: not "what sinks are in there" but "what does this
|
|
7349
|
-
// export TRACE TO". The common AI-built layout puts the client in a lib file —
|
|
7350
|
-
// `export const db = createClient(...)` in `lib/db.ts`, imported everywhere — so the receiver of
|
|
7351
|
-
// `db.from('orders').insert(...)` resolves to a relative specifier and nothing else. Refusing it (as
|
|
7352
|
-
// an unattributable receiver) is right for app code but wrong here: one hop away it is a real
|
|
7353
|
-
// dependency, and that chain is import-to-import, fully static — evidence, not inference.
|
|
7354
|
-
importedBinding(fromFile, specifier, exportName) {
|
|
7355
|
-
const target = resolveInProject(fromFile, specifier);
|
|
7356
|
-
if (!target) return void 0;
|
|
7357
|
-
const mod = load(target);
|
|
7358
|
-
if (!mod) return void 0;
|
|
7359
|
-
const pkg = npmPackageOf(mod.bindings.resolve(exportName));
|
|
7360
|
-
if (pkg === void 0) return void 0;
|
|
7361
|
-
return {
|
|
7362
|
-
package: pkg,
|
|
7363
|
-
origin: mod.bindings.originOf(exportName) ?? "direct",
|
|
7364
|
-
name: mod.bindings.exportNameOf(exportName) ?? exportName
|
|
7365
|
-
};
|
|
7366
|
-
}
|
|
7367
|
-
};
|
|
7368
|
-
}
|
|
7369
|
-
function resolveRelativeModule(fromFile, spec) {
|
|
7370
|
-
if (!spec.startsWith(".")) return void 0;
|
|
7371
|
-
const dir = dirname8(fromFile);
|
|
7372
|
-
const base = resolvePath(dir, spec);
|
|
7373
|
-
const candidates = [];
|
|
7374
|
-
const jsLike = /\.(js|jsx|mjs|cjs)$/.exec(base);
|
|
7375
|
-
if (jsLike) {
|
|
7376
|
-
const stem = base.slice(0, -jsLike[0].length);
|
|
7377
|
-
for (const e of RESOLVE_EXTS) candidates.push(stem + e);
|
|
7378
|
-
}
|
|
7379
|
-
candidates.push(base);
|
|
7380
|
-
for (const e of RESOLVE_EXTS) candidates.push(base + e);
|
|
7381
|
-
for (const e of RESOLVE_EXTS) candidates.push(join16(base, "index" + e));
|
|
7382
|
-
for (const c of candidates) {
|
|
7383
|
-
try {
|
|
7384
|
-
if (statSync7(c).isFile()) return c;
|
|
7385
|
-
} catch {
|
|
7386
|
-
}
|
|
7387
|
-
}
|
|
7388
|
-
return void 0;
|
|
7389
|
-
}
|
|
7390
|
-
function collectCallees(sf, ts) {
|
|
7391
|
-
const map = /* @__PURE__ */ new Map();
|
|
7392
|
-
const visit = (node) => {
|
|
7393
|
-
let name;
|
|
7394
|
-
let body;
|
|
7395
|
-
if (ts.isFunctionDeclaration(node) && node.name && node.body) {
|
|
7396
|
-
name = node.name.text;
|
|
7397
|
-
body = node.body;
|
|
7398
|
-
} else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && isFnLike(node.initializer, ts)) {
|
|
7399
|
-
name = node.name.text;
|
|
7400
|
-
body = node.initializer.body;
|
|
7401
|
-
}
|
|
7402
|
-
if (name && body) map.set(name, localCalls(body, ts));
|
|
7403
|
-
ts.forEachChild(node, visit);
|
|
7404
|
-
};
|
|
7405
|
-
visit(sf);
|
|
7406
|
-
return map;
|
|
7407
|
-
}
|
|
7408
|
-
|
|
7409
7481
|
// src/map/coordinates.ts
|
|
7410
7482
|
function runtimeCoordinate(source, path17) {
|
|
7411
7483
|
if (/\[\d*\]/.test(path17)) {
|
|
@@ -7477,7 +7549,9 @@ function inputsFromValidator(validatorCall, ts, bindings) {
|
|
|
7477
7549
|
}
|
|
7478
7550
|
function inputsFromHandler(params, body, ts, bindings, opts = {}) {
|
|
7479
7551
|
const schemaSource = opts.validatorSource ?? "json-body";
|
|
7480
|
-
const
|
|
7552
|
+
const inline = zodObjectFields(body, ts, bindings);
|
|
7553
|
+
const referenced = inline.length > 0 ? { fields: [], unresolved: false } : referencedSchemaFields(params, body, ts, bindings);
|
|
7554
|
+
const schemaFields = inline.length > 0 ? inline : referenced.fields;
|
|
7481
7555
|
const reads = requestMemberAccesses(params, body, ts, opts);
|
|
7482
7556
|
const byId = /* @__PURE__ */ new Map();
|
|
7483
7557
|
const put = (name, source, extra = {}) => {
|
|
@@ -7490,7 +7564,58 @@ function inputsFromHandler(params, body, ts, bindings, opts = {}) {
|
|
|
7490
7564
|
put(name, source ?? schemaSource, shape);
|
|
7491
7565
|
}
|
|
7492
7566
|
for (const { name, sources } of reads) for (const source of sources) put(name, source);
|
|
7493
|
-
return [...byId.values()];
|
|
7567
|
+
return { inputs: [...byId.values()], schemaUnresolved: referenced.unresolved };
|
|
7568
|
+
}
|
|
7569
|
+
var SCHEMA_METHODS = /* @__PURE__ */ new Set(["parse", "safeParse", "parseAsync", "safeParseAsync"]);
|
|
7570
|
+
function referencedSchemaFields(params, body, ts, bindings) {
|
|
7571
|
+
if (!body) return { fields: [], unresolved: false };
|
|
7572
|
+
const paramDecls = /* @__PURE__ */ new Set();
|
|
7573
|
+
for (const p of params ?? []) {
|
|
7574
|
+
if (!p?.name) continue;
|
|
7575
|
+
if (ts.isIdentifier(p.name)) paramDecls.add(p.name);
|
|
7576
|
+
else if (ts.isObjectBindingPattern(p.name)) {
|
|
7577
|
+
for (const el of p.name.elements) if (ts.isIdentifier(el.name)) paramDecls.add(el.name);
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
const fromParams = (e) => {
|
|
7581
|
+
const root = rootIdentifierNode(e, ts);
|
|
7582
|
+
const declaration = root ? declarationOf(root, ts) : void 0;
|
|
7583
|
+
if (declaration === void 0) return false;
|
|
7584
|
+
if (paramDecls.has(declaration)) return true;
|
|
7585
|
+
const owner = declaration.parent;
|
|
7586
|
+
if (owner && ts.isVariableDeclaration(owner) && owner.name === declaration && owner.initializer) {
|
|
7587
|
+
const inner = rootIdentifierNode(owner.initializer, ts);
|
|
7588
|
+
const innerDeclaration = inner ? declarationOf(inner, ts) : void 0;
|
|
7589
|
+
return innerDeclaration !== void 0 && paramDecls.has(innerDeclaration);
|
|
7590
|
+
}
|
|
7591
|
+
return false;
|
|
7592
|
+
};
|
|
7593
|
+
let fields = [];
|
|
7594
|
+
let unresolved = false;
|
|
7595
|
+
const visit = (n) => {
|
|
7596
|
+
if (fields.length > 0) return;
|
|
7597
|
+
if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && SCHEMA_METHODS.has(n.expression.name.text) && ts.isIdentifier(n.expression.expression) && n.arguments.some((a) => fromParams(a))) {
|
|
7598
|
+
const declaration = declarationOf(n.expression.expression, ts);
|
|
7599
|
+
const owner = declaration?.parent;
|
|
7600
|
+
if (owner && ts.isVariableDeclaration(owner) && owner.name === declaration && owner.initializer) {
|
|
7601
|
+
const literal = findValidatorObject(owner.initializer, ts, bindings);
|
|
7602
|
+
if (literal) fields = fieldsOfObject(literal, ts, bindings, "");
|
|
7603
|
+
else unresolved = true;
|
|
7604
|
+
} else if (declaration !== void 0 && isImported(declaration, ts)) {
|
|
7605
|
+
unresolved = true;
|
|
7606
|
+
}
|
|
7607
|
+
}
|
|
7608
|
+
ts.forEachChild(n, visit);
|
|
7609
|
+
};
|
|
7610
|
+
visit(body);
|
|
7611
|
+
return { fields, unresolved: fields.length === 0 && unresolved };
|
|
7612
|
+
}
|
|
7613
|
+
function isImported(declaration, ts) {
|
|
7614
|
+
for (let cur = declaration?.parent; cur; cur = cur.parent) {
|
|
7615
|
+
if (ts.isImportDeclaration(cur)) return true;
|
|
7616
|
+
if (ts.isSourceFile(cur) || ts.isStatement(cur)) return false;
|
|
7617
|
+
}
|
|
7618
|
+
return false;
|
|
7494
7619
|
}
|
|
7495
7620
|
function findValidatorObject(node, ts, bindings) {
|
|
7496
7621
|
let found = null;
|
|
@@ -7569,16 +7694,29 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
7569
7694
|
out.set(name, list);
|
|
7570
7695
|
};
|
|
7571
7696
|
const p0 = params?.[0];
|
|
7572
|
-
const
|
|
7697
|
+
const reqDecl = p0 && ts.isIdentifier(p0.name) ? p0.name : void 0;
|
|
7698
|
+
const requestDecls = new Set(reqDecl ? [reqDecl] : []);
|
|
7699
|
+
const isRequest = (e) => ts.isIdentifier(e) && requestDecls.has(declarationOf(e, ts));
|
|
7700
|
+
const isRequestObject = (e) => isRequest(e) || ts.isPropertyAccessExpression(e) && e.name.text === "req" && isRequest(e.expression);
|
|
7701
|
+
const urlDecls = /* @__PURE__ */ new Set();
|
|
7702
|
+
const searchParamDecls = /* @__PURE__ */ new Set();
|
|
7573
7703
|
const sourceNames = /* @__PURE__ */ new Map();
|
|
7574
7704
|
const payloadNames = /* @__PURE__ */ new Set();
|
|
7575
|
-
|
|
7576
|
-
if (
|
|
7705
|
+
const sourceOf = (e) => ts.isIdentifier(e) ? sourceNames.get(declarationOf(e, ts)) : void 0;
|
|
7706
|
+
if (opts.payloadParam && p0 && ts.isIdentifier(p0.name)) payloadNames.add(p0.name);
|
|
7707
|
+
if (p0 && !reqDecl && ts.isObjectBindingPattern(p0.name)) {
|
|
7577
7708
|
for (const el of p0.name.elements) {
|
|
7578
7709
|
const key = bindingKey(el, ts);
|
|
7579
|
-
if (key
|
|
7580
|
-
|
|
7581
|
-
|
|
7710
|
+
if (!key || !ts.isIdentifier(el.name)) continue;
|
|
7711
|
+
if (REQ_SOURCES.includes(key)) sourceNames.set(el.name, namespaceSource(key));
|
|
7712
|
+
else if (key === "request") requestDecls.add(el.name);
|
|
7713
|
+
else if (key === "url") urlDecls.add(el.name);
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
7716
|
+
for (const p of (params ?? []).slice(1)) {
|
|
7717
|
+
if (!p?.name || !ts.isObjectBindingPattern(p.name)) continue;
|
|
7718
|
+
for (const el of p.name.elements) {
|
|
7719
|
+
if (bindingKey(el, ts) === "params" && ts.isIdentifier(el.name)) sourceNames.set(el.name, "route-param");
|
|
7582
7720
|
}
|
|
7583
7721
|
}
|
|
7584
7722
|
const unwrap = (e) => {
|
|
@@ -7586,14 +7724,36 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
7586
7724
|
while (cur && (ts.isAwaitExpression(cur) || ts.isAsExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
|
|
7587
7725
|
return cur;
|
|
7588
7726
|
};
|
|
7589
|
-
const isPayloadExpr = (e) => ts.isIdentifier(e) && payloadNames.has(e
|
|
7590
|
-
const isReqSourceExpr = (e) => isPayloadExpr(e) || ts.isPropertyAccessExpression(e) &&
|
|
7727
|
+
const isPayloadExpr = (e) => ts.isIdentifier(e) && payloadNames.has(declarationOf(e, ts));
|
|
7728
|
+
const isReqSourceExpr = (e) => isPayloadExpr(e) || ts.isPropertyAccessExpression(e) && isRequest(e.expression) && REQ_SOURCES.includes(e.name.text) || sourceOf(e) !== void 0;
|
|
7591
7729
|
const isBodyReadCall = (e) => {
|
|
7592
7730
|
const inner = unwrap(e);
|
|
7593
|
-
return Boolean(inner && ts.isCallExpression(inner) && ts.isPropertyAccessExpression(inner.expression) && ["json", "formData"].includes(inner.expression.name.text) &&
|
|
7731
|
+
return Boolean(inner && ts.isCallExpression(inner) && ts.isPropertyAccessExpression(inner.expression) && ["json", "formData"].includes(inner.expression.name.text) && isRequestObject(inner.expression.expression));
|
|
7732
|
+
};
|
|
7733
|
+
const isRequestUrl = (e) => {
|
|
7734
|
+
const cur = unwrap(e);
|
|
7735
|
+
if (!cur) return false;
|
|
7736
|
+
if (ts.isIdentifier(cur)) return urlDecls.has(declarationOf(cur, ts));
|
|
7737
|
+
if (ts.isPropertyAccessExpression(cur) && cur.name.text === "nextUrl") return isRequest(cur.expression);
|
|
7738
|
+
if (ts.isNewExpression(cur) && isGlobal(cur.expression, "URL", ts)) {
|
|
7739
|
+
const [href] = cur.arguments ?? [];
|
|
7740
|
+
return Boolean(href && ts.isPropertyAccessExpression(href) && href.name.text === "url" && isRequestObject(href.expression));
|
|
7741
|
+
}
|
|
7742
|
+
return false;
|
|
7743
|
+
};
|
|
7744
|
+
const isSearchParams = (e) => {
|
|
7745
|
+
const cur = unwrap(e);
|
|
7746
|
+
if (!cur) return false;
|
|
7747
|
+
if (ts.isIdentifier(cur)) return searchParamDecls.has(declarationOf(cur, ts));
|
|
7748
|
+
return ts.isPropertyAccessExpression(cur) && cur.name.text === "searchParams" && isRequestUrl(cur.expression);
|
|
7749
|
+
};
|
|
7750
|
+
const literalArgument = (call) => {
|
|
7751
|
+
const [arg] = call.arguments ?? [];
|
|
7752
|
+
return call.arguments?.length === 1 && arg && ts.isStringLiteral(arg) ? arg.text : void 0;
|
|
7594
7753
|
};
|
|
7595
7754
|
const isCallee = (n) => Boolean(n.parent && (ts.isCallExpression(n.parent) || ts.isNewExpression(n.parent)) && n.parent.expression === n);
|
|
7596
7755
|
const ACCESSOR_SOURCES = /* @__PURE__ */ new Set(["header", "cookie", "form-body"]);
|
|
7756
|
+
const HONO_ACCESSORS = { query: "query", param: "route-param", header: "header" };
|
|
7597
7757
|
const visit = (n) => {
|
|
7598
7758
|
if (ts.isPropertyAccessExpression(n) && isReqSourceExpr(n.expression) && !isCallee(n)) {
|
|
7599
7759
|
record(n.name.text, sourceOfExpr(n.expression));
|
|
@@ -7604,13 +7764,34 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
7604
7764
|
if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && n.expression.name.text === "get" && isReqSourceExpr(n.expression.expression) && n.arguments.length === 1 && n.arguments[0] && ts.isStringLiteral(n.arguments[0]) && ACCESSOR_SOURCES.has(sourceOfExpr(n.expression.expression))) {
|
|
7605
7765
|
record(n.arguments[0].text, sourceOfExpr(n.expression.expression));
|
|
7606
7766
|
}
|
|
7767
|
+
if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && n.expression.name.text === "get" && isSearchParams(n.expression.expression)) {
|
|
7768
|
+
const name = literalArgument(n);
|
|
7769
|
+
if (name !== void 0) record(name, "query");
|
|
7770
|
+
}
|
|
7771
|
+
if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && ts.isPropertyAccessExpression(n.expression.expression) && n.expression.expression.name.text === "req" && isRequest(n.expression.expression.expression)) {
|
|
7772
|
+
const source = HONO_ACCESSORS[n.expression.name.text];
|
|
7773
|
+
const name = literalArgument(n);
|
|
7774
|
+
if (source && name !== void 0) record(name, source);
|
|
7775
|
+
}
|
|
7607
7776
|
if (ts.isVariableDeclaration(n) && n.initializer) {
|
|
7608
7777
|
const init = unwrap(n.initializer);
|
|
7609
|
-
if (ts.isIdentifier(n.name)
|
|
7610
|
-
|
|
7778
|
+
if (ts.isIdentifier(n.name)) {
|
|
7779
|
+
if (isRequest(init)) requestDecls.add(n.name);
|
|
7780
|
+
else if (ts.isPropertyAccessExpression(init) && isRequest(init.expression) && REQ_SOURCES.includes(init.name.text)) {
|
|
7781
|
+
sourceNames.set(n.name, namespaceSource(init.name.text));
|
|
7782
|
+
} else if (isRequestUrl(init)) urlDecls.add(n.name);
|
|
7783
|
+
else if (isSearchParams(init)) searchParamDecls.add(n.name);
|
|
7784
|
+
}
|
|
7785
|
+
if (ts.isObjectBindingPattern(n.name) && isRequestUrl(init)) {
|
|
7786
|
+
for (const el of n.name.elements) {
|
|
7787
|
+
if (bindingKey(el, ts) === "searchParams" && ts.isIdentifier(el.name)) searchParamDecls.add(el.name);
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7790
|
+
if (ts.isIdentifier(n.name) && isBodyReadCall(n.initializer)) sourceNames.set(n.name, bodyReadSource(n.initializer));
|
|
7791
|
+
if (ts.isObjectBindingPattern(n.name) && isRequest(init)) {
|
|
7611
7792
|
for (const el of n.name.elements) {
|
|
7612
7793
|
const key = bindingKey(el, ts);
|
|
7613
|
-
if (key && REQ_SOURCES.includes(key) && ts.isIdentifier(el.name)) sourceNames.set(el.name
|
|
7794
|
+
if (key && REQ_SOURCES.includes(key) && ts.isIdentifier(el.name)) sourceNames.set(el.name, namespaceSource(key));
|
|
7614
7795
|
}
|
|
7615
7796
|
}
|
|
7616
7797
|
if (ts.isObjectBindingPattern(n.name) && (isReqSourceExpr(init) || isBodyReadCall(n.initializer))) {
|
|
@@ -7632,7 +7813,7 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
7632
7813
|
if (REQ_SOURCES.includes(e.name.text)) return named;
|
|
7633
7814
|
}
|
|
7634
7815
|
if (ts.isIdentifier(e)) {
|
|
7635
|
-
const recorded =
|
|
7816
|
+
const recorded = sourceOf(e);
|
|
7636
7817
|
if (recorded) return recorded;
|
|
7637
7818
|
}
|
|
7638
7819
|
return "body";
|
|
@@ -7653,6 +7834,74 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
7653
7834
|
|
|
7654
7835
|
// src/map/flows.ts
|
|
7655
7836
|
var MAX_DEPENDENCY_INPUT_FLOWS_PER_ENDPOINT = 100;
|
|
7837
|
+
var MAX_UNPROVEN_FLOWS_PER_ENDPOINT = 200;
|
|
7838
|
+
function boundUnprovenFlows(flows, limit) {
|
|
7839
|
+
let unproven = 0;
|
|
7840
|
+
let truncated = false;
|
|
7841
|
+
const kept = flows.filter((f) => {
|
|
7842
|
+
if (isProvenFlow(f.confidence)) return true;
|
|
7843
|
+
if (unproven >= limit) {
|
|
7844
|
+
truncated = true;
|
|
7845
|
+
return false;
|
|
7846
|
+
}
|
|
7847
|
+
unproven++;
|
|
7848
|
+
return true;
|
|
7849
|
+
});
|
|
7850
|
+
return { flows: kept, truncated };
|
|
7851
|
+
}
|
|
7852
|
+
var Roots = class {
|
|
7853
|
+
constructor(ts) {
|
|
7854
|
+
this.ts = ts;
|
|
7855
|
+
}
|
|
7856
|
+
ts;
|
|
7857
|
+
byDeclaration = /* @__PURE__ */ new Map();
|
|
7858
|
+
add(declaration, root) {
|
|
7859
|
+
if (!this.byDeclaration.has(declaration)) this.byDeclaration.set(declaration, root);
|
|
7860
|
+
}
|
|
7861
|
+
/** The root an identifier occurrence refers to, when its binding is tainted. */
|
|
7862
|
+
get(id) {
|
|
7863
|
+
const declaration = declarationOf(id, this.ts);
|
|
7864
|
+
return declaration === void 0 ? void 0 : this.byDeclaration.get(declaration);
|
|
7865
|
+
}
|
|
7866
|
+
};
|
|
7867
|
+
function reassignedDeclarations(body, ts) {
|
|
7868
|
+
const out = /* @__PURE__ */ new Set();
|
|
7869
|
+
const target = (n) => {
|
|
7870
|
+
if (!n) return;
|
|
7871
|
+
if (ts.isParenthesizedExpression(n)) return target(n.expression);
|
|
7872
|
+
if (ts.isIdentifier(n)) {
|
|
7873
|
+
const declaration = declarationOf(n, ts);
|
|
7874
|
+
if (declaration !== void 0) out.add(declaration);
|
|
7875
|
+
return;
|
|
7876
|
+
}
|
|
7877
|
+
if (ts.isObjectLiteralExpression(n)) {
|
|
7878
|
+
for (const p of n.properties) {
|
|
7879
|
+
if (ts.isShorthandPropertyAssignment(p)) target(p.name);
|
|
7880
|
+
else if (ts.isPropertyAssignment(p)) target(p.initializer);
|
|
7881
|
+
else if (ts.isSpreadAssignment(p)) target(p.expression);
|
|
7882
|
+
}
|
|
7883
|
+
return;
|
|
7884
|
+
}
|
|
7885
|
+
if (ts.isArrayLiteralExpression(n)) {
|
|
7886
|
+
for (const e of n.elements) target(ts.isSpreadElement(e) ? e.expression : e);
|
|
7887
|
+
return;
|
|
7888
|
+
}
|
|
7889
|
+
if (ts.isBinaryExpression(n) && n.operatorToken.kind === ts.SyntaxKind.EqualsToken) target(n.left);
|
|
7890
|
+
};
|
|
7891
|
+
const visit = (n) => {
|
|
7892
|
+
if (!n) return;
|
|
7893
|
+
if (ts.isBinaryExpression(n) && n.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && n.operatorToken.kind <= ts.SyntaxKind.LastAssignment) {
|
|
7894
|
+
target(n.left);
|
|
7895
|
+
}
|
|
7896
|
+
if ((ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) && (n.operator === ts.SyntaxKind.PlusPlusToken || n.operator === ts.SyntaxKind.MinusMinusToken)) {
|
|
7897
|
+
target(n.operand);
|
|
7898
|
+
}
|
|
7899
|
+
if ((ts.isForOfStatement(n) || ts.isForInStatement(n)) && !ts.isVariableDeclarationList(n.initializer)) target(n.initializer);
|
|
7900
|
+
ts.forEachChild(n, visit);
|
|
7901
|
+
};
|
|
7902
|
+
visit(body);
|
|
7903
|
+
return out;
|
|
7904
|
+
}
|
|
7656
7905
|
var ACCESSOR_NAMESPACES = /* @__PURE__ */ new Set(["headers", "cookies"]);
|
|
7657
7906
|
function spaceOfKey(key) {
|
|
7658
7907
|
if (key === "query") return "get";
|
|
@@ -7665,9 +7914,11 @@ function spaceOfKey(key) {
|
|
|
7665
7914
|
return void 0;
|
|
7666
7915
|
}
|
|
7667
7916
|
function linkedFlows(body, params, inputs, sinks, ts, invocations = []) {
|
|
7668
|
-
const { flows, limitations, dependencyInputFlows, dependencyInputFlowsTruncated } = linkFlows(body, params, inputs, sinks, ts, invocations);
|
|
7917
|
+
const { flows: linked, limitations, dependencyInputFlows, dependencyInputFlowsTruncated } = linkFlows(body, params, inputs, sinks, ts, invocations);
|
|
7918
|
+
const { flows, truncated } = boundUnprovenFlows(linked, MAX_UNPROVEN_FLOWS_PER_ENDPOINT);
|
|
7669
7919
|
return {
|
|
7670
7920
|
flows,
|
|
7921
|
+
...truncated ? { flowsTruncated: true } : {},
|
|
7671
7922
|
...limitations.length > 0 ? { limitations } : {},
|
|
7672
7923
|
...dependencyInputFlows.length > 0 ? { dependencyInputFlows } : {},
|
|
7673
7924
|
...dependencyInputFlowsTruncated ? { dependencyInputFlowsTruncated: true } : {}
|
|
@@ -7678,19 +7929,29 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
|
|
|
7678
7929
|
return { flows: [], limitations: [], dependencyInputFlows: [], dependencyInputFlowsTruncated: false };
|
|
7679
7930
|
}
|
|
7680
7931
|
const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
|
|
7681
|
-
const rootPath =
|
|
7682
|
-
const
|
|
7683
|
-
|
|
7932
|
+
const rootPath = new Roots(ts);
|
|
7933
|
+
const reassigned = reassignedDeclarations(bodyNode, ts);
|
|
7934
|
+
const addRoot = (declaration, path17, space, accessor = false, request = false, inherited = false, url = false) => {
|
|
7935
|
+
const changed = inherited || reassigned.has(declaration);
|
|
7936
|
+
rootPath.add(declaration, { path: path17, space, ...accessor ? { accessor } : {}, ...request ? { request } : {}, ...changed ? { reassigned: true } : {}, ...url ? { url } : {} });
|
|
7684
7937
|
};
|
|
7685
7938
|
for (const [index, p] of (params ?? []).entries()) {
|
|
7686
7939
|
if (!p?.name) continue;
|
|
7687
|
-
if (ts.isIdentifier(p.name)) addRoot(p.name
|
|
7940
|
+
if (ts.isIdentifier(p.name)) addRoot(p.name, "", void 0, false, index === 0);
|
|
7688
7941
|
else if (ts.isObjectBindingPattern(p.name)) {
|
|
7689
7942
|
for (const el of p.name.elements) {
|
|
7690
7943
|
if (!ts.isBindingElement(el) || !ts.isIdentifier(el.name)) continue;
|
|
7691
7944
|
const key = bindingKey(el, ts);
|
|
7945
|
+
if (index === 0 && key === "request") {
|
|
7946
|
+
addRoot(el.name, "", void 0, false, true);
|
|
7947
|
+
continue;
|
|
7948
|
+
}
|
|
7949
|
+
if (index === 0 && key === "url") {
|
|
7950
|
+
addRoot(el.name, "", void 0, false, false, false, true);
|
|
7951
|
+
continue;
|
|
7952
|
+
}
|
|
7692
7953
|
const container = key !== void 0 && CONTAINER_KEYS.has(key);
|
|
7693
|
-
addRoot(el.name
|
|
7954
|
+
addRoot(el.name, container ? "" : key ?? el.name.text, container ? spaceOfKey(key) : void 0, container && ACCESSOR_NAMESPACES.has(key));
|
|
7694
7955
|
}
|
|
7695
7956
|
}
|
|
7696
7957
|
}
|
|
@@ -7698,12 +7959,17 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
|
|
|
7698
7959
|
let cur = init;
|
|
7699
7960
|
while (cur && (ts.isAwaitExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isAsExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
|
|
7700
7961
|
if (!cur) return void 0;
|
|
7962
|
+
const params2 = searchParamsOrigin(cur, ts, rootPath);
|
|
7963
|
+
if (params2) return { path: "", space: "get", accessor: true, ...carried(params2) };
|
|
7964
|
+
const url = requestUrlOrigin(cur, ts, rootPath);
|
|
7965
|
+
if (url) return { path: "", url: true, ...carried(url) };
|
|
7701
7966
|
if (ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) {
|
|
7702
7967
|
const m = cur.expression.name.text;
|
|
7703
7968
|
if (["json", "formData", "text"].includes(m)) {
|
|
7704
|
-
const root =
|
|
7705
|
-
|
|
7706
|
-
|
|
7969
|
+
const root = rootIdentifierNode(cur.expression.expression, ts);
|
|
7970
|
+
const owner = root ? rootPath.get(root) : void 0;
|
|
7971
|
+
if (!owner) return void 0;
|
|
7972
|
+
return m === "formData" ? { path: "", space: "post", accessor: true, ...carried(owner) } : { path: "", space: "post", ...carried(owner) };
|
|
7707
7973
|
}
|
|
7708
7974
|
if (["parse", "safeParse", "validate", "cast"].includes(m)) {
|
|
7709
7975
|
for (const a of cur.arguments) {
|
|
@@ -7719,15 +7985,19 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
|
|
|
7719
7985
|
if (ts.isVariableDeclaration(n) && n.initializer) {
|
|
7720
7986
|
const base = requestReadPath(n.initializer);
|
|
7721
7987
|
if (base !== void 0) {
|
|
7722
|
-
if (ts.isIdentifier(n.name)) addRoot(n.name
|
|
7988
|
+
if (ts.isIdentifier(n.name)) addRoot(n.name, base.path, base.space, base.accessor === true, base.request === true, base.reassigned === true, base.url === true);
|
|
7723
7989
|
else if (ts.isObjectBindingPattern(n.name)) {
|
|
7724
7990
|
for (const el of n.name.elements) {
|
|
7725
7991
|
if (!ts.isBindingElement(el) || !ts.isIdentifier(el.name)) continue;
|
|
7726
7992
|
const key = bindingKey(el, ts);
|
|
7993
|
+
if (base.url === true) {
|
|
7994
|
+
if (key === "searchParams") addRoot(el.name, "", "get", true, false, base.reassigned === true);
|
|
7995
|
+
continue;
|
|
7996
|
+
}
|
|
7727
7997
|
const namespace = base.request === true && key !== void 0 && REQ_SOURCES.includes(key);
|
|
7728
7998
|
const container = base.path === "" && key !== void 0 && CONTAINER_KEYS.has(key) && (namespace || !REQ_SOURCES.includes(key));
|
|
7729
7999
|
const space = base.space ?? (namespace ? spaceOfKey(key) : void 0);
|
|
7730
|
-
addRoot(el.name
|
|
8000
|
+
addRoot(el.name, container ? "" : join22(base.path, key ?? el.name.text), space, namespace && ACCESSOR_NAMESPACES.has(key), false, base.reassigned === true);
|
|
7731
8001
|
}
|
|
7732
8002
|
}
|
|
7733
8003
|
}
|
|
@@ -7762,7 +8032,7 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
|
|
|
7762
8032
|
const whole = pathFromTainted(args[i], ts, rootPath) ?? accessorRead(args[i], ts, rootPath);
|
|
7763
8033
|
for (const read2 of taintedReadPaths(args[i], ts, rootPath)) {
|
|
7764
8034
|
const key = `${read2.space ?? "*"}:${read2.path}`;
|
|
7765
|
-
const exact = whole !== void 0 && whole.path === read2.path && whole.space === read2.space;
|
|
8035
|
+
const exact = whole !== void 0 && whole.path === read2.path && whole.space === read2.space && whole.reassigned !== true;
|
|
7766
8036
|
const entry = reads.get(key) ?? { read: read2, roles: /* @__PURE__ */ new Set(), exact: false };
|
|
7767
8037
|
entry.roles.add(role);
|
|
7768
8038
|
entry.exact = entry.exact || exact;
|
|
@@ -7852,7 +8122,7 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
|
|
|
7852
8122
|
kind: invocation.kind,
|
|
7853
8123
|
resolution: invocation.resolution,
|
|
7854
8124
|
argumentIndex,
|
|
7855
|
-
argumentUse: whole?.path === read2.path && whole.space === read2.space ? "direct" : "within-expression",
|
|
8125
|
+
argumentUse: whole?.path === read2.path && whole.space === read2.space && whole.reassigned !== true ? "direct" : "within-expression",
|
|
7856
8126
|
line: site.line,
|
|
7857
8127
|
start: site.start,
|
|
7858
8128
|
end: site.end
|
|
@@ -7940,9 +8210,9 @@ function taintedReadPaths(node, ts, rootPath, includeDeferredBodies = true) {
|
|
|
7940
8210
|
return;
|
|
7941
8211
|
}
|
|
7942
8212
|
}
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
if (
|
|
8213
|
+
const bound = ts.isIdentifier(n) && isValueRead(n, ts) ? rootPath.get(n) : void 0;
|
|
8214
|
+
if (bound) {
|
|
8215
|
+
if (bound.path) add({ path: normalizePath(bound.path), space: bound.space });
|
|
7946
8216
|
}
|
|
7947
8217
|
ts.forEachChild(n, visit);
|
|
7948
8218
|
};
|
|
@@ -7968,15 +8238,15 @@ function sinkArgumentLimitations(node, ts, rootPath) {
|
|
|
7968
8238
|
const visit = (n) => {
|
|
7969
8239
|
if (!n) return;
|
|
7970
8240
|
if (ts.isElementAccessExpression(n)) {
|
|
7971
|
-
const root =
|
|
8241
|
+
const root = rootIdentifierNode(n.expression, ts);
|
|
7972
8242
|
const arg = n.argumentExpression;
|
|
7973
|
-
if (root && rootPath.
|
|
8243
|
+
if (root && rootPath.get(root) && arg && !ts.isStringLiteralLike(arg) && !ts.isNumericLiteral(arg)) {
|
|
7974
8244
|
add("dynamic-key", text(n), n);
|
|
7975
8245
|
}
|
|
7976
8246
|
}
|
|
7977
8247
|
if ((ts.isSpreadAssignment?.(n) || ts.isSpreadElement(n)) && n.expression) {
|
|
7978
|
-
const root =
|
|
7979
|
-
if (root && rootPath.
|
|
8248
|
+
const root = rootIdentifierNode(n.expression, ts);
|
|
8249
|
+
if (root && rootPath.get(root)) add("spread-into-sink", text(n.parent ?? n), n);
|
|
7980
8250
|
}
|
|
7981
8251
|
ts.forEachChild(n, visit);
|
|
7982
8252
|
};
|
|
@@ -7988,16 +8258,66 @@ function accessorRead(node, ts, rootPath) {
|
|
|
7988
8258
|
while (cur && (ts.isAwaitExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
|
|
7989
8259
|
if (!cur || !ts.isCallExpression(cur) || !ts.isPropertyAccessExpression(cur.expression)) return void 0;
|
|
7990
8260
|
const [name] = cur.arguments;
|
|
7991
|
-
if (cur.
|
|
7992
|
-
|
|
7993
|
-
|
|
8261
|
+
if (cur.arguments.length !== 1 || !name || !ts.isStringLiteralLike(name)) return void 0;
|
|
8262
|
+
const method = cur.expression.name.text;
|
|
8263
|
+
const receiver = cur.expression.expression;
|
|
8264
|
+
const hono = HONO_ACCESSOR_SPACES[method];
|
|
8265
|
+
if (hono && ts.isPropertyAccessExpression(receiver) && receiver.name.text === "req" && ts.isIdentifier(receiver.expression)) {
|
|
8266
|
+
const context = rootPath.get(receiver.expression);
|
|
8267
|
+
if (context?.request === true) return { path: normalizePath(name.text), space: hono, ...carried(context) };
|
|
8268
|
+
}
|
|
8269
|
+
if (method !== "get") return void 0;
|
|
8270
|
+
const params = ts.isIdentifier(receiver) ? void 0 : searchParamsOrigin(receiver, ts, rootPath);
|
|
8271
|
+
if (params) return { path: normalizePath(name.text), space: "get", ...carried(params) };
|
|
8272
|
+
if (!isAccessor(receiver, ts, rootPath)) return void 0;
|
|
8273
|
+
return pathFromTainted(receiver, ts, rootPath, [name.text]);
|
|
8274
|
+
}
|
|
8275
|
+
var HONO_ACCESSOR_SPACES = { query: "get", param: "route-param", header: "server" };
|
|
8276
|
+
function carried(origin) {
|
|
8277
|
+
return origin.reassigned === true ? { reassigned: true } : {};
|
|
8278
|
+
}
|
|
8279
|
+
function requestUrlOrigin(node, ts, rootPath) {
|
|
8280
|
+
let cur = node;
|
|
8281
|
+
while (cur && (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur) || ts.isAwaitExpression(cur) || ts.isAsExpression(cur))) cur = cur.expression;
|
|
8282
|
+
if (!cur) return void 0;
|
|
8283
|
+
if (ts.isIdentifier(cur)) {
|
|
8284
|
+
const root = rootPath.get(cur);
|
|
8285
|
+
return root?.url === true ? root : void 0;
|
|
8286
|
+
}
|
|
8287
|
+
const request = (e) => {
|
|
8288
|
+
let inner = e;
|
|
8289
|
+
while (inner && (ts.isParenthesizedExpression(inner) || ts.isNonNullExpression(inner) || ts.isAsExpression(inner))) inner = inner.expression;
|
|
8290
|
+
if (!inner || !ts.isIdentifier(inner)) return void 0;
|
|
8291
|
+
const root = rootPath.get(inner);
|
|
8292
|
+
return root?.request === true ? root : void 0;
|
|
8293
|
+
};
|
|
8294
|
+
if (ts.isPropertyAccessExpression(cur) && cur.name.text === "nextUrl") return request(cur.expression);
|
|
8295
|
+
if (ts.isNewExpression(cur) && isGlobal(cur.expression, "URL", ts)) {
|
|
8296
|
+
const [href] = cur.arguments ?? [];
|
|
8297
|
+
if (!href || !ts.isPropertyAccessExpression(href) || href.name.text !== "url") return void 0;
|
|
8298
|
+
const owner = href.expression;
|
|
8299
|
+
if (ts.isPropertyAccessExpression(owner) && owner.name.text === "req") return request(owner.expression) ?? request(owner);
|
|
8300
|
+
return request(owner);
|
|
8301
|
+
}
|
|
8302
|
+
return void 0;
|
|
8303
|
+
}
|
|
8304
|
+
function searchParamsOrigin(node, ts, rootPath) {
|
|
8305
|
+
let cur = node;
|
|
8306
|
+
while (cur && (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur) || ts.isAsExpression(cur))) cur = cur.expression;
|
|
8307
|
+
if (!cur) return void 0;
|
|
8308
|
+
if (ts.isIdentifier(cur)) {
|
|
8309
|
+
const root = rootPath.get(cur);
|
|
8310
|
+
return root?.accessor === true && root.space === "get" ? root : void 0;
|
|
8311
|
+
}
|
|
8312
|
+
if (!ts.isPropertyAccessExpression(cur) || cur.name.text !== "searchParams") return void 0;
|
|
8313
|
+
return requestUrlOrigin(cur.expression, ts, rootPath);
|
|
7994
8314
|
}
|
|
7995
8315
|
function isAccessor(node, ts, rootPath) {
|
|
7996
8316
|
let cur = node;
|
|
7997
8317
|
while (cur && (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
|
|
7998
|
-
if (ts.isIdentifier(cur)) return rootPath.get(cur
|
|
8318
|
+
if (ts.isIdentifier(cur)) return rootPath.get(cur)?.accessor === true;
|
|
7999
8319
|
if (ts.isPropertyAccessExpression(cur) && ACCESSOR_NAMESPACES.has(cur.name.text) && ts.isIdentifier(cur.expression)) {
|
|
8000
|
-
return rootPath.get(cur.expression
|
|
8320
|
+
return rootPath.get(cur.expression)?.request === true;
|
|
8001
8321
|
}
|
|
8002
8322
|
return false;
|
|
8003
8323
|
}
|
|
@@ -8023,16 +8343,128 @@ function pathFromTainted(node, ts, rootPath, trailing = []) {
|
|
|
8023
8343
|
break;
|
|
8024
8344
|
}
|
|
8025
8345
|
if (!cur || !ts.isIdentifier(cur)) return void 0;
|
|
8026
|
-
const base = rootPath.get(cur
|
|
8027
|
-
if (base === void 0) return void 0;
|
|
8346
|
+
const base = rootPath.get(cur);
|
|
8347
|
+
if (base === void 0 || base.url === true) return void 0;
|
|
8028
8348
|
segs.push(...trailing);
|
|
8029
8349
|
let space = base.space;
|
|
8030
|
-
|
|
8031
|
-
|
|
8350
|
+
let namespace;
|
|
8351
|
+
if (base.request === true && base.path === "" && segs.length > 0 && REQ_SOURCES.includes(segs[0])) {
|
|
8352
|
+
namespace = segs[0];
|
|
8353
|
+
space = spaceOfKey(namespace) ?? space;
|
|
8032
8354
|
segs.shift();
|
|
8033
8355
|
}
|
|
8034
|
-
const request = base.request === true && segs.length === 0 ? { request: true } : {};
|
|
8035
|
-
|
|
8356
|
+
const request = base.request === true && namespace === void 0 && segs.length === 0 ? { request: true } : {};
|
|
8357
|
+
const accessor = namespace !== void 0 && segs.length === 0 && ACCESSOR_NAMESPACES.has(namespace) ? { accessor: true } : {};
|
|
8358
|
+
const reassigned = base.reassigned === true ? { reassigned: true } : {};
|
|
8359
|
+
return { path: normalizePath([base.path, ...segs].filter(Boolean).join(".")), space, ...request, ...accessor, ...reassigned };
|
|
8360
|
+
}
|
|
8361
|
+
|
|
8362
|
+
// src/map/module-graph.ts
|
|
8363
|
+
import { readFileSync as readFileSync14, realpathSync as realpathSync5, statSync as statSync7 } from "fs";
|
|
8364
|
+
import { dirname as dirname8, join as join16, relative as relative5, resolve as resolvePath } from "path";
|
|
8365
|
+
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
8366
|
+
function createModuleGraph(ts, opts) {
|
|
8367
|
+
const cache = /* @__PURE__ */ new Map();
|
|
8368
|
+
const load = (file) => {
|
|
8369
|
+
if (cache.has(file)) return cache.get(file) ?? null;
|
|
8370
|
+
let entry = null;
|
|
8371
|
+
try {
|
|
8372
|
+
const text = readFileSync14(file, "utf8");
|
|
8373
|
+
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
8374
|
+
const bindings = buildModuleBindings(sf, ts);
|
|
8375
|
+
entry = { fnSinks: collectLocalSinks(sf, ts, bindings).byName, calleesOf: collectCallees(sf, ts), bindings };
|
|
8376
|
+
} catch {
|
|
8377
|
+
entry = null;
|
|
8378
|
+
}
|
|
8379
|
+
cache.set(file, entry);
|
|
8380
|
+
return entry;
|
|
8381
|
+
};
|
|
8382
|
+
const resolveInProject = (fromFile, specifier) => {
|
|
8383
|
+
const target = resolveRelativeModule(fromFile, specifier);
|
|
8384
|
+
if (!target) return void 0;
|
|
8385
|
+
if (!opts.followOutside) {
|
|
8386
|
+
let real = target;
|
|
8387
|
+
try {
|
|
8388
|
+
real = realpathSync5(target);
|
|
8389
|
+
} catch {
|
|
8390
|
+
}
|
|
8391
|
+
if (!isInside(real, opts.boundary)) return void 0;
|
|
8392
|
+
}
|
|
8393
|
+
return target;
|
|
8394
|
+
};
|
|
8395
|
+
return {
|
|
8396
|
+
resolveLocal: resolveInProject,
|
|
8397
|
+
importedSinks(fromFile, specifier, exportName) {
|
|
8398
|
+
const target = resolveInProject(fromFile, specifier);
|
|
8399
|
+
if (!target) return [];
|
|
8400
|
+
const mod = load(target);
|
|
8401
|
+
if (!mod) return [];
|
|
8402
|
+
const collected = [...mod.fnSinks.get(exportName) ?? []];
|
|
8403
|
+
for (const callee of mod.calleesOf.get(exportName) ?? []) {
|
|
8404
|
+
for (const s of mod.fnSinks.get(callee) ?? []) collected.push(s);
|
|
8405
|
+
}
|
|
8406
|
+
const rel = relative5(opts.cwd, target);
|
|
8407
|
+
return collected.map((s) => ({ ...s, file: rel }));
|
|
8408
|
+
},
|
|
8409
|
+
// A different question about the same module: not "what sinks are in there" but "what does this
|
|
8410
|
+
// export TRACE TO". The common AI-built layout puts the client in a lib file —
|
|
8411
|
+
// `export const db = createClient(...)` in `lib/db.ts`, imported everywhere — so the receiver of
|
|
8412
|
+
// `db.from('orders').insert(...)` resolves to a relative specifier and nothing else. Refusing it (as
|
|
8413
|
+
// an unattributable receiver) is right for app code but wrong here: one hop away it is a real
|
|
8414
|
+
// dependency, and that chain is import-to-import, fully static — evidence, not inference.
|
|
8415
|
+
importedBinding(fromFile, specifier, exportName) {
|
|
8416
|
+
const target = resolveInProject(fromFile, specifier);
|
|
8417
|
+
if (!target) return void 0;
|
|
8418
|
+
const mod = load(target);
|
|
8419
|
+
if (!mod) return void 0;
|
|
8420
|
+
const pkg = npmPackageOf(mod.bindings.resolve(exportName));
|
|
8421
|
+
if (pkg === void 0) return void 0;
|
|
8422
|
+
return {
|
|
8423
|
+
package: pkg,
|
|
8424
|
+
origin: mod.bindings.originOf(exportName) ?? "direct",
|
|
8425
|
+
name: mod.bindings.exportNameOf(exportName) ?? exportName
|
|
8426
|
+
};
|
|
8427
|
+
}
|
|
8428
|
+
};
|
|
8429
|
+
}
|
|
8430
|
+
function resolveRelativeModule(fromFile, spec) {
|
|
8431
|
+
if (!spec.startsWith(".")) return void 0;
|
|
8432
|
+
const dir = dirname8(fromFile);
|
|
8433
|
+
const base = resolvePath(dir, spec);
|
|
8434
|
+
const candidates = [];
|
|
8435
|
+
const jsLike = /\.(js|jsx|mjs|cjs)$/.exec(base);
|
|
8436
|
+
if (jsLike) {
|
|
8437
|
+
const stem = base.slice(0, -jsLike[0].length);
|
|
8438
|
+
for (const e of RESOLVE_EXTS) candidates.push(stem + e);
|
|
8439
|
+
}
|
|
8440
|
+
candidates.push(base);
|
|
8441
|
+
for (const e of RESOLVE_EXTS) candidates.push(base + e);
|
|
8442
|
+
for (const e of RESOLVE_EXTS) candidates.push(join16(base, "index" + e));
|
|
8443
|
+
for (const c of candidates) {
|
|
8444
|
+
try {
|
|
8445
|
+
if (statSync7(c).isFile()) return c;
|
|
8446
|
+
} catch {
|
|
8447
|
+
}
|
|
8448
|
+
}
|
|
8449
|
+
return void 0;
|
|
8450
|
+
}
|
|
8451
|
+
function collectCallees(sf, ts) {
|
|
8452
|
+
const map = /* @__PURE__ */ new Map();
|
|
8453
|
+
const visit = (node) => {
|
|
8454
|
+
let name;
|
|
8455
|
+
let body;
|
|
8456
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.body) {
|
|
8457
|
+
name = node.name.text;
|
|
8458
|
+
body = node.body;
|
|
8459
|
+
} else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && isFnLike(node.initializer, ts)) {
|
|
8460
|
+
name = node.name.text;
|
|
8461
|
+
body = node.initializer.body;
|
|
8462
|
+
}
|
|
8463
|
+
if (name && body) map.set(name, localCalls(body, ts));
|
|
8464
|
+
ts.forEachChild(node, visit);
|
|
8465
|
+
};
|
|
8466
|
+
visit(sf);
|
|
8467
|
+
return map;
|
|
8036
8468
|
}
|
|
8037
8469
|
|
|
8038
8470
|
// src/map/invocations.ts
|
|
@@ -8154,6 +8586,7 @@ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELE
|
|
|
8154
8586
|
function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
8155
8587
|
const out = [];
|
|
8156
8588
|
const isServerActionsFile = fileHasUseServer(sf, ts);
|
|
8589
|
+
const pagesApi = isPagesApiFile(ctx.owner);
|
|
8157
8590
|
const visit = (node) => {
|
|
8158
8591
|
if (ts.isVariableStatement(node) && hasExport(node, ts)) {
|
|
8159
8592
|
for (const decl of node.declarationList.declarations) {
|
|
@@ -8195,6 +8628,12 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
|
8195
8628
|
out.push(handlerEntry(node.name.text, "server-action", node.parameters, node.body, ts, localSinks, bindings, ctx, spanOf(node)));
|
|
8196
8629
|
}
|
|
8197
8630
|
}
|
|
8631
|
+
if (pagesApi) {
|
|
8632
|
+
const handler = defaultExportedHandler(node, ts);
|
|
8633
|
+
if (handler) {
|
|
8634
|
+
out.push(handlerEntry(handler.name ?? "default", "default-export", handler.fn.parameters, handler.fn.body, ts, localSinks, bindings, ctx, spanOf(handler.fn)));
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
8198
8637
|
if (ts.isCallExpression(node)) {
|
|
8199
8638
|
const c = node.expression;
|
|
8200
8639
|
const denoServe = ts.isPropertyAccessExpression(c) && c.name.text === "serve" && ts.isIdentifier(c.expression) && c.expression.text === "Deno";
|
|
@@ -8213,7 +8652,7 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
|
8213
8652
|
const first = args[0];
|
|
8214
8653
|
const route = first && ts.isStringLiteralLike(first) ? first.text : routeFromChain(node.expression.expression, ts);
|
|
8215
8654
|
const handler = args[args.length - 1];
|
|
8216
|
-
if (route !== void 0 && handler && isFnLike(handler, ts)) {
|
|
8655
|
+
if (route !== void 0 && isRoutePath(route) && handler && isFnLike(handler, ts)) {
|
|
8217
8656
|
out.push(handlerEntry(route, "route-registration", handler.parameters, handler.body, ts, localSinks, bindings, ctx, {
|
|
8218
8657
|
// `use`/`all` register handlers but are not HTTP methods — leave method undefined.
|
|
8219
8658
|
method: HTTP_METHODS.has(mname.toUpperCase()) ? mname.toUpperCase() : void 0,
|
|
@@ -8226,7 +8665,7 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
|
8226
8665
|
const arg = node.arguments[0];
|
|
8227
8666
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
8228
8667
|
const reg = routeObject(arg, ts);
|
|
8229
|
-
if (reg.url && reg.handler) {
|
|
8668
|
+
if (reg.url && isRoutePath(reg.url) && reg.handler) {
|
|
8230
8669
|
for (const m of reg.methods.length ? reg.methods : [void 0]) {
|
|
8231
8670
|
out.push(handlerEntry(reg.url, "route-registration", reg.handler.parameters, reg.handler.body, ts, localSinks, bindings, ctx, { method: m, route: reg.url, ...spanOf(node) }));
|
|
8232
8671
|
}
|
|
@@ -8239,6 +8678,19 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
|
8239
8678
|
visit(sf);
|
|
8240
8679
|
return out;
|
|
8241
8680
|
}
|
|
8681
|
+
function defaultExportedHandler(node, ts) {
|
|
8682
|
+
const isDefault = (n) => Boolean(n.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword));
|
|
8683
|
+
if (ts.isFunctionDeclaration(node) && hasExport(node, ts) && isDefault(node) && node.body) return { name: node.name?.text, fn: node };
|
|
8684
|
+
if (!ts.isExportAssignment(node) || node.isExportEquals) return void 0;
|
|
8685
|
+
const target = node.expression;
|
|
8686
|
+
if (isFnLike(target, ts)) return { fn: target };
|
|
8687
|
+
if (!ts.isIdentifier(target)) return void 0;
|
|
8688
|
+
const declaration = declarationOf(target, ts);
|
|
8689
|
+
const owner = declaration?.parent;
|
|
8690
|
+
if (owner && ts.isFunctionDeclaration(owner) && owner.body) return { name: target.text, fn: owner };
|
|
8691
|
+
if (owner && ts.isVariableDeclaration(owner) && owner.initializer && isFnLike(owner.initializer, ts)) return { name: target.text, fn: owner.initializer };
|
|
8692
|
+
return void 0;
|
|
8693
|
+
}
|
|
8242
8694
|
function fileHasUseServer(sf, ts) {
|
|
8243
8695
|
const first = sf.statements?.[0];
|
|
8244
8696
|
return Boolean(first && ts.isExpressionStatement(first) && ts.isStringLiteralLike(first.expression) && first.expression.text === "use server");
|
|
@@ -8254,7 +8706,7 @@ function handlerInvocations(body, ts, bindings, ctx) {
|
|
|
8254
8706
|
function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, ctx, extra = {}) {
|
|
8255
8707
|
const entryKind = kindLabel === "route-registration" || kindLabel === "server-action" || kindLabel === "edge-function" ? kindLabel : "route-handler";
|
|
8256
8708
|
const payloadStyle = kindLabel === "server-action";
|
|
8257
|
-
const inputs = inputsFromHandler(params, body, ts, bindings, {
|
|
8709
|
+
const { inputs, schemaUnresolved } = inputsFromHandler(params, body, ts, bindings, {
|
|
8258
8710
|
payloadParam: payloadStyle,
|
|
8259
8711
|
validatorSource: payloadStyle ? "server-fn-data" : "json-body"
|
|
8260
8712
|
});
|
|
@@ -8269,7 +8721,9 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
|
|
|
8269
8721
|
end: extra.end,
|
|
8270
8722
|
inputs,
|
|
8271
8723
|
sinks,
|
|
8272
|
-
...linkedFlows(body, params, inputs, sinks, ts, handlerInvocations(body, ts, bindings, ctx))
|
|
8724
|
+
...linkedFlows(body, params, inputs, sinks, ts, handlerInvocations(body, ts, bindings, ctx)),
|
|
8725
|
+
// The handler validates its request with a schema declared in another module: its fields are unknown.
|
|
8726
|
+
...schemaUnresolved ? { inputsResolved: false } : {}
|
|
8273
8727
|
};
|
|
8274
8728
|
}
|
|
8275
8729
|
|
|
@@ -8528,6 +8982,7 @@ function lineAt(starts, pos) {
|
|
|
8528
8982
|
|
|
8529
8983
|
// src/map/extract.ts
|
|
8530
8984
|
var MAX_DEPENDENCY_INPUT_FLOWS_PER_MAP = 500;
|
|
8985
|
+
var MAX_UNPROVEN_FLOWS_PER_MAP = 1e3;
|
|
8531
8986
|
async function extractInputMap(cwd, ts, options = {}) {
|
|
8532
8987
|
const notes = [];
|
|
8533
8988
|
const endpoints = [];
|
|
@@ -8543,9 +8998,11 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
8543
8998
|
const imports = createImportInventory(readPathAliases(cwd));
|
|
8544
8999
|
const invocations = createInvocationInventory();
|
|
8545
9000
|
let dependencyInputFlowCount = 0;
|
|
9001
|
+
let unprovenFlowCount = 0;
|
|
8546
9002
|
let parsed = 0;
|
|
8547
9003
|
let preFiltered = 0;
|
|
8548
9004
|
let importScanFailures = 0;
|
|
9005
|
+
let componentFiles = 0;
|
|
8549
9006
|
let unresolvableImports = 0;
|
|
8550
9007
|
let sourceBytes = 0;
|
|
8551
9008
|
const calls = { total: 0, dependency: 0, local: 0, ambiguous: 0 };
|
|
@@ -8557,7 +9014,17 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
8557
9014
|
const text = readFileSync16(file, "utf8");
|
|
8558
9015
|
const relFile = relative6(cwd, file);
|
|
8559
9016
|
sourceBytes += text.length;
|
|
8560
|
-
if (
|
|
9017
|
+
if (isComponentFile(file)) {
|
|
9018
|
+
componentFiles++;
|
|
9019
|
+
preFiltered++;
|
|
9020
|
+
const script = componentScript(text, file);
|
|
9021
|
+
const scanned = script === null ? null : scanFileImports(script, ts);
|
|
9022
|
+
if (scanned === null) importScanFailures++;
|
|
9023
|
+
else imports.add(relFile, scanned, false);
|
|
9024
|
+
if (script !== null) unresolvableImports += countUnresolvableImports(script, ts);
|
|
9025
|
+
continue;
|
|
9026
|
+
}
|
|
9027
|
+
if (!hasEntrySignal(text) && !isPagesApiFile(relFile)) {
|
|
8561
9028
|
preFiltered++;
|
|
8562
9029
|
const scanned = scanFileImports(text, ts);
|
|
8563
9030
|
if (scanned === null) importScanFailures++;
|
|
@@ -8593,11 +9060,17 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
8593
9060
|
ep.dependencyInputFlowsTruncated = true;
|
|
8594
9061
|
}
|
|
8595
9062
|
dependencyInputFlowCount += ep.dependencyInputFlows?.length ?? 0;
|
|
9063
|
+
const bounded = boundUnprovenFlows(ep.flows, Math.max(0, MAX_UNPROVEN_FLOWS_PER_MAP - unprovenFlowCount));
|
|
9064
|
+
if (bounded.truncated) {
|
|
9065
|
+
ep.flows = bounded.flows;
|
|
9066
|
+
ep.flowsTruncated = true;
|
|
9067
|
+
}
|
|
9068
|
+
unprovenFlowCount += ep.flows.filter((f) => !isProvenFlow(f.confidence)).length;
|
|
8596
9069
|
if (ep.route === void 0 && ep.entryKind === "edge-function") {
|
|
8597
9070
|
const fn = functionNameFromPath(relFile);
|
|
8598
9071
|
if (fn) ep.route = "/" + fn;
|
|
8599
9072
|
}
|
|
8600
|
-
if (ep.route === void 0 &&
|
|
9073
|
+
if (ep.route === void 0 && ep.entryKind === "route-handler") {
|
|
8601
9074
|
const derived = routeFromFilePath(relFile);
|
|
8602
9075
|
if (derived.route) {
|
|
8603
9076
|
ep.route = derived.route;
|
|
@@ -8648,6 +9121,10 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
8648
9121
|
if (truncatedDependencyFlows > 0) {
|
|
8649
9122
|
notes.push(`${truncatedDependencyFlows} endpoint(s) had more dependency-input links than the bounded map carries; those endpoint records are marked dependencyInputFlowsTruncated.`);
|
|
8650
9123
|
}
|
|
9124
|
+
const truncatedFlows = endpoints.filter((e) => e.flowsTruncated).length;
|
|
9125
|
+
if (truncatedFlows > 0) {
|
|
9126
|
+
notes.push(`${truncatedFlows} endpoint(s) had more unproven flows than the bounded map carries; those endpoint records are marked flowsTruncated. Every proven flow is kept.`);
|
|
9127
|
+
}
|
|
8651
9128
|
if (unresolved > 0) {
|
|
8652
9129
|
notes.push(`${unresolved} endpoint(s) declare an input validator that could not be statically parsed \u2014 their inputs are UNKNOWN, not empty (marked inputsResolved: false).`);
|
|
8653
9130
|
}
|
|
@@ -8655,6 +9132,9 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
8655
9132
|
if (heuristicOnly > 0) {
|
|
8656
9133
|
notes.push(`${heuristicOnly} endpoint(s) have inputs and sinks but no proven data link \u2014 their flows say "may reach", not "does reach" (see each flow's confidence).`);
|
|
8657
9134
|
}
|
|
9135
|
+
if (componentFiles > 0) {
|
|
9136
|
+
notes.push(`${componentFiles} single-file component(s) (.vue, .svelte, .astro) were scanned for imports only. Code in their script blocks and Astro frontmatter can run on the server, but it is not analyzed for endpoints, inputs or sinks.`);
|
|
9137
|
+
}
|
|
8658
9138
|
if (endpoints.length === 0) notes.push("No recognized server-side entry points found under the analyzed roots.");
|
|
8659
9139
|
const importList = imports.list();
|
|
8660
9140
|
const unmodelled = importList.filter((d) => d.recognizedSinkKinds.length === 0).length;
|