circle-ir 3.167.0 → 3.173.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/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +79 -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 +23 -2
- 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/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 +319 -95
- package/dist/core/circle-ir-core.cjs +193 -72
- package/dist/core/circle-ir-core.js +193 -72
- 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/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)
|
|
@@ -12582,6 +12661,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12582
12661
|
for (const param of method.parameters) {
|
|
12583
12662
|
for (const pattern of patterns) {
|
|
12584
12663
|
if (pattern.annotation && pattern.param_tainted) {
|
|
12664
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12665
|
+
continue;
|
|
12666
|
+
}
|
|
12585
12667
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
12586
12668
|
const paramLine = param.line ?? method.start_line;
|
|
12587
12669
|
sources.push({
|
|
@@ -12602,6 +12684,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12602
12684
|
for (const method of type.methods) {
|
|
12603
12685
|
for (const pattern of patterns) {
|
|
12604
12686
|
if (!pattern.method_annotation) continue;
|
|
12687
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12688
|
+
continue;
|
|
12689
|
+
}
|
|
12605
12690
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
12606
12691
|
for (const param of method.parameters) {
|
|
12607
12692
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -12866,7 +12951,7 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
12866
12951
|
const secondArg = call.arguments.find((a) => a.position === 1);
|
|
12867
12952
|
if (secondArg?.expression) {
|
|
12868
12953
|
const expr = secondArg.expression.trim();
|
|
12869
|
-
if (expr.startsWith("[")) {
|
|
12954
|
+
if (expr.startsWith("[") && !/^\[\s*\]$/.test(expr)) {
|
|
12870
12955
|
return true;
|
|
12871
12956
|
}
|
|
12872
12957
|
}
|
|
@@ -13307,52 +13392,74 @@ function matchesSourcePattern(call, pattern) {
|
|
|
13307
13392
|
}
|
|
13308
13393
|
return false;
|
|
13309
13394
|
}
|
|
13395
|
+
var JS_SOURCE_PATTERN_CACHE = /* @__PURE__ */ new WeakMap();
|
|
13396
|
+
var JS_FALLBACK_COMPILED = (() => {
|
|
13397
|
+
const bases = [
|
|
13398
|
+
{ base: "req\\.params", sourceType: "http_param" },
|
|
13399
|
+
{ base: "req\\.query", sourceType: "http_param" },
|
|
13400
|
+
{ base: "req\\.body", sourceType: "http_body" },
|
|
13401
|
+
{ base: "req\\.headers", sourceType: "http_header" },
|
|
13402
|
+
{ base: "req\\.cookies", sourceType: "http_cookie" },
|
|
13403
|
+
{ base: "req\\.url", sourceType: "http_path" },
|
|
13404
|
+
{ base: "req\\.path", sourceType: "http_path" },
|
|
13405
|
+
{ base: "req\\.originalUrl", sourceType: "http_path" },
|
|
13406
|
+
{ base: "req\\.file", sourceType: "file_input" },
|
|
13407
|
+
{ base: "req\\.files", sourceType: "file_input" },
|
|
13408
|
+
{ base: "request\\.params", sourceType: "http_param" },
|
|
13409
|
+
{ base: "request\\.query", sourceType: "http_param" },
|
|
13410
|
+
{ base: "request\\.body", sourceType: "http_body" },
|
|
13411
|
+
{ base: "request\\.headers", sourceType: "http_header" },
|
|
13412
|
+
{ base: "process\\.env", sourceType: "env_input" },
|
|
13413
|
+
{ base: "process\\.argv", sourceType: "io_input" },
|
|
13414
|
+
{ base: "ctx\\.query", sourceType: "http_param" },
|
|
13415
|
+
{ base: "ctx\\.params", sourceType: "http_param" },
|
|
13416
|
+
{ base: "ctx\\.request", sourceType: "http_body" }
|
|
13417
|
+
];
|
|
13418
|
+
const exact = [];
|
|
13419
|
+
const contained = [];
|
|
13420
|
+
for (const { base, sourceType } of bases) {
|
|
13421
|
+
exact.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
|
|
13422
|
+
contained.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
|
|
13423
|
+
}
|
|
13424
|
+
return { exact, contained };
|
|
13425
|
+
})();
|
|
13426
|
+
function compileJsSourcePatterns(sourcePatterns) {
|
|
13427
|
+
const cached = JS_SOURCE_PATTERN_CACHE.get(sourcePatterns);
|
|
13428
|
+
if (cached) return cached;
|
|
13429
|
+
const exact = [];
|
|
13430
|
+
const contained = [];
|
|
13431
|
+
for (const sp of sourcePatterns) {
|
|
13432
|
+
if (sp.property && sp.object && sp.property_tainted) {
|
|
13433
|
+
exact.push({
|
|
13434
|
+
pattern: new RegExp(`^${sp.object}\\.${sp.property}\\b`),
|
|
13435
|
+
sourceType: sp.type
|
|
13436
|
+
});
|
|
13437
|
+
contained.push({
|
|
13438
|
+
pattern: new RegExp(`\\b${sp.object}\\.${sp.property}\\b`),
|
|
13439
|
+
sourceType: sp.type
|
|
13440
|
+
});
|
|
13441
|
+
}
|
|
13442
|
+
}
|
|
13443
|
+
const compiled = { exact, contained };
|
|
13444
|
+
JS_SOURCE_PATTERN_CACHE.set(sourcePatterns, compiled);
|
|
13445
|
+
return compiled;
|
|
13446
|
+
}
|
|
13310
13447
|
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 });
|
|
13448
|
+
let compiled;
|
|
13449
|
+
if (sourcePatterns && sourcePatterns.length > 0) {
|
|
13450
|
+
compiled = compileJsSourcePatterns(sourcePatterns);
|
|
13451
|
+
if (compiled.exact.length === 0) {
|
|
13452
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
13348
13453
|
}
|
|
13454
|
+
} else {
|
|
13455
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
13349
13456
|
}
|
|
13350
|
-
for (const { pattern, sourceType } of
|
|
13457
|
+
for (const { pattern, sourceType } of compiled.exact) {
|
|
13351
13458
|
if (pattern.test(argExpression)) {
|
|
13352
13459
|
return { isTainted: true, sourceType };
|
|
13353
13460
|
}
|
|
13354
13461
|
}
|
|
13355
|
-
for (const { pattern, sourceType } of
|
|
13462
|
+
for (const { pattern, sourceType } of compiled.contained) {
|
|
13356
13463
|
if (pattern.test(argExpression)) {
|
|
13357
13464
|
return { isTainted: true, sourceType };
|
|
13358
13465
|
}
|
|
@@ -14344,6 +14451,20 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
14344
14451
|
return { visited, lines, hopCapReached };
|
|
14345
14452
|
}
|
|
14346
14453
|
|
|
14454
|
+
// src/analysis/sanitizer-index.ts
|
|
14455
|
+
var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
|
|
14456
|
+
function getSanitizesSet(san) {
|
|
14457
|
+
let s = SANITIZER_SET_CACHE.get(san);
|
|
14458
|
+
if (!s) {
|
|
14459
|
+
s = new Set(san.sanitizes);
|
|
14460
|
+
SANITIZER_SET_CACHE.set(san, s);
|
|
14461
|
+
}
|
|
14462
|
+
return s;
|
|
14463
|
+
}
|
|
14464
|
+
function sanitizerCoversSink(san, sinkType) {
|
|
14465
|
+
return getSanitizesSet(san).has(sinkType);
|
|
14466
|
+
}
|
|
14467
|
+
|
|
14347
14468
|
// src/analysis/taint-propagation.ts
|
|
14348
14469
|
function buildSanitizersByLine(sanitizers) {
|
|
14349
14470
|
const out2 = /* @__PURE__ */ new Map();
|
|
@@ -14580,7 +14701,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
14580
14701
|
if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
|
|
14581
14702
|
for (const san of sanitizersAtTarget) {
|
|
14582
14703
|
if (isKnownSinkType) {
|
|
14583
|
-
if (san
|
|
14704
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
14584
14705
|
return { sanitized: true, sanitizer: san };
|
|
14585
14706
|
}
|
|
14586
14707
|
} else if (san.sanitizes.length > 0) {
|
|
@@ -14602,7 +14723,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
14602
14723
|
if (!sansAtLine || sansAtLine.length === 0) continue;
|
|
14603
14724
|
for (const san of sansAtLine) {
|
|
14604
14725
|
if (isKnownSinkType) {
|
|
14605
|
-
if (san
|
|
14726
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
14606
14727
|
return { sanitized: true, sanitizer: san };
|
|
14607
14728
|
}
|
|
14608
14729
|
} else if (san.sanitizes.length > 0) {
|