circle-ir 3.47.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/constant-propagation/propagator.d.ts +7 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +81 -41
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/html/html-attribute-security-pass.js +14 -9
- package/dist/analysis/html/html-attribute-security-pass.js.map +1 -1
- package/dist/analysis/html/html-extractor.d.ts.map +1 -1
- package/dist/analysis/html/html-extractor.js +16 -11
- package/dist/analysis/html/html-extractor.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 +453 -99
- package/dist/core/circle-ir-core.cjs +251 -64
- package/dist/core/circle-ir-core.js +251 -64
- package/dist/core/extractors/types.js +85 -2
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/core/parser.d.ts +10 -0
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +20 -5
- package/dist/core/parser.js.map +1 -1
- package/dist/languages/plugins/base.d.ts.map +1 -1
- package/dist/languages/plugins/base.js +15 -11
- package/dist/languages/plugins/base.js.map +1 -1
- package/dist/languages/plugins/java.d.ts.map +1 -1
- package/dist/languages/plugins/java.js +8 -4
- package/dist/languages/plugins/java.js.map +1 -1
- package/package.json +1 -1
|
@@ -4152,11 +4152,13 @@ async function parse(code, language) {
|
|
|
4152
4152
|
return tree;
|
|
4153
4153
|
}
|
|
4154
4154
|
function walkTree(node, visitor) {
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
const
|
|
4158
|
-
|
|
4159
|
-
|
|
4155
|
+
const stack = [node];
|
|
4156
|
+
while (stack.length > 0) {
|
|
4157
|
+
const current = stack.pop();
|
|
4158
|
+
visitor(current);
|
|
4159
|
+
for (let i2 = current.childCount - 1; i2 >= 0; i2--) {
|
|
4160
|
+
const child = current.child(i2);
|
|
4161
|
+
if (child) stack.push(child);
|
|
4160
4162
|
}
|
|
4161
4163
|
}
|
|
4162
4164
|
}
|
|
@@ -4796,14 +4798,46 @@ function extractJSClassInfo(node) {
|
|
|
4796
4798
|
end_line: node.endPosition.row + 1
|
|
4797
4799
|
};
|
|
4798
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
|
+
}
|
|
4799
4821
|
function extractJSMethods(body2) {
|
|
4800
4822
|
const methods = [];
|
|
4823
|
+
let pendingDecorators = [];
|
|
4801
4824
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4802
4825
|
const child = body2.child(i2);
|
|
4803
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;
|
|
4804
4833
|
if (child.type === "method_definition") {
|
|
4805
|
-
|
|
4834
|
+
const m = extractJSMethodInfo(child);
|
|
4835
|
+
if (pendingDecorators.length > 0) {
|
|
4836
|
+
m.annotations = pendingDecorators;
|
|
4837
|
+
}
|
|
4838
|
+
methods.push(m);
|
|
4806
4839
|
}
|
|
4840
|
+
pendingDecorators = [];
|
|
4807
4841
|
}
|
|
4808
4842
|
return methods;
|
|
4809
4843
|
}
|
|
@@ -4965,10 +4999,18 @@ function extractJSParameters(params) {
|
|
|
4965
4999
|
if (typeNode) {
|
|
4966
5000
|
paramType = getNodeText(typeNode).replace(/^:\s*/, "");
|
|
4967
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
|
+
}
|
|
4968
5010
|
parameters.push({
|
|
4969
5011
|
name: paramName,
|
|
4970
5012
|
type: paramType,
|
|
4971
|
-
annotations:
|
|
5013
|
+
annotations: decorators,
|
|
4972
5014
|
line: child.startPosition.row + 1
|
|
4973
5015
|
});
|
|
4974
5016
|
}
|
|
@@ -11015,6 +11057,26 @@ var DEFAULT_SINKS = [
|
|
|
11015
11057
|
{ method: "setQuotedArgumentsEnabled", class: "Shell", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
11016
11058
|
// Sandbox/script security
|
|
11017
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"] },
|
|
11018
11080
|
// =========================================================================
|
|
11019
11081
|
// Node.js/Express Sinks
|
|
11020
11082
|
// =========================================================================
|
|
@@ -11067,13 +11129,47 @@ var DEFAULT_SINKS = [
|
|
|
11067
11129
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11068
11130
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11069
11131
|
{ method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11070
|
-
// 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.
|
|
11071
11140
|
{ method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11072
11141
|
{ method: "findOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11073
11142
|
{ method: "updateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11074
11143
|
{ method: "updateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11075
11144
|
{ method: "deleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
|
|
11076
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"] },
|
|
11077
11173
|
// Node.js SSRF (HTTP clients)
|
|
11078
11174
|
{ method: "get", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11079
11175
|
{ method: "post", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -11095,6 +11191,24 @@ var DEFAULT_SINKS = [
|
|
|
11095
11191
|
{ method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11096
11192
|
// node-fetch
|
|
11097
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"] },
|
|
11098
11212
|
// =========================================================================
|
|
11099
11213
|
// Python Sinks
|
|
11100
11214
|
// =========================================================================
|
|
@@ -11136,7 +11250,12 @@ var DEFAULT_SINKS = [
|
|
|
11136
11250
|
{ method: "rmtree", class: "shutil", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
11137
11251
|
{ method: "send_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11138
11252
|
// Python XSS / SSTI
|
|
11139
|
-
|
|
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"] },
|
|
11140
11259
|
{ method: "Markup", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11141
11260
|
{ method: "mark_safe", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11142
11261
|
// Python SSRF
|
|
@@ -11499,6 +11618,13 @@ var DEFAULT_SANITIZERS = [
|
|
|
11499
11618
|
{ method: "secure_filename", class: "werkzeug.utils", removes: ["path_traversal"] },
|
|
11500
11619
|
{ method: "basename", class: "os.path", removes: ["path_traversal"] },
|
|
11501
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"] },
|
|
11502
11628
|
// Python Type coercion
|
|
11503
11629
|
{ method: "int", removes: ["sql_injection", "command_injection", "xss"] },
|
|
11504
11630
|
{ method: "float", removes: ["sql_injection", "command_injection"] },
|
|
@@ -11560,7 +11686,7 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
11560
11686
|
];
|
|
11561
11687
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
11562
11688
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
11563
|
-
const sources = findSources(calls, types, config.sources, sourceLines);
|
|
11689
|
+
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
11564
11690
|
const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
|
|
11565
11691
|
const sanitizers = findSanitizers(calls, types, config.sanitizers);
|
|
11566
11692
|
return { sources, sinks, sanitizers };
|
|
@@ -11578,7 +11704,7 @@ function attachSourceLineCode(sources, sinks, code) {
|
|
|
11578
11704
|
}
|
|
11579
11705
|
}
|
|
11580
11706
|
}
|
|
11581
|
-
function findSources(calls, types, patterns, sourceLines) {
|
|
11707
|
+
function findSources(calls, types, patterns, sourceLines, language) {
|
|
11582
11708
|
const sources = [];
|
|
11583
11709
|
for (const call of calls) {
|
|
11584
11710
|
for (const pattern of patterns) {
|
|
@@ -11631,23 +11757,29 @@ function findSources(calls, types, patterns, sourceLines) {
|
|
|
11631
11757
|
}
|
|
11632
11758
|
}
|
|
11633
11759
|
}
|
|
11634
|
-
const
|
|
11760
|
+
const RUST_EXTRACTOR_KIND = /(?:^|::)(Json|Form|Query|Path|Extension|Multipart|Body|Bytes)(?:<|$)/;
|
|
11635
11761
|
for (const type of types) {
|
|
11636
11762
|
for (const method of type.methods) {
|
|
11637
11763
|
for (const param of method.parameters) {
|
|
11638
|
-
if (param.type
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
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
|
+
});
|
|
11651
11783
|
}
|
|
11652
11784
|
}
|
|
11653
11785
|
}
|
|
@@ -11729,6 +11861,15 @@ function findSources(calls, types, patterns, sourceLines) {
|
|
|
11729
11861
|
s.code = sourceLines[s.line - 1]?.trim();
|
|
11730
11862
|
}
|
|
11731
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
|
+
}
|
|
11732
11873
|
return result;
|
|
11733
11874
|
}
|
|
11734
11875
|
function isInterproceduralTaintableType(typeName) {
|
|
@@ -11834,6 +11975,20 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
11834
11975
|
}
|
|
11835
11976
|
return false;
|
|
11836
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
|
+
}
|
|
11837
11992
|
var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
|
|
11838
11993
|
function argIsClassLiteral(call, position) {
|
|
11839
11994
|
const arg = call.arguments.find((a) => a.position === position);
|
|
@@ -11850,6 +12005,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
11850
12005
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
11851
12006
|
continue;
|
|
11852
12007
|
}
|
|
12008
|
+
if (isSafePythonSubprocessCall(call, pattern, language)) {
|
|
12009
|
+
continue;
|
|
12010
|
+
}
|
|
11853
12011
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
11854
12012
|
continue;
|
|
11855
12013
|
}
|
|
@@ -12252,7 +12410,12 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12252
12410
|
"controller",
|
|
12253
12411
|
"task",
|
|
12254
12412
|
"thread",
|
|
12255
|
-
"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"
|
|
12256
12419
|
]);
|
|
12257
12420
|
const isAmbiguous = ambiguousIdentifiers.has(lowerReceiver);
|
|
12258
12421
|
if (!isAmbiguous && lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
|
|
@@ -12262,7 +12425,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12262
12425
|
}
|
|
12263
12426
|
if (!isAmbiguous && lowerReceiver.length >= 2) {
|
|
12264
12427
|
if (lowerClass.startsWith(lowerReceiver) || lowerClass.endsWith(lowerReceiver)) {
|
|
12265
|
-
|
|
12428
|
+
if (lowerReceiver.length / lowerClass.length >= 0.4) {
|
|
12429
|
+
return true;
|
|
12430
|
+
}
|
|
12266
12431
|
}
|
|
12267
12432
|
}
|
|
12268
12433
|
if (!isAmbiguous && lowerReceiver.length >= 3) {
|
|
@@ -12285,6 +12450,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12285
12450
|
ps: ["PreparedStatement"],
|
|
12286
12451
|
rs: ["ResultSet"],
|
|
12287
12452
|
template: ["JdbcTemplate"],
|
|
12453
|
+
cur: ["Cursor"],
|
|
12454
|
+
// Python DB-API cursor — see ambiguousIdentifiers note
|
|
12455
|
+
cursor: ["Cursor"],
|
|
12288
12456
|
// I/O
|
|
12289
12457
|
writer: ["PrintWriter"],
|
|
12290
12458
|
out: ["PrintWriter", "OutputStream"],
|
|
@@ -13972,8 +14140,10 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
13972
14140
|
* These are variables declared directly in the class body, not inside methods.
|
|
13973
14141
|
*/
|
|
13974
14142
|
collectClassFields(root) {
|
|
13975
|
-
const
|
|
13976
|
-
|
|
14143
|
+
const stack = [root];
|
|
14144
|
+
while (stack.length > 0) {
|
|
14145
|
+
const n = stack.pop();
|
|
14146
|
+
if (!n) continue;
|
|
13977
14147
|
if (n.type === "class_body") {
|
|
13978
14148
|
for (const child of n.children) {
|
|
13979
14149
|
if (child.type === "field_declaration") {
|
|
@@ -13987,32 +14157,28 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
13987
14157
|
}
|
|
13988
14158
|
}
|
|
13989
14159
|
}
|
|
13990
|
-
|
|
13991
|
-
traverse(child, true, true);
|
|
13992
|
-
} else {
|
|
13993
|
-
traverse(child, true, false);
|
|
13994
|
-
}
|
|
14160
|
+
stack.push(child);
|
|
13995
14161
|
}
|
|
13996
|
-
|
|
14162
|
+
continue;
|
|
13997
14163
|
}
|
|
13998
14164
|
for (const child of n.children) {
|
|
13999
|
-
|
|
14165
|
+
stack.push(child);
|
|
14000
14166
|
}
|
|
14001
|
-
}
|
|
14002
|
-
traverse(root, false, false);
|
|
14167
|
+
}
|
|
14003
14168
|
}
|
|
14004
14169
|
findAllMethods(node) {
|
|
14005
14170
|
const methods = [];
|
|
14006
|
-
const
|
|
14007
|
-
|
|
14171
|
+
const stack = [node];
|
|
14172
|
+
while (stack.length > 0) {
|
|
14173
|
+
const n = stack.pop();
|
|
14174
|
+
if (!n) continue;
|
|
14008
14175
|
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
14009
14176
|
methods.push(n);
|
|
14010
14177
|
}
|
|
14011
14178
|
for (const child of n.children) {
|
|
14012
|
-
if (child)
|
|
14179
|
+
if (child) stack.push(child);
|
|
14013
14180
|
}
|
|
14014
|
-
}
|
|
14015
|
-
traverse(node);
|
|
14181
|
+
}
|
|
14016
14182
|
return methods;
|
|
14017
14183
|
}
|
|
14018
14184
|
getMethodName(method) {
|
|
@@ -14063,9 +14229,24 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
14063
14229
|
// AST Visitor
|
|
14064
14230
|
// ===========================================================================
|
|
14065
14231
|
visit(node) {
|
|
14232
|
+
const stack = [node];
|
|
14233
|
+
while (stack.length > 0) {
|
|
14234
|
+
const current = stack.pop();
|
|
14235
|
+
if (this.visitOne(current)) continue;
|
|
14236
|
+
for (let i2 = current.children.length - 1; i2 >= 0; i2--) {
|
|
14237
|
+
stack.push(current.children[i2]);
|
|
14238
|
+
}
|
|
14239
|
+
}
|
|
14240
|
+
}
|
|
14241
|
+
/**
|
|
14242
|
+
* Visit a single node. Returns true if the handler already descended into
|
|
14243
|
+
* children (and the caller should NOT push them), false to fall through to
|
|
14244
|
+
* the default pre-order descent.
|
|
14245
|
+
*/
|
|
14246
|
+
visitOne(node) {
|
|
14066
14247
|
const line = getNodeLine(node);
|
|
14067
14248
|
if (this.unreachableLines.has(line)) {
|
|
14068
|
-
return;
|
|
14249
|
+
return true;
|
|
14069
14250
|
}
|
|
14070
14251
|
if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
|
|
14071
14252
|
this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
|
|
@@ -14074,43 +14255,41 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
14074
14255
|
case "method_declaration":
|
|
14075
14256
|
case "constructor_declaration":
|
|
14076
14257
|
this.handleMethodDeclaration(node);
|
|
14077
|
-
return;
|
|
14258
|
+
return true;
|
|
14078
14259
|
// Don't visit children directly, handleMethodDeclaration does it
|
|
14079
14260
|
case "local_variable_declaration":
|
|
14080
14261
|
this.handleVariableDeclaration(node);
|
|
14081
|
-
|
|
14262
|
+
return false;
|
|
14082
14263
|
case "assignment_expression":
|
|
14083
14264
|
this.handleAssignment(node);
|
|
14084
|
-
|
|
14265
|
+
return false;
|
|
14085
14266
|
case "update_expression":
|
|
14086
14267
|
this.handleUpdateExpression(node);
|
|
14087
|
-
|
|
14268
|
+
return false;
|
|
14088
14269
|
case "if_statement":
|
|
14089
14270
|
this.handleIfStatement(node);
|
|
14090
|
-
return;
|
|
14271
|
+
return true;
|
|
14091
14272
|
case "switch_expression":
|
|
14092
14273
|
case "switch_statement":
|
|
14093
14274
|
this.handleSwitch(node);
|
|
14094
|
-
return;
|
|
14275
|
+
return true;
|
|
14095
14276
|
case "ternary_expression":
|
|
14096
14277
|
this.handleTernary(node);
|
|
14097
|
-
|
|
14278
|
+
return false;
|
|
14098
14279
|
case "expression_statement":
|
|
14099
14280
|
this.handleExpressionStatement(node);
|
|
14100
|
-
|
|
14281
|
+
return false;
|
|
14101
14282
|
case "for_statement":
|
|
14102
14283
|
case "enhanced_for_statement":
|
|
14103
14284
|
case "while_statement":
|
|
14104
14285
|
case "do_statement":
|
|
14105
14286
|
this.handleLoopStatement(node);
|
|
14106
|
-
return;
|
|
14287
|
+
return true;
|
|
14107
14288
|
case "synchronized_statement":
|
|
14108
14289
|
this.handleSynchronizedStatement(node);
|
|
14109
|
-
return;
|
|
14290
|
+
return true;
|
|
14110
14291
|
default:
|
|
14111
|
-
|
|
14112
|
-
this.visit(child);
|
|
14113
|
-
}
|
|
14292
|
+
return false;
|
|
14114
14293
|
}
|
|
14115
14294
|
}
|
|
14116
14295
|
/**
|
|
@@ -14878,6 +15057,19 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
14878
15057
|
return null;
|
|
14879
15058
|
}
|
|
14880
15059
|
isTaintedExpression(node) {
|
|
15060
|
+
const stack = [node];
|
|
15061
|
+
while (stack.length > 0) {
|
|
15062
|
+
const current = stack.pop();
|
|
15063
|
+
const result = this.isTaintedExpressionStep(current);
|
|
15064
|
+
if (result === true) return true;
|
|
15065
|
+
if (result === false) continue;
|
|
15066
|
+
for (let i2 = current.children.length - 1; i2 >= 0; i2--) {
|
|
15067
|
+
stack.push(current.children[i2]);
|
|
15068
|
+
}
|
|
15069
|
+
}
|
|
15070
|
+
return false;
|
|
15071
|
+
}
|
|
15072
|
+
isTaintedExpressionStep(node) {
|
|
14881
15073
|
const text = getNodeText2(node, this.source);
|
|
14882
15074
|
if (node.type === "method_invocation") {
|
|
14883
15075
|
const nameNode = node.childForFieldName("name");
|
|
@@ -15137,12 +15329,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15137
15329
|
}
|
|
15138
15330
|
return isTainted;
|
|
15139
15331
|
}
|
|
15140
|
-
|
|
15141
|
-
if (this.isTaintedExpression(child)) {
|
|
15142
|
-
return true;
|
|
15143
|
-
}
|
|
15144
|
-
}
|
|
15145
|
-
return false;
|
|
15332
|
+
return void 0;
|
|
15146
15333
|
}
|
|
15147
15334
|
checkCollectionTaint(node) {
|
|
15148
15335
|
const objectNode = node.childForFieldName("object");
|
|
@@ -15313,7 +15500,7 @@ function isFalsePositive(result, sinkLine, taintedVar) {
|
|
|
15313
15500
|
if (varValue && varValue.type !== "unknown" && !result.tainted.has(taintedVar)) {
|
|
15314
15501
|
return { isFalsePositive: true, reason: `variable_is_constant: ${varValue.value}` };
|
|
15315
15502
|
}
|
|
15316
|
-
if (result.symbols.
|
|
15503
|
+
if (result.symbols.has(taintedVar) && !result.tainted.has(taintedVar)) {
|
|
15317
15504
|
return { isFalsePositive: true, reason: "variable_not_tainted" };
|
|
15318
15505
|
}
|
|
15319
15506
|
return { isFalsePositive: false, reason: null };
|