circle-ir 3.48.0 → 3.49.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 +86 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/index.js +16 -6
- package/dist/analysis/constant-propagation/index.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +53 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -0
- package/dist/analysis/passes/insecure-cookie-pass.js +109 -0
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +7 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +14 -0
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +50 -0
- package/dist/analysis/passes/language-sources-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 +21 -2
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +94 -3
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +117 -20
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +3 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +356 -26
- package/dist/core/circle-ir-core.cjs +189 -23
- package/dist/core/circle-ir-core.js +189 -23
- package/dist/core/extractors/types.js +85 -2
- package/dist/core/extractors/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -4798,14 +4798,46 @@ function extractJSClassInfo(node) {
|
|
|
4798
4798
|
end_line: node.endPosition.row + 1
|
|
4799
4799
|
};
|
|
4800
4800
|
}
|
|
4801
|
+
function extractDecoratorName(node) {
|
|
4802
|
+
const child = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
4803
|
+
if (!child) return null;
|
|
4804
|
+
if (child.type === "identifier") return getNodeText(child);
|
|
4805
|
+
if (child.type === "call_expression") {
|
|
4806
|
+
const fn = child.childForFieldName("function");
|
|
4807
|
+
if (fn) {
|
|
4808
|
+
if (fn.type === "identifier") return getNodeText(fn);
|
|
4809
|
+
if (fn.type === "member_expression") {
|
|
4810
|
+
const propNode = fn.childForFieldName("property");
|
|
4811
|
+
if (propNode) return getNodeText(propNode);
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
if (child.type === "member_expression") {
|
|
4816
|
+
const propNode = child.childForFieldName("property");
|
|
4817
|
+
if (propNode) return getNodeText(propNode);
|
|
4818
|
+
}
|
|
4819
|
+
return null;
|
|
4820
|
+
}
|
|
4801
4821
|
function extractJSMethods(body2) {
|
|
4802
4822
|
const methods = [];
|
|
4823
|
+
let pendingDecorators = [];
|
|
4803
4824
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4804
4825
|
const child = body2.child(i2);
|
|
4805
4826
|
if (!child) continue;
|
|
4827
|
+
if (child.type === "decorator") {
|
|
4828
|
+
const name2 = extractDecoratorName(child);
|
|
4829
|
+
if (name2) pendingDecorators.push(name2);
|
|
4830
|
+
continue;
|
|
4831
|
+
}
|
|
4832
|
+
if (child.type === "comment") continue;
|
|
4806
4833
|
if (child.type === "method_definition") {
|
|
4807
|
-
|
|
4834
|
+
const m = extractJSMethodInfo(child);
|
|
4835
|
+
if (pendingDecorators.length > 0) {
|
|
4836
|
+
m.annotations = pendingDecorators;
|
|
4837
|
+
}
|
|
4838
|
+
methods.push(m);
|
|
4808
4839
|
}
|
|
4840
|
+
pendingDecorators = [];
|
|
4809
4841
|
}
|
|
4810
4842
|
return methods;
|
|
4811
4843
|
}
|
|
@@ -4967,10 +4999,18 @@ function extractJSParameters(params) {
|
|
|
4967
4999
|
if (typeNode) {
|
|
4968
5000
|
paramType = getNodeText(typeNode).replace(/^:\s*/, "");
|
|
4969
5001
|
}
|
|
5002
|
+
const decorators = [];
|
|
5003
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
5004
|
+
const c = child.child(j);
|
|
5005
|
+
if (c && c.type === "decorator") {
|
|
5006
|
+
const name2 = extractDecoratorName(c);
|
|
5007
|
+
if (name2) decorators.push(name2);
|
|
5008
|
+
}
|
|
5009
|
+
}
|
|
4970
5010
|
parameters.push({
|
|
4971
5011
|
name: paramName,
|
|
4972
5012
|
type: paramType,
|
|
4973
|
-
annotations:
|
|
5013
|
+
annotations: decorators,
|
|
4974
5014
|
line: child.startPosition.row + 1
|
|
4975
5015
|
});
|
|
4976
5016
|
}
|
|
@@ -11017,6 +11057,26 @@ var DEFAULT_SINKS = [
|
|
|
11017
11057
|
{ method: "setQuotedArgumentsEnabled", class: "Shell", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
11018
11058
|
// Sandbox/script security
|
|
11019
11059
|
{ method: "onNewInstance", class: "SandboxInterceptor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11060
|
+
// Java Log Injection (slf4j / logback / java.util.logging) — CWE-117
|
|
11061
|
+
// Issue #44: log.info/warn/error/debug emit the message argument and any
|
|
11062
|
+
// {} format arguments to the log stream. Untrusted input forwarded into
|
|
11063
|
+
// these calls allows log forging (newline injection) and downstream log
|
|
11064
|
+
// analyzer pollution. Scoped to `java` so the generic method names don't
|
|
11065
|
+
// collide with JS console / Python logger entries below.
|
|
11066
|
+
{ method: "info", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
11067
|
+
{ method: "warn", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
11068
|
+
{ method: "error", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
11069
|
+
{ method: "debug", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
11070
|
+
{ method: "trace", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
|
|
11071
|
+
// java.util.logging.Logger uses the same class name `Logger` — same entries above cover it.
|
|
11072
|
+
// Severity-tagged levels: SEVERE/WARNING/INFO/CONFIG/FINE/FINER/FINEST
|
|
11073
|
+
{ method: "severe", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
|
|
11074
|
+
{ method: "warning", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
|
|
11075
|
+
{ method: "config", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
|
|
11076
|
+
{ method: "fine", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
|
|
11077
|
+
{ method: "finer", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
|
|
11078
|
+
{ method: "finest", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
|
|
11079
|
+
{ method: "log", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3], languages: ["java"] },
|
|
11020
11080
|
// =========================================================================
|
|
11021
11081
|
// Node.js/Express Sinks
|
|
11022
11082
|
// =========================================================================
|
|
@@ -11069,13 +11129,47 @@ var DEFAULT_SINKS = [
|
|
|
11069
11129
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11070
11130
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11071
11131
|
{ method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11072
|
-
// Node.js NoSQL Injection (MongoDB)
|
|
11132
|
+
// Node.js NoSQL Injection (MongoDB native driver + mongoose) — CWE-943
|
|
11133
|
+
// Issue #45: the bare `class: 'Collection'` constraint missed mongoose's
|
|
11134
|
+
// fluent chains (mongoose.connection.db.collection('x').find({...})) and
|
|
11135
|
+
// Model.find calls because the call-site receiver type does not resolve
|
|
11136
|
+
// to `Collection`. Add classless+language-scoped entries for the
|
|
11137
|
+
// MongoDB-specific method names (findOne/aggregate/updateOne/etc.) and
|
|
11138
|
+
// mongoose `Model`/`Query` class entries. Bare `find` stays class-scoped
|
|
11139
|
+
// to avoid colliding with Array.prototype.find.
|
|
11073
11140
|
{ method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11074
11141
|
{ method: "findOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11075
11142
|
{ method: "updateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11076
11143
|
{ method: "updateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11077
11144
|
{ method: "deleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11078
11145
|
{ method: "deleteMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11146
|
+
// Mongoose Model/Query class entries — Model.find/findOne/etc.
|
|
11147
|
+
{ method: "find", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11148
|
+
{ method: "findOne", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11149
|
+
{ method: "findById", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11150
|
+
{ method: "findOneAndUpdate", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11151
|
+
{ method: "findOneAndDelete", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11152
|
+
{ method: "findOneAndReplace", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11153
|
+
{ method: "updateOne", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11154
|
+
{ method: "updateMany", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11155
|
+
{ method: "deleteOne", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11156
|
+
{ method: "deleteMany", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11157
|
+
{ method: "countDocuments", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11158
|
+
{ method: "aggregate", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11159
|
+
// Mongoose Query class entries — chain methods returning Query
|
|
11160
|
+
{ method: "where", class: "Query", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11161
|
+
{ method: "equals", class: "Query", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11162
|
+
// Classless MongoDB-specific method names (rare outside MongoDB APIs) —
|
|
11163
|
+
// language-scoped to JS/TS. Excludes plain `find` (Array.prototype.find FP).
|
|
11164
|
+
{ method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11165
|
+
{ method: "findOneAndUpdate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11166
|
+
{ method: "findOneAndDelete", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11167
|
+
{ method: "findOneAndReplace", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11168
|
+
{ method: "updateOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11169
|
+
{ method: "updateMany", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
|
|
11170
|
+
{ method: "deleteOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11171
|
+
{ method: "deleteMany", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11172
|
+
{ method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11079
11173
|
// Node.js SSRF (HTTP clients)
|
|
11080
11174
|
{ method: "get", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11081
11175
|
{ method: "post", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -11097,6 +11191,24 @@ var DEFAULT_SINKS = [
|
|
|
11097
11191
|
{ method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11098
11192
|
// node-fetch
|
|
11099
11193
|
{ method: "default", class: "node-fetch", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11194
|
+
// Node.js / JavaScript Log Injection (console.*) — CWE-117
|
|
11195
|
+
// Issue #44: console.log/warn/error/info with tainted template literals
|
|
11196
|
+
// allow log forging (newline-injection) and downstream log analyzer
|
|
11197
|
+
// pollution. Scoped to JS/TS so the bare class `console` doesn't collide
|
|
11198
|
+
// with Python `console` module or Java identifiers.
|
|
11199
|
+
{ method: "log", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11200
|
+
{ method: "warn", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11201
|
+
{ method: "error", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11202
|
+
{ method: "info", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11203
|
+
{ method: "debug", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11204
|
+
{ method: "trace", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11205
|
+
// Node.js / Express Open Redirect — CWE-601
|
|
11206
|
+
// Issue #46: `res.redirect(req.query.next)` did not fire because the
|
|
11207
|
+
// legacy `class: 'Response'` constraint depended on receiver type
|
|
11208
|
+
// resolution of the Express `res` parameter. Mirror Python's classless
|
|
11209
|
+
// pattern with a language-scoped classless entry. The method name
|
|
11210
|
+
// `redirect` is rare outside HTTP frameworks so the FP risk is low.
|
|
11211
|
+
{ method: "redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11100
11212
|
// =========================================================================
|
|
11101
11213
|
// Python Sinks
|
|
11102
11214
|
// =========================================================================
|
|
@@ -11138,7 +11250,12 @@ var DEFAULT_SINKS = [
|
|
|
11138
11250
|
{ method: "rmtree", class: "shutil", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
11139
11251
|
{ method: "send_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11140
11252
|
// Python XSS / SSTI
|
|
11141
|
-
|
|
11253
|
+
// Issue #54: Flask's `render_template_string(template_str)` with an
|
|
11254
|
+
// attacker-controlled template string is Server-Side Template Injection
|
|
11255
|
+
// (Jinja2 SSTI → RCE), not reflected XSS. Classify as code_injection
|
|
11256
|
+
// (CWE-94) with critical severity to match `jinja2.Template().render()`
|
|
11257
|
+
// and `Template.from_string()` entries above.
|
|
11258
|
+
{ method: "render_template_string", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11142
11259
|
{ method: "Markup", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11143
11260
|
{ method: "mark_safe", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11144
11261
|
// Python SSRF
|
|
@@ -11501,6 +11618,13 @@ var DEFAULT_SANITIZERS = [
|
|
|
11501
11618
|
{ method: "secure_filename", class: "werkzeug.utils", removes: ["path_traversal"] },
|
|
11502
11619
|
{ method: "basename", class: "os.path", removes: ["path_traversal"] },
|
|
11503
11620
|
{ method: "normpath", class: "os.path", removes: ["path_traversal"] },
|
|
11621
|
+
// Issue #48 part 2: realpath/abspath are canonical Python path-canonicalization
|
|
11622
|
+
// functions (analogous to Java File.getCanonicalPath). Register on both
|
|
11623
|
+
// `os.path` and the bare `path` receiver to cover `import os.path as path`.
|
|
11624
|
+
{ method: "realpath", class: "os.path", removes: ["path_traversal"] },
|
|
11625
|
+
{ method: "abspath", class: "os.path", removes: ["path_traversal"] },
|
|
11626
|
+
{ method: "realpath", class: "path", removes: ["path_traversal"] },
|
|
11627
|
+
{ method: "abspath", class: "path", removes: ["path_traversal"] },
|
|
11504
11628
|
// Python Type coercion
|
|
11505
11629
|
{ method: "int", removes: ["sql_injection", "command_injection", "xss"] },
|
|
11506
11630
|
{ method: "float", removes: ["sql_injection", "command_injection"] },
|
|
@@ -11562,7 +11686,7 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
11562
11686
|
];
|
|
11563
11687
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
11564
11688
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
11565
|
-
const sources = findSources(calls, types, config.sources, sourceLines);
|
|
11689
|
+
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
11566
11690
|
const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
|
|
11567
11691
|
const sanitizers = findSanitizers(calls, types, config.sanitizers);
|
|
11568
11692
|
return { sources, sinks, sanitizers };
|
|
@@ -11580,7 +11704,7 @@ function attachSourceLineCode(sources, sinks, code) {
|
|
|
11580
11704
|
}
|
|
11581
11705
|
}
|
|
11582
11706
|
}
|
|
11583
|
-
function findSources(calls, types, patterns, sourceLines) {
|
|
11707
|
+
function findSources(calls, types, patterns, sourceLines, language) {
|
|
11584
11708
|
const sources = [];
|
|
11585
11709
|
for (const call of calls) {
|
|
11586
11710
|
for (const pattern of patterns) {
|
|
@@ -11633,23 +11757,29 @@ function findSources(calls, types, patterns, sourceLines) {
|
|
|
11633
11757
|
}
|
|
11634
11758
|
}
|
|
11635
11759
|
}
|
|
11636
|
-
const
|
|
11760
|
+
const RUST_EXTRACTOR_KIND = /(?:^|::)(Json|Form|Query|Path|Extension|Multipart|Body|Bytes)(?:<|$)/;
|
|
11637
11761
|
for (const type of types) {
|
|
11638
11762
|
for (const method of type.methods) {
|
|
11639
11763
|
for (const param of method.parameters) {
|
|
11640
|
-
if (param.type
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11764
|
+
if (!param.type) continue;
|
|
11765
|
+
const kindMatch = RUST_EXTRACTOR_KIND.exec(param.type);
|
|
11766
|
+
if (!kindMatch) continue;
|
|
11767
|
+
const kind = kindMatch[1];
|
|
11768
|
+
if (kind === "Extension") continue;
|
|
11769
|
+
const sourceType = kind === "Form" || kind === "Query" || kind === "Path" ? "http_param" : "http_body";
|
|
11770
|
+
const paramLine = param.line ?? method.start_line;
|
|
11771
|
+
const alreadyExists = sources.some(
|
|
11772
|
+
(s) => s.line === paramLine && s.variable === param.name
|
|
11773
|
+
);
|
|
11774
|
+
if (alreadyExists) continue;
|
|
11775
|
+
sources.push({
|
|
11776
|
+
type: sourceType,
|
|
11777
|
+
location: `${param.type} ${param.name} in ${method.name}`,
|
|
11778
|
+
severity: "high",
|
|
11779
|
+
line: paramLine,
|
|
11780
|
+
confidence: 1,
|
|
11781
|
+
variable: param.name
|
|
11782
|
+
});
|
|
11653
11783
|
}
|
|
11654
11784
|
}
|
|
11655
11785
|
}
|
|
@@ -11731,6 +11861,15 @@ function findSources(calls, types, patterns, sourceLines) {
|
|
|
11731
11861
|
s.code = sourceLines[s.line - 1]?.trim();
|
|
11732
11862
|
}
|
|
11733
11863
|
}
|
|
11864
|
+
if (language === "rust" && sourceLines) {
|
|
11865
|
+
const LET_BINDING = /^\s*let\s+(?:mut\s+)?([A-Za-z_]\w*)\s*(?::\s*[^=]+)?=/;
|
|
11866
|
+
for (const s of result) {
|
|
11867
|
+
if (s.variable && s.variable.length > 0) continue;
|
|
11868
|
+
const lineText = sourceLines[s.line - 1] ?? "";
|
|
11869
|
+
const m = LET_BINDING.exec(lineText);
|
|
11870
|
+
if (m) s.variable = m[1];
|
|
11871
|
+
}
|
|
11872
|
+
}
|
|
11734
11873
|
return result;
|
|
11735
11874
|
}
|
|
11736
11875
|
function isInterproceduralTaintableType(typeName) {
|
|
@@ -11836,6 +11975,20 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
11836
11975
|
}
|
|
11837
11976
|
return false;
|
|
11838
11977
|
}
|
|
11978
|
+
function isSafePythonSubprocessCall(call, pattern, language) {
|
|
11979
|
+
if (language !== "python") return false;
|
|
11980
|
+
if (pattern.type !== "command_injection") return false;
|
|
11981
|
+
if (pattern.class !== "subprocess") return false;
|
|
11982
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
11983
|
+
if (!arg0) return false;
|
|
11984
|
+
const expr0 = (arg0.literal ?? arg0.expression ?? "").trim();
|
|
11985
|
+
if (!expr0.startsWith("[")) return false;
|
|
11986
|
+
for (const a of call.arguments) {
|
|
11987
|
+
const e = (a.expression ?? "").trim();
|
|
11988
|
+
if (/^shell\s*=\s*True\b/.test(e)) return false;
|
|
11989
|
+
}
|
|
11990
|
+
return true;
|
|
11991
|
+
}
|
|
11839
11992
|
var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
|
|
11840
11993
|
function argIsClassLiteral(call, position) {
|
|
11841
11994
|
const arg = call.arguments.find((a) => a.position === position);
|
|
@@ -11852,6 +12005,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
11852
12005
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
11853
12006
|
continue;
|
|
11854
12007
|
}
|
|
12008
|
+
if (isSafePythonSubprocessCall(call, pattern, language)) {
|
|
12009
|
+
continue;
|
|
12010
|
+
}
|
|
11855
12011
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
11856
12012
|
continue;
|
|
11857
12013
|
}
|
|
@@ -12254,7 +12410,12 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12254
12410
|
"controller",
|
|
12255
12411
|
"task",
|
|
12256
12412
|
"thread",
|
|
12257
|
-
"job"
|
|
12413
|
+
"job",
|
|
12414
|
+
// Short Python DB abbreviation; would otherwise prefix-match obscure XSS
|
|
12415
|
+
// sink classes like XWiki's `CurrentTimePlugin` ('current'.startsWith('cur'))
|
|
12416
|
+
// via the CamelCase word prefix heuristic and produce an xss FP on every
|
|
12417
|
+
// `cur.execute(...)`. Resolved via commonMappings → ['Cursor']. See #65 / #48 pt3.
|
|
12418
|
+
"cur"
|
|
12258
12419
|
]);
|
|
12259
12420
|
const isAmbiguous = ambiguousIdentifiers.has(lowerReceiver);
|
|
12260
12421
|
if (!isAmbiguous && lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
|
|
@@ -12264,7 +12425,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12264
12425
|
}
|
|
12265
12426
|
if (!isAmbiguous && lowerReceiver.length >= 2) {
|
|
12266
12427
|
if (lowerClass.startsWith(lowerReceiver) || lowerClass.endsWith(lowerReceiver)) {
|
|
12267
|
-
|
|
12428
|
+
if (lowerReceiver.length / lowerClass.length >= 0.4) {
|
|
12429
|
+
return true;
|
|
12430
|
+
}
|
|
12268
12431
|
}
|
|
12269
12432
|
}
|
|
12270
12433
|
if (!isAmbiguous && lowerReceiver.length >= 3) {
|
|
@@ -12287,6 +12450,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12287
12450
|
ps: ["PreparedStatement"],
|
|
12288
12451
|
rs: ["ResultSet"],
|
|
12289
12452
|
template: ["JdbcTemplate"],
|
|
12453
|
+
cur: ["Cursor"],
|
|
12454
|
+
// Python DB-API cursor — see ambiguousIdentifiers note
|
|
12455
|
+
cursor: ["Cursor"],
|
|
12290
12456
|
// I/O
|
|
12291
12457
|
writer: ["PrintWriter"],
|
|
12292
12458
|
out: ["PrintWriter", "OutputStream"],
|
|
@@ -15334,7 +15500,7 @@ function isFalsePositive(result, sinkLine, taintedVar) {
|
|
|
15334
15500
|
if (varValue && varValue.type !== "unknown" && !result.tainted.has(taintedVar)) {
|
|
15335
15501
|
return { isFalsePositive: true, reason: `variable_is_constant: ${varValue.value}` };
|
|
15336
15502
|
}
|
|
15337
|
-
if (result.symbols.
|
|
15503
|
+
if (result.symbols.has(taintedVar) && !result.tainted.has(taintedVar)) {
|
|
15338
15504
|
return { isFalsePositive: true, reason: "variable_not_tainted" };
|
|
15339
15505
|
}
|
|
15340
15506
|
return { isFalsePositive: false, reason: null };
|