circle-ir 3.168.0 → 3.174.0
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/configs/sinks/prompt_injection.yaml +50 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +157 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +3 -2
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +3 -2
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +2 -1
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +29 -15
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/rules.d.ts.map +1 -1
- package/dist/analysis/rules.js +9 -0
- package/dist/analysis/rules.js.map +1 -1
- package/dist/analysis/sanitizer-index.d.ts +34 -0
- package/dist/analysis/sanitizer-index.d.ts.map +1 -0
- package/dist/analysis/sanitizer-index.js +48 -0
- package/dist/analysis/sanitizer-index.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +93 -45
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +5 -2
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +51 -2
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +336 -95
- package/dist/core/circle-ir-core.cjs +272 -73
- package/dist/core/circle-ir-core.js +272 -73
- package/dist/core/extractors/cfg.d.ts +11 -1
- package/dist/core/extractors/cfg.d.ts.map +1 -1
- package/dist/core/extractors/cfg.js +18 -9
- package/dist/core/extractors/cfg.js.map +1 -1
- package/dist/core/extractors/imports.d.ts +9 -1
- package/dist/core/extractors/imports.d.ts.map +1 -1
- package/dist/core/extractors/imports.js +29 -22
- package/dist/core/extractors/imports.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -7497,33 +7497,33 @@ function detectLanguage2(tree) {
|
|
|
7497
7497
|
if (pythonScore > jsScore && pythonScore > javaScore) return "python";
|
|
7498
7498
|
return jsScore > javaScore ? "javascript" : "java";
|
|
7499
7499
|
}
|
|
7500
|
-
function extractImports(tree, language) {
|
|
7500
|
+
function extractImports(tree, language, cache) {
|
|
7501
7501
|
const effectiveLanguage = language ?? detectLanguage2(tree);
|
|
7502
7502
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
7503
7503
|
const isPython = effectiveLanguage === "python";
|
|
7504
7504
|
const isRust = effectiveLanguage === "rust";
|
|
7505
7505
|
if (effectiveLanguage === "go") {
|
|
7506
|
-
return extractGoImports(tree);
|
|
7506
|
+
return extractGoImports(tree, cache);
|
|
7507
7507
|
}
|
|
7508
7508
|
if (isRust) {
|
|
7509
|
-
return extractRustImports(tree);
|
|
7509
|
+
return extractRustImports(tree, cache);
|
|
7510
7510
|
}
|
|
7511
7511
|
if (isPython) {
|
|
7512
|
-
return extractPythonImports(tree);
|
|
7512
|
+
return extractPythonImports(tree, cache);
|
|
7513
7513
|
}
|
|
7514
7514
|
if (isJavaScript) {
|
|
7515
|
-
return extractJavaScriptImports(tree);
|
|
7515
|
+
return extractJavaScriptImports(tree, cache);
|
|
7516
7516
|
}
|
|
7517
|
-
return extractJavaImports(tree);
|
|
7517
|
+
return extractJavaImports(tree, cache);
|
|
7518
7518
|
}
|
|
7519
|
-
function extractJavaScriptImports(tree) {
|
|
7519
|
+
function extractJavaScriptImports(tree, cache) {
|
|
7520
7520
|
const imports = [];
|
|
7521
|
-
const importStatements =
|
|
7521
|
+
const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
|
|
7522
7522
|
for (const importStmt of importStatements) {
|
|
7523
7523
|
const importInfos = extractJSImportInfo(importStmt);
|
|
7524
7524
|
imports.push(...importInfos);
|
|
7525
7525
|
}
|
|
7526
|
-
const exportStatements =
|
|
7526
|
+
const exportStatements = getNodesFromCache(tree.rootNode, "export_statement", cache);
|
|
7527
7527
|
for (const exportStmt of exportStatements) {
|
|
7528
7528
|
const sourceNode = exportStmt.childForFieldName("source");
|
|
7529
7529
|
if (!sourceNode) continue;
|
|
@@ -7537,13 +7537,13 @@ function extractJavaScriptImports(tree) {
|
|
|
7537
7537
|
line_number: exportStmt.startPosition.row + 1
|
|
7538
7538
|
});
|
|
7539
7539
|
}
|
|
7540
|
-
const requireCalls = findRequireCalls(tree);
|
|
7540
|
+
const requireCalls = findRequireCalls(tree, cache);
|
|
7541
7541
|
imports.push(...requireCalls);
|
|
7542
7542
|
return imports;
|
|
7543
7543
|
}
|
|
7544
|
-
function extractJavaImports(tree) {
|
|
7544
|
+
function extractJavaImports(tree, cache) {
|
|
7545
7545
|
const imports = [];
|
|
7546
|
-
const importDecls =
|
|
7546
|
+
const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
7547
7547
|
for (const importDecl of importDecls) {
|
|
7548
7548
|
const importInfo = extractJavaImportInfo(importDecl);
|
|
7549
7549
|
if (importInfo) {
|
|
@@ -7691,9 +7691,9 @@ function extractJSImportInfo(node) {
|
|
|
7691
7691
|
}
|
|
7692
7692
|
return imports;
|
|
7693
7693
|
}
|
|
7694
|
-
function findRequireCalls(tree) {
|
|
7694
|
+
function findRequireCalls(tree, cache) {
|
|
7695
7695
|
const imports = [];
|
|
7696
|
-
const callExpressions =
|
|
7696
|
+
const callExpressions = getNodesFromCache(tree.rootNode, "call_expression", cache);
|
|
7697
7697
|
for (const call of callExpressions) {
|
|
7698
7698
|
const funcNode = call.childForFieldName("function");
|
|
7699
7699
|
if (!funcNode || getNodeText(funcNode) !== "require") continue;
|
|
@@ -7807,14 +7807,14 @@ function parseImportPath(fullPath, isWildcard) {
|
|
|
7807
7807
|
fromPackage: fullPath.substring(0, lastDot)
|
|
7808
7808
|
};
|
|
7809
7809
|
}
|
|
7810
|
-
function extractPythonImports(tree) {
|
|
7810
|
+
function extractPythonImports(tree, cache) {
|
|
7811
7811
|
const imports = [];
|
|
7812
|
-
const importStatements =
|
|
7812
|
+
const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
|
|
7813
7813
|
for (const stmt of importStatements) {
|
|
7814
7814
|
const importInfos = extractPythonImportStatement(stmt);
|
|
7815
7815
|
imports.push(...importInfos);
|
|
7816
7816
|
}
|
|
7817
|
-
const importFromStatements =
|
|
7817
|
+
const importFromStatements = getNodesFromCache(tree.rootNode, "import_from_statement", cache);
|
|
7818
7818
|
for (const stmt of importFromStatements) {
|
|
7819
7819
|
const importInfos = extractPythonFromImportStatement(stmt);
|
|
7820
7820
|
imports.push(...importInfos);
|
|
@@ -7908,9 +7908,9 @@ function extractPythonFromImportStatement(node) {
|
|
|
7908
7908
|
}
|
|
7909
7909
|
return imports;
|
|
7910
7910
|
}
|
|
7911
|
-
function extractRustImports(tree) {
|
|
7911
|
+
function extractRustImports(tree, cache) {
|
|
7912
7912
|
const imports = [];
|
|
7913
|
-
const useDecls =
|
|
7913
|
+
const useDecls = getNodesFromCache(tree.rootNode, "use_declaration", cache);
|
|
7914
7914
|
for (const useDecl of useDecls) {
|
|
7915
7915
|
const useImports = extractRustUseDecl(useDecl);
|
|
7916
7916
|
imports.push(...useImports);
|
|
@@ -8052,9 +8052,9 @@ function extractRustScopedUseList(node, lineNumber) {
|
|
|
8052
8052
|
}
|
|
8053
8053
|
return imports;
|
|
8054
8054
|
}
|
|
8055
|
-
function extractGoImports(tree) {
|
|
8055
|
+
function extractGoImports(tree, cache) {
|
|
8056
8056
|
const imports = [];
|
|
8057
|
-
const importDecls =
|
|
8057
|
+
const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
8058
8058
|
for (const decl of importDecls) {
|
|
8059
8059
|
const singleSpec = findGoChildByType(decl, "import_spec");
|
|
8060
8060
|
if (singleSpec) {
|
|
@@ -8189,7 +8189,7 @@ function detectLanguage3(tree) {
|
|
|
8189
8189
|
}
|
|
8190
8190
|
return jsScore > javaScore ? "javascript" : "java";
|
|
8191
8191
|
}
|
|
8192
|
-
function buildCFG(tree, language) {
|
|
8192
|
+
function buildCFG(tree, language, cache) {
|
|
8193
8193
|
const effectiveLanguage = language ?? detectLanguage3(tree);
|
|
8194
8194
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
8195
8195
|
const allBlocks = [];
|
|
@@ -8203,11 +8203,11 @@ function buildCFG(tree, language) {
|
|
|
8203
8203
|
}
|
|
8204
8204
|
if (isJavaScript) {
|
|
8205
8205
|
const functions = [
|
|
8206
|
-
...
|
|
8207
|
-
...
|
|
8208
|
-
...
|
|
8209
|
-
...
|
|
8210
|
-
...
|
|
8206
|
+
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
8207
|
+
...getNodesFromCache(tree.rootNode, "arrow_function", cache),
|
|
8208
|
+
...getNodesFromCache(tree.rootNode, "method_definition", cache),
|
|
8209
|
+
...getNodesFromCache(tree.rootNode, "function", cache),
|
|
8210
|
+
...getNodesFromCache(tree.rootNode, "function_expression", cache)
|
|
8211
8211
|
];
|
|
8212
8212
|
for (const func2 of functions) {
|
|
8213
8213
|
const body2 = func2.childForFieldName("body");
|
|
@@ -8229,8 +8229,8 @@ function buildCFG(tree, language) {
|
|
|
8229
8229
|
}
|
|
8230
8230
|
} else {
|
|
8231
8231
|
const methods = [
|
|
8232
|
-
...
|
|
8233
|
-
...
|
|
8232
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8233
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache)
|
|
8234
8234
|
];
|
|
8235
8235
|
for (const method of methods) {
|
|
8236
8236
|
const body2 = method.childForFieldName("body");
|
|
@@ -11134,7 +11134,86 @@ var DEFAULT_SOURCES = [
|
|
|
11134
11134
|
{ method: "recv", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11135
11135
|
{ method: "read", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11136
11136
|
{ method: "read_to_end", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11137
|
-
{ method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true }
|
|
11137
|
+
{ method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11138
|
+
// =========================================================================
|
|
11139
|
+
// Modern taint sources (cognium-dev #242 — 3.170.0)
|
|
11140
|
+
// GraphQL resolver args, gRPC request metadata, cache reads (second-order
|
|
11141
|
+
// taint), and JWT claims (unverified decode). These attacker-influenced
|
|
11142
|
+
// surfaces were previously silent — a resolver that concatenated an
|
|
11143
|
+
// `Args`-annotated field into a SQL string produced zero flows.
|
|
11144
|
+
// =========================================================================
|
|
11145
|
+
// --- GraphQL resolver argument sources (JS / TS — Apollo, TypeGraphQL, NestJS) ---
|
|
11146
|
+
// TypeGraphQL / NestJS annotate query/mutation/subscription methods; every
|
|
11147
|
+
// parameter on the annotated method carries attacker input from the GraphQL
|
|
11148
|
+
// POST body's `variables` field.
|
|
11149
|
+
{ method_annotation: "Query", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11150
|
+
{ method_annotation: "Mutation", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11151
|
+
{ method_annotation: "Subscription", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11152
|
+
{ method_annotation: "FieldResolver", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11153
|
+
{ method_annotation: "ResolveField", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11154
|
+
// Parameter-level: `@Arg('id') id: string`, `@Args('input') input: FooDto`.
|
|
11155
|
+
{ annotation: "Arg", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
|
|
11156
|
+
{ annotation: "Args", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
|
|
11157
|
+
// --- GraphQL resolver argument sources (Python — Strawberry, Graphene, Ariadne) ---
|
|
11158
|
+
{ method_annotation: "strawberry.field", type: "http_body", severity: "high", languages: ["python"] },
|
|
11159
|
+
{ method_annotation: "strawberry.mutation", type: "http_body", severity: "high", languages: ["python"] },
|
|
11160
|
+
{ method_annotation: "strawberry.subscription", type: "http_body", severity: "high", languages: ["python"] },
|
|
11161
|
+
// --- GraphQL resolver argument sources (Java — Netflix DGS, SPQR, graphql-java-annotations) ---
|
|
11162
|
+
{ method_annotation: "DgsQuery", type: "http_body", severity: "high", languages: ["java"] },
|
|
11163
|
+
{ method_annotation: "DgsMutation", type: "http_body", severity: "high", languages: ["java"] },
|
|
11164
|
+
{ method_annotation: "DgsSubscription", type: "http_body", severity: "high", languages: ["java"] },
|
|
11165
|
+
{ method_annotation: "DgsData", type: "http_body", severity: "high", languages: ["java"] },
|
|
11166
|
+
{ method_annotation: "GraphQLQuery", type: "http_body", severity: "high", languages: ["java"] },
|
|
11167
|
+
{ method_annotation: "GraphQLMutation", type: "http_body", severity: "high", languages: ["java"] },
|
|
11168
|
+
{ annotation: "InputArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11169
|
+
{ annotation: "GraphQLArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11170
|
+
// --- gRPC request metadata (Python — grpcio) ---
|
|
11171
|
+
// `context.invocation_metadata()` returns the caller-supplied metadata tuple.
|
|
11172
|
+
// No `class` filter — receiver names vary (`context`, `ctx`, `servicer_context`).
|
|
11173
|
+
{ method: "invocation_metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11174
|
+
// --- gRPC request metadata (JS / TS — @grpc/grpc-js) ---
|
|
11175
|
+
// `call.metadata.get(key)` / `call.metadata.getMap()` inside a service handler.
|
|
11176
|
+
{ method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11177
|
+
{ method: "getMap", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11178
|
+
// --- gRPC request metadata (Go — google.golang.org/grpc/metadata) ---
|
|
11179
|
+
// `md, _ := metadata.FromIncomingContext(ctx)` pulls the caller's headers.
|
|
11180
|
+
{ method: "FromIncomingContext", class: "metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] },
|
|
11181
|
+
// --- gRPC request metadata (Java — io.grpc.Metadata) ---
|
|
11182
|
+
// Server-side interceptor receives `Metadata headers`; `headers.get(KEY)`
|
|
11183
|
+
// returns caller-supplied header values.
|
|
11184
|
+
{ method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
11185
|
+
// --- Cache reads (second-order taint — Redis / Memcached / Django cache) ---
|
|
11186
|
+
// The cache round-trip is a canonical second-order sink: whatever was
|
|
11187
|
+
// written previously (potentially attacker-controlled) resurfaces on read.
|
|
11188
|
+
// Severity 'medium' — cache contents are usually filtered by the writer but
|
|
11189
|
+
// the read side often forgets that guarantee. Class-scoped to `Redis`,
|
|
11190
|
+
// `Jedis`, `cache` to avoid colliding with generic `Map.get()`.
|
|
11191
|
+
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11192
|
+
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11193
|
+
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11194
|
+
{ method: "lrange", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11195
|
+
{ method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11196
|
+
{ method: "get_many", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11197
|
+
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11198
|
+
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11199
|
+
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11200
|
+
{ method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
11201
|
+
{ method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
11202
|
+
{ method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
11203
|
+
// --- JWT claims (unverified decode — PyJWT / jose / jsonwebtoken / auth0 java-jwt / golang-jwt) ---
|
|
11204
|
+
// A JWT's payload is *always* attacker-authored. Even after verification
|
|
11205
|
+
// the *contents* of the claims (username, role, custom fields) are not
|
|
11206
|
+
// trusted for downstream flows into SQL, HTML, shell, etc. The `decode`
|
|
11207
|
+
// variants here return the parsed claims dictionary/object.
|
|
11208
|
+
{ method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11209
|
+
{ method: "get_unverified_claims", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11210
|
+
{ method: "get_unverified_header", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11211
|
+
{ method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11212
|
+
{ method: "decode", class: "jsonwebtoken", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11213
|
+
{ method: "decodeJwt", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11214
|
+
{ method: "decodeProtectedHeader", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11215
|
+
{ method: "decode", class: "JWT", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
11216
|
+
{ method: "ParseUnverified", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] }
|
|
11138
11217
|
];
|
|
11139
11218
|
var DEFAULT_SINKS = [
|
|
11140
11219
|
// SQL Injection (CWE-89)
|
|
@@ -12703,7 +12782,85 @@ var DEFAULT_SINKS = [
|
|
|
12703
12782
|
// they iterate after net/http entries and don't hijack `http.Get`.
|
|
12704
12783
|
{ method: "Do", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
12705
12784
|
// *fasthttp.Client.Do(req)
|
|
12706
|
-
{ method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] }
|
|
12785
|
+
{ method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
12786
|
+
// =========================================================================
|
|
12787
|
+
// cognium-dev #248 — prompt-injection sinks (CWE-1427)
|
|
12788
|
+
//
|
|
12789
|
+
// Tainted data reaching a generative-model prompt-construction API is
|
|
12790
|
+
// classified as `prompt_injection`. v1 uses broad positional matching
|
|
12791
|
+
// (arg_positions [0..3]) because these APIs are kwarg-heavy: Python
|
|
12792
|
+
// `openai.chat.completions.create(model=..., messages=...)` may pass the
|
|
12793
|
+
// messages arg at position 0 or 1 depending on caller order, and JS/TS
|
|
12794
|
+
// object-literal `{ messages: [...], model: '...' }` is a single
|
|
12795
|
+
// positional arg whose taint is inherited from any tainted property.
|
|
12796
|
+
// Argname-precise matching (messages=/prompt=/content=) and
|
|
12797
|
+
// sanitizer credit for prompt-template libraries (PromptTemplate,
|
|
12798
|
+
// ChatPromptTemplate) are follow-ups.
|
|
12799
|
+
//
|
|
12800
|
+
// Class-qualified entries prevent bare `create()` / `generate()` calls
|
|
12801
|
+
// with no receiver from matching (per taint-matcher.ts:1696 guard).
|
|
12802
|
+
// =========================================================================
|
|
12803
|
+
// --- Python: openai (v1 SDK) --------------------------------------------
|
|
12804
|
+
{ method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12805
|
+
{ method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12806
|
+
// openai <1.0 legacy top-level API — `openai.ChatCompletion.create(...)` / `openai.Completion.create(...)`
|
|
12807
|
+
{ method: "create", class: "ChatCompletion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12808
|
+
{ method: "create", class: "Completion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12809
|
+
// --- Python: anthropic ---------------------------------------------------
|
|
12810
|
+
{ method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12811
|
+
{ method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12812
|
+
// --- Python: litellm (bare functions from the litellm module) -----------
|
|
12813
|
+
{ method: "completion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12814
|
+
{ method: "acompletion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12815
|
+
// --- Python: langchain chat/llm models ----------------------------------
|
|
12816
|
+
// Common concrete classes; matching the class name catches typical usage
|
|
12817
|
+
// `ChatOpenAI().invoke(prompt)` where the receiver-type resolver identifies
|
|
12818
|
+
// the class from the constructor.
|
|
12819
|
+
{ method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12820
|
+
{ method: "predict", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12821
|
+
{ method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12822
|
+
{ method: "generate", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12823
|
+
{ method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12824
|
+
{ method: "invoke", class: "ChatGoogleGenerativeAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12825
|
+
{ method: "run", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12826
|
+
{ method: "invoke", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12827
|
+
// --- JS/TS: openai node SDK ---------------------------------------------
|
|
12828
|
+
{ method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12829
|
+
{ method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12830
|
+
// --- JS/TS: @anthropic-ai/sdk -------------------------------------------
|
|
12831
|
+
{ method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12832
|
+
{ method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12833
|
+
// --- JS/TS: Vercel AI SDK (bare functions from the `ai` package) --------
|
|
12834
|
+
// generateText/streamText/generateObject take a single options object;
|
|
12835
|
+
// taint reaches the sink when a tainted variable flows into any property.
|
|
12836
|
+
{ method: "generateText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12837
|
+
{ method: "streamText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12838
|
+
{ method: "generateObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12839
|
+
{ method: "streamObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12840
|
+
// --- JS/TS: langchain.js ------------------------------------------------
|
|
12841
|
+
{ method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12842
|
+
{ method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12843
|
+
{ method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12844
|
+
// --- Java: LangChain4j --------------------------------------------------
|
|
12845
|
+
{ method: "generate", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12846
|
+
{ method: "chat", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12847
|
+
{ method: "generate", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12848
|
+
{ method: "chat", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12849
|
+
// --- Java: Spring AI ----------------------------------------------------
|
|
12850
|
+
{ method: "prompt", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12851
|
+
{ method: "call", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12852
|
+
{ method: "call", class: "ChatModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12853
|
+
// --- Java: OpenAI Java SDK (theokanning) --------------------------------
|
|
12854
|
+
{ method: "createChatCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12855
|
+
{ method: "createCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12856
|
+
// --- Go: go-openai (sashabaranov/go-openai) -----------------------------
|
|
12857
|
+
{ method: "CreateChatCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12858
|
+
{ method: "CreateCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12859
|
+
{ method: "CreateChatCompletionStream", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12860
|
+
// --- Go: langchaingo ----------------------------------------------------
|
|
12861
|
+
{ method: "Call", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12862
|
+
{ method: "Generate", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12863
|
+
{ method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] }
|
|
12707
12864
|
];
|
|
12708
12865
|
var DEFAULT_SANITIZERS = [
|
|
12709
12866
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -13274,6 +13431,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13274
13431
|
for (const param of method.parameters) {
|
|
13275
13432
|
for (const pattern of patterns) {
|
|
13276
13433
|
if (pattern.annotation && pattern.param_tainted) {
|
|
13434
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13435
|
+
continue;
|
|
13436
|
+
}
|
|
13277
13437
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
13278
13438
|
const paramLine = param.line ?? method.start_line;
|
|
13279
13439
|
sources.push({
|
|
@@ -13294,6 +13454,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13294
13454
|
for (const method of type.methods) {
|
|
13295
13455
|
for (const pattern of patterns) {
|
|
13296
13456
|
if (!pattern.method_annotation) continue;
|
|
13457
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13458
|
+
continue;
|
|
13459
|
+
}
|
|
13297
13460
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
13298
13461
|
for (const param of method.parameters) {
|
|
13299
13462
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -13558,7 +13721,7 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
13558
13721
|
const secondArg = call.arguments.find((a) => a.position === 1);
|
|
13559
13722
|
if (secondArg?.expression) {
|
|
13560
13723
|
const expr = secondArg.expression.trim();
|
|
13561
|
-
if (expr.startsWith("[")) {
|
|
13724
|
+
if (expr.startsWith("[") && !/^\[\s*\]$/.test(expr)) {
|
|
13562
13725
|
return true;
|
|
13563
13726
|
}
|
|
13564
13727
|
}
|
|
@@ -13999,52 +14162,74 @@ function matchesSourcePattern(call, pattern) {
|
|
|
13999
14162
|
}
|
|
14000
14163
|
return false;
|
|
14001
14164
|
}
|
|
14165
|
+
var JS_SOURCE_PATTERN_CACHE = /* @__PURE__ */ new WeakMap();
|
|
14166
|
+
var JS_FALLBACK_COMPILED = (() => {
|
|
14167
|
+
const bases = [
|
|
14168
|
+
{ base: "req\\.params", sourceType: "http_param" },
|
|
14169
|
+
{ base: "req\\.query", sourceType: "http_param" },
|
|
14170
|
+
{ base: "req\\.body", sourceType: "http_body" },
|
|
14171
|
+
{ base: "req\\.headers", sourceType: "http_header" },
|
|
14172
|
+
{ base: "req\\.cookies", sourceType: "http_cookie" },
|
|
14173
|
+
{ base: "req\\.url", sourceType: "http_path" },
|
|
14174
|
+
{ base: "req\\.path", sourceType: "http_path" },
|
|
14175
|
+
{ base: "req\\.originalUrl", sourceType: "http_path" },
|
|
14176
|
+
{ base: "req\\.file", sourceType: "file_input" },
|
|
14177
|
+
{ base: "req\\.files", sourceType: "file_input" },
|
|
14178
|
+
{ base: "request\\.params", sourceType: "http_param" },
|
|
14179
|
+
{ base: "request\\.query", sourceType: "http_param" },
|
|
14180
|
+
{ base: "request\\.body", sourceType: "http_body" },
|
|
14181
|
+
{ base: "request\\.headers", sourceType: "http_header" },
|
|
14182
|
+
{ base: "process\\.env", sourceType: "env_input" },
|
|
14183
|
+
{ base: "process\\.argv", sourceType: "io_input" },
|
|
14184
|
+
{ base: "ctx\\.query", sourceType: "http_param" },
|
|
14185
|
+
{ base: "ctx\\.params", sourceType: "http_param" },
|
|
14186
|
+
{ base: "ctx\\.request", sourceType: "http_body" }
|
|
14187
|
+
];
|
|
14188
|
+
const exact = [];
|
|
14189
|
+
const contained = [];
|
|
14190
|
+
for (const { base, sourceType } of bases) {
|
|
14191
|
+
exact.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
|
|
14192
|
+
contained.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
|
|
14193
|
+
}
|
|
14194
|
+
return { exact, contained };
|
|
14195
|
+
})();
|
|
14196
|
+
function compileJsSourcePatterns(sourcePatterns) {
|
|
14197
|
+
const cached = JS_SOURCE_PATTERN_CACHE.get(sourcePatterns);
|
|
14198
|
+
if (cached) return cached;
|
|
14199
|
+
const exact = [];
|
|
14200
|
+
const contained = [];
|
|
14201
|
+
for (const sp of sourcePatterns) {
|
|
14202
|
+
if (sp.property && sp.object && sp.property_tainted) {
|
|
14203
|
+
exact.push({
|
|
14204
|
+
pattern: new RegExp(`^${sp.object}\\.${sp.property}\\b`),
|
|
14205
|
+
sourceType: sp.type
|
|
14206
|
+
});
|
|
14207
|
+
contained.push({
|
|
14208
|
+
pattern: new RegExp(`\\b${sp.object}\\.${sp.property}\\b`),
|
|
14209
|
+
sourceType: sp.type
|
|
14210
|
+
});
|
|
14211
|
+
}
|
|
14212
|
+
}
|
|
14213
|
+
const compiled = { exact, contained };
|
|
14214
|
+
JS_SOURCE_PATTERN_CACHE.set(sourcePatterns, compiled);
|
|
14215
|
+
return compiled;
|
|
14216
|
+
}
|
|
14002
14217
|
function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
const exactRegex = new RegExp(`^${sp.object}\\.${sp.property}\\b`);
|
|
14009
|
-
exactPatterns.push({ pattern: exactRegex, sourceType: sp.type });
|
|
14010
|
-
const containedRegex = new RegExp(`\\b${sp.object}\\.${sp.property}\\b`);
|
|
14011
|
-
containedPatterns.push({ pattern: containedRegex, sourceType: sp.type });
|
|
14012
|
-
}
|
|
14013
|
-
}
|
|
14014
|
-
}
|
|
14015
|
-
if (exactPatterns.length === 0) {
|
|
14016
|
-
const basePatterns = [
|
|
14017
|
-
{ base: "req\\.params", sourceType: "http_param" },
|
|
14018
|
-
{ base: "req\\.query", sourceType: "http_param" },
|
|
14019
|
-
{ base: "req\\.body", sourceType: "http_body" },
|
|
14020
|
-
{ base: "req\\.headers", sourceType: "http_header" },
|
|
14021
|
-
{ base: "req\\.cookies", sourceType: "http_cookie" },
|
|
14022
|
-
{ base: "req\\.url", sourceType: "http_path" },
|
|
14023
|
-
{ base: "req\\.path", sourceType: "http_path" },
|
|
14024
|
-
{ base: "req\\.originalUrl", sourceType: "http_path" },
|
|
14025
|
-
{ base: "req\\.file", sourceType: "file_input" },
|
|
14026
|
-
{ base: "req\\.files", sourceType: "file_input" },
|
|
14027
|
-
{ base: "request\\.params", sourceType: "http_param" },
|
|
14028
|
-
{ base: "request\\.query", sourceType: "http_param" },
|
|
14029
|
-
{ base: "request\\.body", sourceType: "http_body" },
|
|
14030
|
-
{ base: "request\\.headers", sourceType: "http_header" },
|
|
14031
|
-
{ base: "process\\.env", sourceType: "env_input" },
|
|
14032
|
-
{ base: "process\\.argv", sourceType: "io_input" },
|
|
14033
|
-
{ base: "ctx\\.query", sourceType: "http_param" },
|
|
14034
|
-
{ base: "ctx\\.params", sourceType: "http_param" },
|
|
14035
|
-
{ base: "ctx\\.request", sourceType: "http_body" }
|
|
14036
|
-
];
|
|
14037
|
-
for (const { base, sourceType } of basePatterns) {
|
|
14038
|
-
exactPatterns.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
|
|
14039
|
-
containedPatterns.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
|
|
14218
|
+
let compiled;
|
|
14219
|
+
if (sourcePatterns && sourcePatterns.length > 0) {
|
|
14220
|
+
compiled = compileJsSourcePatterns(sourcePatterns);
|
|
14221
|
+
if (compiled.exact.length === 0) {
|
|
14222
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
14040
14223
|
}
|
|
14224
|
+
} else {
|
|
14225
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
14041
14226
|
}
|
|
14042
|
-
for (const { pattern, sourceType } of
|
|
14227
|
+
for (const { pattern, sourceType } of compiled.exact) {
|
|
14043
14228
|
if (pattern.test(argExpression)) {
|
|
14044
14229
|
return { isTainted: true, sourceType };
|
|
14045
14230
|
}
|
|
14046
14231
|
}
|
|
14047
|
-
for (const { pattern, sourceType } of
|
|
14232
|
+
for (const { pattern, sourceType } of compiled.contained) {
|
|
14048
14233
|
if (pattern.test(argExpression)) {
|
|
14049
14234
|
return { isTainted: true, sourceType };
|
|
14050
14235
|
}
|
|
@@ -16351,6 +16536,20 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
16351
16536
|
return { visited, lines, hopCapReached };
|
|
16352
16537
|
}
|
|
16353
16538
|
|
|
16539
|
+
// src/analysis/sanitizer-index.ts
|
|
16540
|
+
var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
|
|
16541
|
+
function getSanitizesSet(san) {
|
|
16542
|
+
let s = SANITIZER_SET_CACHE.get(san);
|
|
16543
|
+
if (!s) {
|
|
16544
|
+
s = new Set(san.sanitizes);
|
|
16545
|
+
SANITIZER_SET_CACHE.set(san, s);
|
|
16546
|
+
}
|
|
16547
|
+
return s;
|
|
16548
|
+
}
|
|
16549
|
+
function sanitizerCoversSink(san, sinkType) {
|
|
16550
|
+
return getSanitizesSet(san).has(sinkType);
|
|
16551
|
+
}
|
|
16552
|
+
|
|
16354
16553
|
// src/analysis/taint-propagation.ts
|
|
16355
16554
|
function buildSanitizersByLine(sanitizers) {
|
|
16356
16555
|
const out2 = /* @__PURE__ */ new Map();
|
|
@@ -16587,7 +16786,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
16587
16786
|
if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
|
|
16588
16787
|
for (const san of sanitizersAtTarget) {
|
|
16589
16788
|
if (isKnownSinkType) {
|
|
16590
|
-
if (san
|
|
16789
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
16591
16790
|
return { sanitized: true, sanitizer: san };
|
|
16592
16791
|
}
|
|
16593
16792
|
} else if (san.sanitizes.length > 0) {
|
|
@@ -16609,7 +16808,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
16609
16808
|
if (!sansAtLine || sansAtLine.length === 0) continue;
|
|
16610
16809
|
for (const san of sansAtLine) {
|
|
16611
16810
|
if (isKnownSinkType) {
|
|
16612
|
-
if (san
|
|
16811
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
16613
16812
|
return { sanitized: true, sanitizer: san };
|
|
16614
16813
|
}
|
|
16615
16814
|
} else if (san.sanitizes.length > 0) {
|
|
@@ -32194,7 +32393,7 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
|
32194
32393
|
const lineSanitizers = sanitizersByLine.get(sink.line);
|
|
32195
32394
|
if (!lineSanitizers || lineSanitizers.length === 0) return true;
|
|
32196
32395
|
for (const san of lineSanitizers) {
|
|
32197
|
-
if (san
|
|
32396
|
+
if (sanitizerCoversSink(san, sink.type)) {
|
|
32198
32397
|
const lineCalls = callsByLine.get(sink.line) ?? [];
|
|
32199
32398
|
for (const call of lineCalls) {
|
|
32200
32399
|
for (const arg of call.arguments) {
|
|
@@ -32687,15 +32886,23 @@ var TaintPropagationPass = class {
|
|
|
32687
32886
|
confidence: flow.confidence,
|
|
32688
32887
|
sanitized: flow.sanitized
|
|
32689
32888
|
}));
|
|
32889
|
+
const flowKey = (x) => `${x.source_line}|${x.sink_line}|${x.sink_type}`;
|
|
32890
|
+
const flowKeys = /* @__PURE__ */ new Set();
|
|
32891
|
+
for (const x of flows) flowKeys.add(flowKey(x));
|
|
32892
|
+
const pushIfNew = (f) => {
|
|
32893
|
+
const k = flowKey(f);
|
|
32894
|
+
if (flowKeys.has(k)) return false;
|
|
32895
|
+
flowKeys.add(k);
|
|
32896
|
+
flows.push(f);
|
|
32897
|
+
return true;
|
|
32898
|
+
};
|
|
32690
32899
|
const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
|
|
32691
32900
|
for (const f of arrayFlows) {
|
|
32692
|
-
|
|
32693
|
-
flows.push(f);
|
|
32694
|
-
}
|
|
32901
|
+
pushIfNew(f);
|
|
32695
32902
|
}
|
|
32696
32903
|
const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
|
|
32697
32904
|
for (const f of collectionFlows) {
|
|
32698
|
-
if (
|
|
32905
|
+
if (flowKeys.has(flowKey(f))) continue;
|
|
32699
32906
|
const flowForCheck = {
|
|
32700
32907
|
source: { line: f.source_line },
|
|
32701
32908
|
sink: { line: f.sink_line },
|
|
@@ -32710,19 +32917,15 @@ var TaintPropagationPass = class {
|
|
|
32710
32917
|
}
|
|
32711
32918
|
}
|
|
32712
32919
|
if (isFP) continue;
|
|
32713
|
-
|
|
32920
|
+
pushIfNew(f);
|
|
32714
32921
|
}
|
|
32715
32922
|
const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
|
|
32716
32923
|
for (const f of paramFlows) {
|
|
32717
|
-
|
|
32718
|
-
flows.push(f);
|
|
32719
|
-
}
|
|
32924
|
+
pushIfNew(f);
|
|
32720
32925
|
}
|
|
32721
32926
|
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
|
|
32722
32927
|
for (const f of exprScanFlows) {
|
|
32723
|
-
if (
|
|
32724
|
-
(x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type
|
|
32725
|
-
)) continue;
|
|
32928
|
+
if (flowKeys.has(flowKey(f))) continue;
|
|
32726
32929
|
const flowForCheck = {
|
|
32727
32930
|
source: { line: f.source_line },
|
|
32728
32931
|
sink: { line: f.sink_line },
|
|
@@ -32737,7 +32940,7 @@ var TaintPropagationPass = class {
|
|
|
32737
32940
|
}
|
|
32738
32941
|
}
|
|
32739
32942
|
if (isFP) continue;
|
|
32740
|
-
|
|
32943
|
+
pushIfNew(f);
|
|
32741
32944
|
}
|
|
32742
32945
|
const sanitizedNames = constProp.sanitizedVars;
|
|
32743
32946
|
let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
|
|
@@ -32765,7 +32968,7 @@ var TaintPropagationPass = class {
|
|
|
32765
32968
|
const sansAtLine = sanitizersByLine.get(line);
|
|
32766
32969
|
if (!sansAtLine) continue;
|
|
32767
32970
|
for (const san of sansAtLine) {
|
|
32768
|
-
if (san
|
|
32971
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
32769
32972
|
return false;
|
|
32770
32973
|
}
|
|
32771
32974
|
}
|
|
@@ -32775,7 +32978,7 @@ var TaintPropagationPass = class {
|
|
|
32775
32978
|
const sansAtSink = sanitizersByLine.get(f.sink_line);
|
|
32776
32979
|
if (sansAtSink && sansAtSink.length > 0) {
|
|
32777
32980
|
for (const san of sansAtSink) {
|
|
32778
|
-
if (san
|
|
32981
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
32779
32982
|
return false;
|
|
32780
32983
|
}
|
|
32781
32984
|
}
|
|
@@ -32799,7 +33002,7 @@ var TaintPropagationPass = class {
|
|
|
32799
33002
|
const sansAtLine = sanitizersByLine.get(line);
|
|
32800
33003
|
if (!sansAtLine || sansAtLine.length === 0) continue;
|
|
32801
33004
|
for (const san of sansAtLine) {
|
|
32802
|
-
if (san
|
|
33005
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
32803
33006
|
return false;
|
|
32804
33007
|
}
|
|
32805
33008
|
}
|
|
@@ -33764,7 +33967,7 @@ var InterproceduralPass = class {
|
|
|
33764
33967
|
const sansAtLine = sanitizersByLine.get(line);
|
|
33765
33968
|
if (!sansAtLine) continue;
|
|
33766
33969
|
for (const san of sansAtLine) {
|
|
33767
|
-
if (san
|
|
33970
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
33768
33971
|
sanitizedSinkKeys.add(`${f.sink_line}:${f.sink_type}`);
|
|
33769
33972
|
return false;
|
|
33770
33973
|
}
|
|
@@ -33775,7 +33978,7 @@ var InterproceduralPass = class {
|
|
|
33775
33978
|
const sansAtSink = sanitizersByLine.get(f.sink_line);
|
|
33776
33979
|
if (!sansAtSink || sansAtSink.length === 0) return true;
|
|
33777
33980
|
for (const san of sansAtSink) {
|
|
33778
|
-
if (san
|
|
33981
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
33779
33982
|
return false;
|
|
33780
33983
|
}
|
|
33781
33984
|
}
|
|
@@ -42186,7 +42389,13 @@ function getNodeTypesForLanguage(language) {
|
|
|
42186
42389
|
"jsx_self_closing_element",
|
|
42187
42390
|
"jsx_opening_element",
|
|
42188
42391
|
"jsx_attribute",
|
|
42189
|
-
"jsx_expression"
|
|
42392
|
+
"jsx_expression",
|
|
42393
|
+
// buildCFG containers (3.172.0 T2-A) — the two below are legacy
|
|
42394
|
+
// tree-sitter-javascript node types for anonymous / expression
|
|
42395
|
+
// functions; declaring them here lets `collectAllNodes` populate
|
|
42396
|
+
// the cache in the same pass so `buildCFG` skips its per-type walk.
|
|
42397
|
+
"function",
|
|
42398
|
+
"function_expression"
|
|
42190
42399
|
]);
|
|
42191
42400
|
case "bash":
|
|
42192
42401
|
return /* @__PURE__ */ new Set([
|
|
@@ -42240,7 +42449,9 @@ function getNodeTypesForLanguage(language) {
|
|
|
42240
42449
|
"field_declaration",
|
|
42241
42450
|
"import_declaration",
|
|
42242
42451
|
"interface_declaration",
|
|
42243
|
-
"enum_declaration"
|
|
42452
|
+
"enum_declaration",
|
|
42453
|
+
"package_declaration",
|
|
42454
|
+
"local_variable_declaration"
|
|
42244
42455
|
]);
|
|
42245
42456
|
}
|
|
42246
42457
|
}
|
|
@@ -42264,10 +42475,20 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42264
42475
|
}
|
|
42265
42476
|
}
|
|
42266
42477
|
logger.debug("Analyzing file", { filePath, language, parseGrammar, codeLength: code.length });
|
|
42478
|
+
const phaseTimingEnabled = globalThis.__circleIrPassTiming === true;
|
|
42479
|
+
const phaseStart = () => phaseTimingEnabled ? Date.now() : 0;
|
|
42480
|
+
const phaseEnd = (label, t0) => {
|
|
42481
|
+
if (!phaseTimingEnabled) return;
|
|
42482
|
+
console.error(`[phase-timing] ${label} ${Date.now() - t0}ms file=${filePath}`);
|
|
42483
|
+
};
|
|
42484
|
+
const tParse = phaseStart();
|
|
42267
42485
|
const tree = await parse(code, parseGrammar);
|
|
42486
|
+
phaseEnd("parse", tParse);
|
|
42268
42487
|
try {
|
|
42269
42488
|
logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
|
|
42489
|
+
const tParseStatus = phaseStart();
|
|
42270
42490
|
const parseStatus = extractParseStatus(tree);
|
|
42491
|
+
phaseEnd("extractParseStatus", tParseStatus);
|
|
42271
42492
|
if (parseStatus.has_errors) {
|
|
42272
42493
|
logger.warn("Partial parse \u2014 IR may be incomplete", {
|
|
42273
42494
|
filePath,
|
|
@@ -42276,18 +42497,37 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42276
42497
|
firstErrorLine: parseStatus.error_locations[0]?.line
|
|
42277
42498
|
});
|
|
42278
42499
|
}
|
|
42500
|
+
const tCollect = phaseStart();
|
|
42279
42501
|
const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
|
|
42502
|
+
phaseEnd("collectAllNodes", tCollect);
|
|
42503
|
+
const tMeta = phaseStart();
|
|
42280
42504
|
const meta = extractMeta(code, tree, filePath, language);
|
|
42281
42505
|
if (options.projectProfile !== void 0) {
|
|
42282
42506
|
meta.projectProfile = makeProfileResolver(options.projectProfile)(filePath);
|
|
42283
42507
|
}
|
|
42508
|
+
phaseEnd("extractMeta", tMeta);
|
|
42509
|
+
const tTypes = phaseStart();
|
|
42284
42510
|
const types = extractTypes(tree, nodeCache, language);
|
|
42511
|
+
phaseEnd("extractTypes", tTypes);
|
|
42512
|
+
const tCalls = phaseStart();
|
|
42285
42513
|
const calls = extractCalls(tree, nodeCache, language);
|
|
42286
|
-
|
|
42514
|
+
phaseEnd("extractCalls", tCalls);
|
|
42515
|
+
const tImports = phaseStart();
|
|
42516
|
+
const imports = extractImports(tree, language, nodeCache);
|
|
42517
|
+
phaseEnd("extractImports", tImports);
|
|
42518
|
+
const tExports = phaseStart();
|
|
42287
42519
|
const exports = extractExports(types);
|
|
42288
|
-
|
|
42520
|
+
phaseEnd("extractExports", tExports);
|
|
42521
|
+
const tCFG = phaseStart();
|
|
42522
|
+
const cfg = buildCFG(tree, language, nodeCache);
|
|
42523
|
+
phaseEnd("buildCFG", tCFG);
|
|
42524
|
+
const tDFG = phaseStart();
|
|
42289
42525
|
const dfg = buildDFG(tree, nodeCache, language);
|
|
42526
|
+
phaseEnd("buildDFG", tDFG);
|
|
42527
|
+
const tRuntime = phaseStart();
|
|
42290
42528
|
const runtimeRegistrations = extractRuntimeRegistrations(tree, nodeCache, language, imports);
|
|
42529
|
+
phaseEnd("extractRuntimeRegistrations", tRuntime);
|
|
42530
|
+
const tGraph = phaseStart();
|
|
42291
42531
|
const graph = new CodeGraph({
|
|
42292
42532
|
meta,
|
|
42293
42533
|
types,
|
|
@@ -42300,6 +42540,7 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42300
42540
|
unresolved: [],
|
|
42301
42541
|
enriched: {}
|
|
42302
42542
|
});
|
|
42543
|
+
phaseEnd("buildCodeGraph", tGraph);
|
|
42303
42544
|
const config = options.taintConfig ?? getDefaultConfig();
|
|
42304
42545
|
const disabledPasses = new Set(options.disabledPasses ?? []);
|
|
42305
42546
|
const passOpts = options.passOptions ?? {};
|