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
|
@@ -7543,33 +7543,33 @@ function detectLanguage2(tree) {
|
|
|
7543
7543
|
if (pythonScore > jsScore && pythonScore > javaScore) return "python";
|
|
7544
7544
|
return jsScore > javaScore ? "javascript" : "java";
|
|
7545
7545
|
}
|
|
7546
|
-
function extractImports(tree, language) {
|
|
7546
|
+
function extractImports(tree, language, cache) {
|
|
7547
7547
|
const effectiveLanguage = language ?? detectLanguage2(tree);
|
|
7548
7548
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
7549
7549
|
const isPython = effectiveLanguage === "python";
|
|
7550
7550
|
const isRust = effectiveLanguage === "rust";
|
|
7551
7551
|
if (effectiveLanguage === "go") {
|
|
7552
|
-
return extractGoImports(tree);
|
|
7552
|
+
return extractGoImports(tree, cache);
|
|
7553
7553
|
}
|
|
7554
7554
|
if (isRust) {
|
|
7555
|
-
return extractRustImports(tree);
|
|
7555
|
+
return extractRustImports(tree, cache);
|
|
7556
7556
|
}
|
|
7557
7557
|
if (isPython) {
|
|
7558
|
-
return extractPythonImports(tree);
|
|
7558
|
+
return extractPythonImports(tree, cache);
|
|
7559
7559
|
}
|
|
7560
7560
|
if (isJavaScript) {
|
|
7561
|
-
return extractJavaScriptImports(tree);
|
|
7561
|
+
return extractJavaScriptImports(tree, cache);
|
|
7562
7562
|
}
|
|
7563
|
-
return extractJavaImports(tree);
|
|
7563
|
+
return extractJavaImports(tree, cache);
|
|
7564
7564
|
}
|
|
7565
|
-
function extractJavaScriptImports(tree) {
|
|
7565
|
+
function extractJavaScriptImports(tree, cache) {
|
|
7566
7566
|
const imports = [];
|
|
7567
|
-
const importStatements =
|
|
7567
|
+
const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
|
|
7568
7568
|
for (const importStmt of importStatements) {
|
|
7569
7569
|
const importInfos = extractJSImportInfo(importStmt);
|
|
7570
7570
|
imports.push(...importInfos);
|
|
7571
7571
|
}
|
|
7572
|
-
const exportStatements =
|
|
7572
|
+
const exportStatements = getNodesFromCache(tree.rootNode, "export_statement", cache);
|
|
7573
7573
|
for (const exportStmt of exportStatements) {
|
|
7574
7574
|
const sourceNode = exportStmt.childForFieldName("source");
|
|
7575
7575
|
if (!sourceNode) continue;
|
|
@@ -7583,13 +7583,13 @@ function extractJavaScriptImports(tree) {
|
|
|
7583
7583
|
line_number: exportStmt.startPosition.row + 1
|
|
7584
7584
|
});
|
|
7585
7585
|
}
|
|
7586
|
-
const requireCalls = findRequireCalls(tree);
|
|
7586
|
+
const requireCalls = findRequireCalls(tree, cache);
|
|
7587
7587
|
imports.push(...requireCalls);
|
|
7588
7588
|
return imports;
|
|
7589
7589
|
}
|
|
7590
|
-
function extractJavaImports(tree) {
|
|
7590
|
+
function extractJavaImports(tree, cache) {
|
|
7591
7591
|
const imports = [];
|
|
7592
|
-
const importDecls =
|
|
7592
|
+
const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
7593
7593
|
for (const importDecl of importDecls) {
|
|
7594
7594
|
const importInfo = extractJavaImportInfo(importDecl);
|
|
7595
7595
|
if (importInfo) {
|
|
@@ -7737,9 +7737,9 @@ function extractJSImportInfo(node) {
|
|
|
7737
7737
|
}
|
|
7738
7738
|
return imports;
|
|
7739
7739
|
}
|
|
7740
|
-
function findRequireCalls(tree) {
|
|
7740
|
+
function findRequireCalls(tree, cache) {
|
|
7741
7741
|
const imports = [];
|
|
7742
|
-
const callExpressions =
|
|
7742
|
+
const callExpressions = getNodesFromCache(tree.rootNode, "call_expression", cache);
|
|
7743
7743
|
for (const call of callExpressions) {
|
|
7744
7744
|
const funcNode = call.childForFieldName("function");
|
|
7745
7745
|
if (!funcNode || getNodeText(funcNode) !== "require") continue;
|
|
@@ -7853,14 +7853,14 @@ function parseImportPath(fullPath, isWildcard) {
|
|
|
7853
7853
|
fromPackage: fullPath.substring(0, lastDot)
|
|
7854
7854
|
};
|
|
7855
7855
|
}
|
|
7856
|
-
function extractPythonImports(tree) {
|
|
7856
|
+
function extractPythonImports(tree, cache) {
|
|
7857
7857
|
const imports = [];
|
|
7858
|
-
const importStatements =
|
|
7858
|
+
const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
|
|
7859
7859
|
for (const stmt of importStatements) {
|
|
7860
7860
|
const importInfos = extractPythonImportStatement(stmt);
|
|
7861
7861
|
imports.push(...importInfos);
|
|
7862
7862
|
}
|
|
7863
|
-
const importFromStatements =
|
|
7863
|
+
const importFromStatements = getNodesFromCache(tree.rootNode, "import_from_statement", cache);
|
|
7864
7864
|
for (const stmt of importFromStatements) {
|
|
7865
7865
|
const importInfos = extractPythonFromImportStatement(stmt);
|
|
7866
7866
|
imports.push(...importInfos);
|
|
@@ -7954,9 +7954,9 @@ function extractPythonFromImportStatement(node) {
|
|
|
7954
7954
|
}
|
|
7955
7955
|
return imports;
|
|
7956
7956
|
}
|
|
7957
|
-
function extractRustImports(tree) {
|
|
7957
|
+
function extractRustImports(tree, cache) {
|
|
7958
7958
|
const imports = [];
|
|
7959
|
-
const useDecls =
|
|
7959
|
+
const useDecls = getNodesFromCache(tree.rootNode, "use_declaration", cache);
|
|
7960
7960
|
for (const useDecl of useDecls) {
|
|
7961
7961
|
const useImports = extractRustUseDecl(useDecl);
|
|
7962
7962
|
imports.push(...useImports);
|
|
@@ -8098,9 +8098,9 @@ function extractRustScopedUseList(node, lineNumber) {
|
|
|
8098
8098
|
}
|
|
8099
8099
|
return imports;
|
|
8100
8100
|
}
|
|
8101
|
-
function extractGoImports(tree) {
|
|
8101
|
+
function extractGoImports(tree, cache) {
|
|
8102
8102
|
const imports = [];
|
|
8103
|
-
const importDecls =
|
|
8103
|
+
const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
8104
8104
|
for (const decl of importDecls) {
|
|
8105
8105
|
const singleSpec = findGoChildByType(decl, "import_spec");
|
|
8106
8106
|
if (singleSpec) {
|
|
@@ -8235,7 +8235,7 @@ function detectLanguage3(tree) {
|
|
|
8235
8235
|
}
|
|
8236
8236
|
return jsScore > javaScore ? "javascript" : "java";
|
|
8237
8237
|
}
|
|
8238
|
-
function buildCFG(tree, language) {
|
|
8238
|
+
function buildCFG(tree, language, cache) {
|
|
8239
8239
|
const effectiveLanguage = language ?? detectLanguage3(tree);
|
|
8240
8240
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
8241
8241
|
const allBlocks = [];
|
|
@@ -8249,11 +8249,11 @@ function buildCFG(tree, language) {
|
|
|
8249
8249
|
}
|
|
8250
8250
|
if (isJavaScript) {
|
|
8251
8251
|
const functions = [
|
|
8252
|
-
...
|
|
8253
|
-
...
|
|
8254
|
-
...
|
|
8255
|
-
...
|
|
8256
|
-
...
|
|
8252
|
+
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
8253
|
+
...getNodesFromCache(tree.rootNode, "arrow_function", cache),
|
|
8254
|
+
...getNodesFromCache(tree.rootNode, "method_definition", cache),
|
|
8255
|
+
...getNodesFromCache(tree.rootNode, "function", cache),
|
|
8256
|
+
...getNodesFromCache(tree.rootNode, "function_expression", cache)
|
|
8257
8257
|
];
|
|
8258
8258
|
for (const func2 of functions) {
|
|
8259
8259
|
const body2 = func2.childForFieldName("body");
|
|
@@ -8275,8 +8275,8 @@ function buildCFG(tree, language) {
|
|
|
8275
8275
|
}
|
|
8276
8276
|
} else {
|
|
8277
8277
|
const methods = [
|
|
8278
|
-
...
|
|
8279
|
-
...
|
|
8278
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8279
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache)
|
|
8280
8280
|
];
|
|
8281
8281
|
for (const method of methods) {
|
|
8282
8282
|
const body2 = method.childForFieldName("body");
|
|
@@ -10529,7 +10529,86 @@ var DEFAULT_SOURCES = [
|
|
|
10529
10529
|
{ method: "recv", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
10530
10530
|
{ method: "read", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
10531
10531
|
{ method: "read_to_end", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
10532
|
-
{ method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true }
|
|
10532
|
+
{ method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
10533
|
+
// =========================================================================
|
|
10534
|
+
// Modern taint sources (cognium-dev #242 — 3.170.0)
|
|
10535
|
+
// GraphQL resolver args, gRPC request metadata, cache reads (second-order
|
|
10536
|
+
// taint), and JWT claims (unverified decode). These attacker-influenced
|
|
10537
|
+
// surfaces were previously silent — a resolver that concatenated an
|
|
10538
|
+
// `Args`-annotated field into a SQL string produced zero flows.
|
|
10539
|
+
// =========================================================================
|
|
10540
|
+
// --- GraphQL resolver argument sources (JS / TS — Apollo, TypeGraphQL, NestJS) ---
|
|
10541
|
+
// TypeGraphQL / NestJS annotate query/mutation/subscription methods; every
|
|
10542
|
+
// parameter on the annotated method carries attacker input from the GraphQL
|
|
10543
|
+
// POST body's `variables` field.
|
|
10544
|
+
{ method_annotation: "Query", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
10545
|
+
{ method_annotation: "Mutation", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
10546
|
+
{ method_annotation: "Subscription", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
10547
|
+
{ method_annotation: "FieldResolver", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
10548
|
+
{ method_annotation: "ResolveField", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
10549
|
+
// Parameter-level: `@Arg('id') id: string`, `@Args('input') input: FooDto`.
|
|
10550
|
+
{ annotation: "Arg", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
|
|
10551
|
+
{ annotation: "Args", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
|
|
10552
|
+
// --- GraphQL resolver argument sources (Python — Strawberry, Graphene, Ariadne) ---
|
|
10553
|
+
{ method_annotation: "strawberry.field", type: "http_body", severity: "high", languages: ["python"] },
|
|
10554
|
+
{ method_annotation: "strawberry.mutation", type: "http_body", severity: "high", languages: ["python"] },
|
|
10555
|
+
{ method_annotation: "strawberry.subscription", type: "http_body", severity: "high", languages: ["python"] },
|
|
10556
|
+
// --- GraphQL resolver argument sources (Java — Netflix DGS, SPQR, graphql-java-annotations) ---
|
|
10557
|
+
{ method_annotation: "DgsQuery", type: "http_body", severity: "high", languages: ["java"] },
|
|
10558
|
+
{ method_annotation: "DgsMutation", type: "http_body", severity: "high", languages: ["java"] },
|
|
10559
|
+
{ method_annotation: "DgsSubscription", type: "http_body", severity: "high", languages: ["java"] },
|
|
10560
|
+
{ method_annotation: "DgsData", type: "http_body", severity: "high", languages: ["java"] },
|
|
10561
|
+
{ method_annotation: "GraphQLQuery", type: "http_body", severity: "high", languages: ["java"] },
|
|
10562
|
+
{ method_annotation: "GraphQLMutation", type: "http_body", severity: "high", languages: ["java"] },
|
|
10563
|
+
{ annotation: "InputArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10564
|
+
{ annotation: "GraphQLArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10565
|
+
// --- gRPC request metadata (Python — grpcio) ---
|
|
10566
|
+
// `context.invocation_metadata()` returns the caller-supplied metadata tuple.
|
|
10567
|
+
// No `class` filter — receiver names vary (`context`, `ctx`, `servicer_context`).
|
|
10568
|
+
{ method: "invocation_metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
10569
|
+
// --- gRPC request metadata (JS / TS — @grpc/grpc-js) ---
|
|
10570
|
+
// `call.metadata.get(key)` / `call.metadata.getMap()` inside a service handler.
|
|
10571
|
+
{ method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10572
|
+
{ method: "getMap", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10573
|
+
// --- gRPC request metadata (Go — google.golang.org/grpc/metadata) ---
|
|
10574
|
+
// `md, _ := metadata.FromIncomingContext(ctx)` pulls the caller's headers.
|
|
10575
|
+
{ method: "FromIncomingContext", class: "metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] },
|
|
10576
|
+
// --- gRPC request metadata (Java — io.grpc.Metadata) ---
|
|
10577
|
+
// Server-side interceptor receives `Metadata headers`; `headers.get(KEY)`
|
|
10578
|
+
// returns caller-supplied header values.
|
|
10579
|
+
{ method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
10580
|
+
// --- Cache reads (second-order taint — Redis / Memcached / Django cache) ---
|
|
10581
|
+
// The cache round-trip is a canonical second-order sink: whatever was
|
|
10582
|
+
// written previously (potentially attacker-controlled) resurfaces on read.
|
|
10583
|
+
// Severity 'medium' — cache contents are usually filtered by the writer but
|
|
10584
|
+
// the read side often forgets that guarantee. Class-scoped to `Redis`,
|
|
10585
|
+
// `Jedis`, `cache` to avoid colliding with generic `Map.get()`.
|
|
10586
|
+
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
10587
|
+
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
10588
|
+
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
10589
|
+
{ method: "lrange", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
10590
|
+
{ method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
10591
|
+
{ method: "get_many", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
10592
|
+
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10593
|
+
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10594
|
+
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10595
|
+
{ method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
10596
|
+
{ method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
10597
|
+
{ method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
10598
|
+
// --- JWT claims (unverified decode — PyJWT / jose / jsonwebtoken / auth0 java-jwt / golang-jwt) ---
|
|
10599
|
+
// A JWT's payload is *always* attacker-authored. Even after verification
|
|
10600
|
+
// the *contents* of the claims (username, role, custom fields) are not
|
|
10601
|
+
// trusted for downstream flows into SQL, HTML, shell, etc. The `decode`
|
|
10602
|
+
// variants here return the parsed claims dictionary/object.
|
|
10603
|
+
{ method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
10604
|
+
{ method: "get_unverified_claims", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
10605
|
+
{ method: "get_unverified_header", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
10606
|
+
{ method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10607
|
+
{ method: "decode", class: "jsonwebtoken", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10608
|
+
{ method: "decodeJwt", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10609
|
+
{ method: "decodeProtectedHeader", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
10610
|
+
{ method: "decode", class: "JWT", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
10611
|
+
{ method: "ParseUnverified", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] }
|
|
10533
10612
|
];
|
|
10534
10613
|
var DEFAULT_SINKS = [
|
|
10535
10614
|
// SQL Injection (CWE-89)
|
|
@@ -12098,7 +12177,85 @@ var DEFAULT_SINKS = [
|
|
|
12098
12177
|
// they iterate after net/http entries and don't hijack `http.Get`.
|
|
12099
12178
|
{ method: "Do", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
12100
12179
|
// *fasthttp.Client.Do(req)
|
|
12101
|
-
{ method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] }
|
|
12180
|
+
{ method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
12181
|
+
// =========================================================================
|
|
12182
|
+
// cognium-dev #248 — prompt-injection sinks (CWE-1427)
|
|
12183
|
+
//
|
|
12184
|
+
// Tainted data reaching a generative-model prompt-construction API is
|
|
12185
|
+
// classified as `prompt_injection`. v1 uses broad positional matching
|
|
12186
|
+
// (arg_positions [0..3]) because these APIs are kwarg-heavy: Python
|
|
12187
|
+
// `openai.chat.completions.create(model=..., messages=...)` may pass the
|
|
12188
|
+
// messages arg at position 0 or 1 depending on caller order, and JS/TS
|
|
12189
|
+
// object-literal `{ messages: [...], model: '...' }` is a single
|
|
12190
|
+
// positional arg whose taint is inherited from any tainted property.
|
|
12191
|
+
// Argname-precise matching (messages=/prompt=/content=) and
|
|
12192
|
+
// sanitizer credit for prompt-template libraries (PromptTemplate,
|
|
12193
|
+
// ChatPromptTemplate) are follow-ups.
|
|
12194
|
+
//
|
|
12195
|
+
// Class-qualified entries prevent bare `create()` / `generate()` calls
|
|
12196
|
+
// with no receiver from matching (per taint-matcher.ts:1696 guard).
|
|
12197
|
+
// =========================================================================
|
|
12198
|
+
// --- Python: openai (v1 SDK) --------------------------------------------
|
|
12199
|
+
{ method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12200
|
+
{ method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12201
|
+
// openai <1.0 legacy top-level API — `openai.ChatCompletion.create(...)` / `openai.Completion.create(...)`
|
|
12202
|
+
{ method: "create", class: "ChatCompletion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12203
|
+
{ method: "create", class: "Completion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12204
|
+
// --- Python: anthropic ---------------------------------------------------
|
|
12205
|
+
{ method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12206
|
+
{ method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12207
|
+
// --- Python: litellm (bare functions from the litellm module) -----------
|
|
12208
|
+
{ method: "completion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12209
|
+
{ method: "acompletion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12210
|
+
// --- Python: langchain chat/llm models ----------------------------------
|
|
12211
|
+
// Common concrete classes; matching the class name catches typical usage
|
|
12212
|
+
// `ChatOpenAI().invoke(prompt)` where the receiver-type resolver identifies
|
|
12213
|
+
// the class from the constructor.
|
|
12214
|
+
{ method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12215
|
+
{ method: "predict", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12216
|
+
{ method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12217
|
+
{ method: "generate", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12218
|
+
{ method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12219
|
+
{ method: "invoke", class: "ChatGoogleGenerativeAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12220
|
+
{ method: "run", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12221
|
+
{ method: "invoke", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
|
|
12222
|
+
// --- JS/TS: openai node SDK ---------------------------------------------
|
|
12223
|
+
{ method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12224
|
+
{ method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12225
|
+
// --- JS/TS: @anthropic-ai/sdk -------------------------------------------
|
|
12226
|
+
{ method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12227
|
+
{ method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12228
|
+
// --- JS/TS: Vercel AI SDK (bare functions from the `ai` package) --------
|
|
12229
|
+
// generateText/streamText/generateObject take a single options object;
|
|
12230
|
+
// taint reaches the sink when a tainted variable flows into any property.
|
|
12231
|
+
{ method: "generateText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12232
|
+
{ method: "streamText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12233
|
+
{ method: "generateObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12234
|
+
{ method: "streamObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12235
|
+
// --- JS/TS: langchain.js ------------------------------------------------
|
|
12236
|
+
{ method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12237
|
+
{ method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12238
|
+
{ method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12239
|
+
// --- Java: LangChain4j --------------------------------------------------
|
|
12240
|
+
{ method: "generate", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12241
|
+
{ method: "chat", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12242
|
+
{ method: "generate", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12243
|
+
{ method: "chat", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12244
|
+
// --- Java: Spring AI ----------------------------------------------------
|
|
12245
|
+
{ method: "prompt", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12246
|
+
{ method: "call", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12247
|
+
{ method: "call", class: "ChatModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12248
|
+
// --- Java: OpenAI Java SDK (theokanning) --------------------------------
|
|
12249
|
+
{ method: "createChatCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12250
|
+
{ method: "createCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
12251
|
+
// --- Go: go-openai (sashabaranov/go-openai) -----------------------------
|
|
12252
|
+
{ method: "CreateChatCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12253
|
+
{ method: "CreateCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12254
|
+
{ method: "CreateChatCompletionStream", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12255
|
+
// --- Go: langchaingo ----------------------------------------------------
|
|
12256
|
+
{ method: "Call", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12257
|
+
{ method: "Generate", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12258
|
+
{ method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] }
|
|
12102
12259
|
];
|
|
12103
12260
|
var DEFAULT_SANITIZERS = [
|
|
12104
12261
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -12582,6 +12739,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12582
12739
|
for (const param of method.parameters) {
|
|
12583
12740
|
for (const pattern of patterns) {
|
|
12584
12741
|
if (pattern.annotation && pattern.param_tainted) {
|
|
12742
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12743
|
+
continue;
|
|
12744
|
+
}
|
|
12585
12745
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
12586
12746
|
const paramLine = param.line ?? method.start_line;
|
|
12587
12747
|
sources.push({
|
|
@@ -12602,6 +12762,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12602
12762
|
for (const method of type.methods) {
|
|
12603
12763
|
for (const pattern of patterns) {
|
|
12604
12764
|
if (!pattern.method_annotation) continue;
|
|
12765
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12766
|
+
continue;
|
|
12767
|
+
}
|
|
12605
12768
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
12606
12769
|
for (const param of method.parameters) {
|
|
12607
12770
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -12866,7 +13029,7 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
12866
13029
|
const secondArg = call.arguments.find((a) => a.position === 1);
|
|
12867
13030
|
if (secondArg?.expression) {
|
|
12868
13031
|
const expr = secondArg.expression.trim();
|
|
12869
|
-
if (expr.startsWith("[")) {
|
|
13032
|
+
if (expr.startsWith("[") && !/^\[\s*\]$/.test(expr)) {
|
|
12870
13033
|
return true;
|
|
12871
13034
|
}
|
|
12872
13035
|
}
|
|
@@ -13307,52 +13470,74 @@ function matchesSourcePattern(call, pattern) {
|
|
|
13307
13470
|
}
|
|
13308
13471
|
return false;
|
|
13309
13472
|
}
|
|
13473
|
+
var JS_SOURCE_PATTERN_CACHE = /* @__PURE__ */ new WeakMap();
|
|
13474
|
+
var JS_FALLBACK_COMPILED = (() => {
|
|
13475
|
+
const bases = [
|
|
13476
|
+
{ base: "req\\.params", sourceType: "http_param" },
|
|
13477
|
+
{ base: "req\\.query", sourceType: "http_param" },
|
|
13478
|
+
{ base: "req\\.body", sourceType: "http_body" },
|
|
13479
|
+
{ base: "req\\.headers", sourceType: "http_header" },
|
|
13480
|
+
{ base: "req\\.cookies", sourceType: "http_cookie" },
|
|
13481
|
+
{ base: "req\\.url", sourceType: "http_path" },
|
|
13482
|
+
{ base: "req\\.path", sourceType: "http_path" },
|
|
13483
|
+
{ base: "req\\.originalUrl", sourceType: "http_path" },
|
|
13484
|
+
{ base: "req\\.file", sourceType: "file_input" },
|
|
13485
|
+
{ base: "req\\.files", sourceType: "file_input" },
|
|
13486
|
+
{ base: "request\\.params", sourceType: "http_param" },
|
|
13487
|
+
{ base: "request\\.query", sourceType: "http_param" },
|
|
13488
|
+
{ base: "request\\.body", sourceType: "http_body" },
|
|
13489
|
+
{ base: "request\\.headers", sourceType: "http_header" },
|
|
13490
|
+
{ base: "process\\.env", sourceType: "env_input" },
|
|
13491
|
+
{ base: "process\\.argv", sourceType: "io_input" },
|
|
13492
|
+
{ base: "ctx\\.query", sourceType: "http_param" },
|
|
13493
|
+
{ base: "ctx\\.params", sourceType: "http_param" },
|
|
13494
|
+
{ base: "ctx\\.request", sourceType: "http_body" }
|
|
13495
|
+
];
|
|
13496
|
+
const exact = [];
|
|
13497
|
+
const contained = [];
|
|
13498
|
+
for (const { base, sourceType } of bases) {
|
|
13499
|
+
exact.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
|
|
13500
|
+
contained.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
|
|
13501
|
+
}
|
|
13502
|
+
return { exact, contained };
|
|
13503
|
+
})();
|
|
13504
|
+
function compileJsSourcePatterns(sourcePatterns) {
|
|
13505
|
+
const cached = JS_SOURCE_PATTERN_CACHE.get(sourcePatterns);
|
|
13506
|
+
if (cached) return cached;
|
|
13507
|
+
const exact = [];
|
|
13508
|
+
const contained = [];
|
|
13509
|
+
for (const sp of sourcePatterns) {
|
|
13510
|
+
if (sp.property && sp.object && sp.property_tainted) {
|
|
13511
|
+
exact.push({
|
|
13512
|
+
pattern: new RegExp(`^${sp.object}\\.${sp.property}\\b`),
|
|
13513
|
+
sourceType: sp.type
|
|
13514
|
+
});
|
|
13515
|
+
contained.push({
|
|
13516
|
+
pattern: new RegExp(`\\b${sp.object}\\.${sp.property}\\b`),
|
|
13517
|
+
sourceType: sp.type
|
|
13518
|
+
});
|
|
13519
|
+
}
|
|
13520
|
+
}
|
|
13521
|
+
const compiled = { exact, contained };
|
|
13522
|
+
JS_SOURCE_PATTERN_CACHE.set(sourcePatterns, compiled);
|
|
13523
|
+
return compiled;
|
|
13524
|
+
}
|
|
13310
13525
|
function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
const exactRegex = new RegExp(`^${sp.object}\\.${sp.property}\\b`);
|
|
13317
|
-
exactPatterns.push({ pattern: exactRegex, sourceType: sp.type });
|
|
13318
|
-
const containedRegex = new RegExp(`\\b${sp.object}\\.${sp.property}\\b`);
|
|
13319
|
-
containedPatterns.push({ pattern: containedRegex, sourceType: sp.type });
|
|
13320
|
-
}
|
|
13321
|
-
}
|
|
13322
|
-
}
|
|
13323
|
-
if (exactPatterns.length === 0) {
|
|
13324
|
-
const basePatterns = [
|
|
13325
|
-
{ base: "req\\.params", sourceType: "http_param" },
|
|
13326
|
-
{ base: "req\\.query", sourceType: "http_param" },
|
|
13327
|
-
{ base: "req\\.body", sourceType: "http_body" },
|
|
13328
|
-
{ base: "req\\.headers", sourceType: "http_header" },
|
|
13329
|
-
{ base: "req\\.cookies", sourceType: "http_cookie" },
|
|
13330
|
-
{ base: "req\\.url", sourceType: "http_path" },
|
|
13331
|
-
{ base: "req\\.path", sourceType: "http_path" },
|
|
13332
|
-
{ base: "req\\.originalUrl", sourceType: "http_path" },
|
|
13333
|
-
{ base: "req\\.file", sourceType: "file_input" },
|
|
13334
|
-
{ base: "req\\.files", sourceType: "file_input" },
|
|
13335
|
-
{ base: "request\\.params", sourceType: "http_param" },
|
|
13336
|
-
{ base: "request\\.query", sourceType: "http_param" },
|
|
13337
|
-
{ base: "request\\.body", sourceType: "http_body" },
|
|
13338
|
-
{ base: "request\\.headers", sourceType: "http_header" },
|
|
13339
|
-
{ base: "process\\.env", sourceType: "env_input" },
|
|
13340
|
-
{ base: "process\\.argv", sourceType: "io_input" },
|
|
13341
|
-
{ base: "ctx\\.query", sourceType: "http_param" },
|
|
13342
|
-
{ base: "ctx\\.params", sourceType: "http_param" },
|
|
13343
|
-
{ base: "ctx\\.request", sourceType: "http_body" }
|
|
13344
|
-
];
|
|
13345
|
-
for (const { base, sourceType } of basePatterns) {
|
|
13346
|
-
exactPatterns.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
|
|
13347
|
-
containedPatterns.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
|
|
13526
|
+
let compiled;
|
|
13527
|
+
if (sourcePatterns && sourcePatterns.length > 0) {
|
|
13528
|
+
compiled = compileJsSourcePatterns(sourcePatterns);
|
|
13529
|
+
if (compiled.exact.length === 0) {
|
|
13530
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
13348
13531
|
}
|
|
13532
|
+
} else {
|
|
13533
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
13349
13534
|
}
|
|
13350
|
-
for (const { pattern, sourceType } of
|
|
13535
|
+
for (const { pattern, sourceType } of compiled.exact) {
|
|
13351
13536
|
if (pattern.test(argExpression)) {
|
|
13352
13537
|
return { isTainted: true, sourceType };
|
|
13353
13538
|
}
|
|
13354
13539
|
}
|
|
13355
|
-
for (const { pattern, sourceType } of
|
|
13540
|
+
for (const { pattern, sourceType } of compiled.contained) {
|
|
13356
13541
|
if (pattern.test(argExpression)) {
|
|
13357
13542
|
return { isTainted: true, sourceType };
|
|
13358
13543
|
}
|
|
@@ -14344,6 +14529,20 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
14344
14529
|
return { visited, lines, hopCapReached };
|
|
14345
14530
|
}
|
|
14346
14531
|
|
|
14532
|
+
// src/analysis/sanitizer-index.ts
|
|
14533
|
+
var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
|
|
14534
|
+
function getSanitizesSet(san) {
|
|
14535
|
+
let s = SANITIZER_SET_CACHE.get(san);
|
|
14536
|
+
if (!s) {
|
|
14537
|
+
s = new Set(san.sanitizes);
|
|
14538
|
+
SANITIZER_SET_CACHE.set(san, s);
|
|
14539
|
+
}
|
|
14540
|
+
return s;
|
|
14541
|
+
}
|
|
14542
|
+
function sanitizerCoversSink(san, sinkType) {
|
|
14543
|
+
return getSanitizesSet(san).has(sinkType);
|
|
14544
|
+
}
|
|
14545
|
+
|
|
14347
14546
|
// src/analysis/taint-propagation.ts
|
|
14348
14547
|
function buildSanitizersByLine(sanitizers) {
|
|
14349
14548
|
const out2 = /* @__PURE__ */ new Map();
|
|
@@ -14580,7 +14779,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
14580
14779
|
if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
|
|
14581
14780
|
for (const san of sanitizersAtTarget) {
|
|
14582
14781
|
if (isKnownSinkType) {
|
|
14583
|
-
if (san
|
|
14782
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
14584
14783
|
return { sanitized: true, sanitizer: san };
|
|
14585
14784
|
}
|
|
14586
14785
|
} else if (san.sanitizes.length > 0) {
|
|
@@ -14602,7 +14801,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
14602
14801
|
if (!sansAtLine || sansAtLine.length === 0) continue;
|
|
14603
14802
|
for (const san of sansAtLine) {
|
|
14604
14803
|
if (isKnownSinkType) {
|
|
14605
|
-
if (san
|
|
14804
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
14606
14805
|
return { sanitized: true, sanitizer: san };
|
|
14607
14806
|
}
|
|
14608
14807
|
} else if (san.sanitizes.length > 0) {
|