cognium-dev 3.57.0 → 3.59.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/cli.js +586 -45
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -10233,10 +10233,10 @@ var DEFAULT_SOURCES = [
|
|
|
10233
10233
|
{ method: "cookie", class: "HttpRequest", type: "http_cookie", severity: "high", return_tainted: true },
|
|
10234
10234
|
{ method: "param", class: "Request", type: "http_param", severity: "high", return_tainted: true },
|
|
10235
10235
|
{ method: "cookies", class: "Request", type: "http_cookie", severity: "high", return_tainted: true },
|
|
10236
|
-
{ method: "Json", type: "http_body", severity: "high", return_tainted: true },
|
|
10237
|
-
{ method: "Query", type: "http_param", severity: "high", return_tainted: true },
|
|
10238
|
-
{ method: "Path", type: "http_path", severity: "high", return_tainted: true },
|
|
10239
|
-
{ method: "Form", type: "http_param", severity: "high", return_tainted: true },
|
|
10236
|
+
{ method: "Json", type: "http_body", severity: "high", return_tainted: true, languages: ["rust"] },
|
|
10237
|
+
{ method: "Query", type: "http_param", severity: "high", return_tainted: true, languages: ["rust"] },
|
|
10238
|
+
{ method: "Path", type: "http_path", severity: "high", return_tainted: true, languages: ["rust"] },
|
|
10239
|
+
{ method: "Form", type: "http_param", severity: "high", return_tainted: true, languages: ["rust"] },
|
|
10240
10240
|
{ method: "var", class: "env", type: "env_input", severity: "medium", return_tainted: true },
|
|
10241
10241
|
{ method: "var_os", class: "env", type: "env_input", severity: "medium", return_tainted: true },
|
|
10242
10242
|
{ method: "args", class: "env", type: "env_input", severity: "medium", return_tainted: true },
|
|
@@ -10389,10 +10389,10 @@ var DEFAULT_SINKS = [
|
|
|
10389
10389
|
{ method: "UrlResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10390
10390
|
{ method: "PathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10391
10391
|
{ method: "forFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10392
|
-
{ method: "resolve", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10393
|
-
{ method: "resolve", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10394
|
-
{ method: "resolveSibling", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10395
|
-
{ method: "relativize", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
10392
|
+
{ method: "resolve", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10393
|
+
{ method: "resolve", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10394
|
+
{ method: "resolveSibling", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10395
|
+
{ method: "relativize", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0], languages: ["java"] },
|
|
10396
10396
|
{ method: "staticFiles", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10397
10397
|
{ method: "setRoot", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10398
10398
|
{ method: "setWebRoot", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -11208,6 +11208,11 @@ var DEFAULT_SANITIZERS = [
|
|
|
11208
11208
|
{ method: "toRealPath", class: "Path", removes: ["path_traversal"] },
|
|
11209
11209
|
{ method: "file_name", removes: ["path_traversal"] },
|
|
11210
11210
|
{ method: "canonicalize", removes: ["path_traversal"] },
|
|
11211
|
+
{ method: "Base", class: "filepath", removes: ["path_traversal"] },
|
|
11212
|
+
{ method: "Base", class: "path", removes: ["path_traversal"] },
|
|
11213
|
+
{ method: "Clean", class: "filepath", removes: ["path_traversal"] },
|
|
11214
|
+
{ method: "Clean", class: "path", removes: ["path_traversal"] },
|
|
11215
|
+
{ method: "EvalSymlinks", class: "filepath", removes: ["path_traversal"] },
|
|
11211
11216
|
{ method: "replace", removes: ["log_injection"] },
|
|
11212
11217
|
{ method: "encodeForLDAP", removes: ["ldap_injection"] },
|
|
11213
11218
|
{ method: "encodeForDN", removes: ["ldap_injection"] },
|
|
@@ -11268,6 +11273,7 @@ var DEFAULT_SANITIZERS = [
|
|
|
11268
11273
|
{ method: "abspath", class: "os.path", removes: ["path_traversal"] },
|
|
11269
11274
|
{ method: "realpath", class: "path", removes: ["path_traversal"] },
|
|
11270
11275
|
{ method: "abspath", class: "path", removes: ["path_traversal"] },
|
|
11276
|
+
{ method: "resolve", class: "Path", removes: ["path_traversal"] },
|
|
11271
11277
|
{ method: "int", removes: ["sql_injection", "command_injection", "xss"] },
|
|
11272
11278
|
{ method: "float", removes: ["sql_injection", "command_injection"] },
|
|
11273
11279
|
{ method: "query!", removes: ["sql_injection"] },
|
|
@@ -11287,7 +11293,25 @@ var DEFAULT_SANITIZERS = [
|
|
|
11287
11293
|
{ method: "encode_text", class: "html_escape", removes: ["xss"] },
|
|
11288
11294
|
{ method: "encode_attribute", class: "html_escape", removes: ["xss"] },
|
|
11289
11295
|
{ method: "escape_html", removes: ["xss"] },
|
|
11290
|
-
{ method: "parse", removes: ["sql_injection", "command_injection", "xss"] }
|
|
11296
|
+
{ method: "parse", removes: ["sql_injection", "command_injection", "xss"] },
|
|
11297
|
+
{ method: "parseInt", class: "Integer", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11298
|
+
{ method: "parseLong", class: "Long", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11299
|
+
{ method: "parseFloat", class: "Float", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11300
|
+
{ method: "parseDouble", class: "Double", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11301
|
+
{ method: "parseShort", class: "Short", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11302
|
+
{ method: "parseByte", class: "Byte", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11303
|
+
{ method: "fromString", class: "UUID", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11304
|
+
{ method: "BigInt", removes: ["sql_injection", "nosql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11305
|
+
{ method: "Atoi", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11306
|
+
{ method: "ParseInt", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11307
|
+
{ method: "ParseFloat", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11308
|
+
{ method: "ParseUint", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11309
|
+
{ method: "ParseBool", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11310
|
+
{ method: "Parse", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11311
|
+
{ method: "MustParse", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11312
|
+
{ method: "bool", removes: ["sql_injection", "command_injection", "xss", "code_injection"] },
|
|
11313
|
+
{ method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11314
|
+
{ method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
|
|
11291
11315
|
];
|
|
11292
11316
|
function getDefaultConfig() {
|
|
11293
11317
|
return {
|
|
@@ -11398,7 +11422,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
11398
11422
|
`) : undefined;
|
|
11399
11423
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
11400
11424
|
const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
|
|
11401
|
-
const sanitizers = findSanitizers(calls, types, config.sanitizers);
|
|
11425
|
+
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
11402
11426
|
return { sources, sinks, sanitizers };
|
|
11403
11427
|
}
|
|
11404
11428
|
function attachSourceLineCode(sources, sinks, code) {
|
|
@@ -11419,6 +11443,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11419
11443
|
const sources = [];
|
|
11420
11444
|
for (const call of calls) {
|
|
11421
11445
|
for (const pattern of patterns) {
|
|
11446
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
|
|
11447
|
+
continue;
|
|
11448
|
+
}
|
|
11422
11449
|
if (matchesSourcePattern(call, pattern)) {
|
|
11423
11450
|
sources.push({
|
|
11424
11451
|
type: pattern.type,
|
|
@@ -12048,6 +12075,15 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12048
12075
|
if (receiver === className) {
|
|
12049
12076
|
return true;
|
|
12050
12077
|
}
|
|
12078
|
+
if (receiver.endsWith(")")) {
|
|
12079
|
+
const ctorMatch = receiver.match(/^(\w+)\(/);
|
|
12080
|
+
if (ctorMatch) {
|
|
12081
|
+
const ctorName = ctorMatch[1];
|
|
12082
|
+
if (ctorName === className || ctorName.toLowerCase() === className.toLowerCase()) {
|
|
12083
|
+
return true;
|
|
12084
|
+
}
|
|
12085
|
+
}
|
|
12086
|
+
}
|
|
12051
12087
|
if (receiver.includes("::")) {
|
|
12052
12088
|
const scopePrefix = receiver.match(/^(\w+)::/);
|
|
12053
12089
|
if (scopePrefix) {
|
|
@@ -12290,7 +12326,7 @@ function calculateSinkConfidence(call, pattern) {
|
|
|
12290
12326
|
}
|
|
12291
12327
|
return Math.min(confidence, 1);
|
|
12292
12328
|
}
|
|
12293
|
-
function findSanitizers(calls, types, patterns) {
|
|
12329
|
+
function findSanitizers(calls, types, patterns, sourceLines) {
|
|
12294
12330
|
const sanitizers = [];
|
|
12295
12331
|
const sanitizerMethods = new Set;
|
|
12296
12332
|
for (const type of types) {
|
|
@@ -12300,6 +12336,77 @@ function findSanitizers(calls, types, patterns) {
|
|
|
12300
12336
|
}
|
|
12301
12337
|
}
|
|
12302
12338
|
}
|
|
12339
|
+
const wrapperSanitizers = new Map;
|
|
12340
|
+
for (const type of types) {
|
|
12341
|
+
for (const method of type.methods) {
|
|
12342
|
+
const bodySize = method.end_line - method.start_line;
|
|
12343
|
+
if (bodySize < 0 || bodySize > 2)
|
|
12344
|
+
continue;
|
|
12345
|
+
const paramNames = new Set(method.parameters.map((p) => p.name));
|
|
12346
|
+
if (paramNames.size === 0)
|
|
12347
|
+
continue;
|
|
12348
|
+
const inside = [];
|
|
12349
|
+
for (const c of calls) {
|
|
12350
|
+
if (c.location.line < method.start_line || c.location.line > method.end_line)
|
|
12351
|
+
continue;
|
|
12352
|
+
if (c.method_name === method.name)
|
|
12353
|
+
continue;
|
|
12354
|
+
inside.push(c);
|
|
12355
|
+
}
|
|
12356
|
+
if (inside.length !== 1)
|
|
12357
|
+
continue;
|
|
12358
|
+
const innerCall = inside[0];
|
|
12359
|
+
let matched;
|
|
12360
|
+
for (const pattern of patterns) {
|
|
12361
|
+
if (matchesSanitizerPattern(innerCall, pattern)) {
|
|
12362
|
+
matched = pattern;
|
|
12363
|
+
break;
|
|
12364
|
+
}
|
|
12365
|
+
}
|
|
12366
|
+
if (!matched || !matched.removes || matched.removes.length === 0)
|
|
12367
|
+
continue;
|
|
12368
|
+
let argOk = false;
|
|
12369
|
+
for (const arg of innerCall.arguments) {
|
|
12370
|
+
if (arg.variable && paramNames.has(arg.variable)) {
|
|
12371
|
+
argOk = true;
|
|
12372
|
+
break;
|
|
12373
|
+
}
|
|
12374
|
+
}
|
|
12375
|
+
if (!argOk)
|
|
12376
|
+
continue;
|
|
12377
|
+
if (sourceLines) {
|
|
12378
|
+
const lineText = sourceLines[innerCall.location.line - 1] ?? "";
|
|
12379
|
+
const stripped = lineText.trim();
|
|
12380
|
+
const returnMatch = stripped.match(/^return\s+(?:await\s+)?(.*)$/);
|
|
12381
|
+
if (!returnMatch)
|
|
12382
|
+
continue;
|
|
12383
|
+
const after = returnMatch[1].replace(/;\s*$/, "").trimEnd();
|
|
12384
|
+
const callPrefix = innerCall.receiver ? `${innerCall.receiver}.${innerCall.method_name}(` : `${innerCall.method_name}(`;
|
|
12385
|
+
if (!after.startsWith(callPrefix))
|
|
12386
|
+
continue;
|
|
12387
|
+
if (!after.endsWith(")"))
|
|
12388
|
+
continue;
|
|
12389
|
+
}
|
|
12390
|
+
const existing = wrapperSanitizers.get(method.name);
|
|
12391
|
+
if (existing) {
|
|
12392
|
+
const set = new Set([...existing, ...matched.removes]);
|
|
12393
|
+
wrapperSanitizers.set(method.name, Array.from(set));
|
|
12394
|
+
} else {
|
|
12395
|
+
wrapperSanitizers.set(method.name, [...matched.removes]);
|
|
12396
|
+
}
|
|
12397
|
+
}
|
|
12398
|
+
}
|
|
12399
|
+
for (const call of calls) {
|
|
12400
|
+
const removes = wrapperSanitizers.get(call.method_name);
|
|
12401
|
+
if (!removes)
|
|
12402
|
+
continue;
|
|
12403
|
+
sanitizers.push({
|
|
12404
|
+
type: "derived_wrapper",
|
|
12405
|
+
method: formatSanitizerMethod(call),
|
|
12406
|
+
line: call.location.line,
|
|
12407
|
+
sanitizes: removes
|
|
12408
|
+
});
|
|
12409
|
+
}
|
|
12303
12410
|
for (const call of calls) {
|
|
12304
12411
|
if (sanitizerMethods.has(call.method_name)) {
|
|
12305
12412
|
sanitizers.push({
|
|
@@ -12957,7 +13064,24 @@ var SANITIZER_METHODS = new Set([
|
|
|
12957
13064
|
"validatePath",
|
|
12958
13065
|
"validateCityName",
|
|
12959
13066
|
"validateInput",
|
|
12960
|
-
"sanitizeInput"
|
|
13067
|
+
"sanitizeInput",
|
|
13068
|
+
"parseInt",
|
|
13069
|
+
"parseLong",
|
|
13070
|
+
"parseFloat",
|
|
13071
|
+
"parseDouble",
|
|
13072
|
+
"parseShort",
|
|
13073
|
+
"parseByte",
|
|
13074
|
+
"fromString",
|
|
13075
|
+
"Number",
|
|
13076
|
+
"BigInt",
|
|
13077
|
+
"Atoi",
|
|
13078
|
+
"ParseInt",
|
|
13079
|
+
"ParseFloat",
|
|
13080
|
+
"ParseUint",
|
|
13081
|
+
"ParseBool",
|
|
13082
|
+
"int",
|
|
13083
|
+
"float",
|
|
13084
|
+
"bool"
|
|
12961
13085
|
]);
|
|
12962
13086
|
var ANTI_SANITIZER_METHODS = new Set([
|
|
12963
13087
|
"decode",
|
|
@@ -13030,6 +13154,7 @@ class ConstantPropagator {
|
|
|
13030
13154
|
currentClassName = null;
|
|
13031
13155
|
inConstructor = false;
|
|
13032
13156
|
constructorParamPositions = new Map;
|
|
13157
|
+
safePatternFieldsCache = null;
|
|
13033
13158
|
analyze(tree, sourceCode, additionalTaintPatterns = [], sanitizerMethods = [], taintedParameters = []) {
|
|
13034
13159
|
this.source = sourceCode;
|
|
13035
13160
|
this.additionalTaintPatterns = additionalTaintPatterns;
|
|
@@ -13060,12 +13185,14 @@ class ConstantPropagator {
|
|
|
13060
13185
|
this.currentClassName = null;
|
|
13061
13186
|
this.inConstructor = false;
|
|
13062
13187
|
this.constructorParamPositions.clear();
|
|
13188
|
+
this.safePatternFieldsCache = null;
|
|
13063
13189
|
this.collectClassFields(tree.rootNode);
|
|
13064
13190
|
for (const methodName of sanitizerMethods) {
|
|
13065
13191
|
this.methodReturnsSanitized.add(methodName);
|
|
13066
13192
|
}
|
|
13067
13193
|
this.evaluator = new ExpressionEvaluator(this.source, (name2) => this.lookupSymbol(name2));
|
|
13068
13194
|
this.analyzeMethodReturns(tree.rootNode);
|
|
13195
|
+
this.seedPythonModuleConstants(tree.rootNode);
|
|
13069
13196
|
this.visit(tree.rootNode);
|
|
13070
13197
|
this.refineTaintFromConstants();
|
|
13071
13198
|
const resultTainted = new Set(this.tainted);
|
|
@@ -13410,6 +13537,141 @@ class ConstantPropagator {
|
|
|
13410
13537
|
}
|
|
13411
13538
|
}
|
|
13412
13539
|
}
|
|
13540
|
+
fieldDeclHasPrimitiveLiteralValue(node) {
|
|
13541
|
+
const primitive = new Set([
|
|
13542
|
+
"true",
|
|
13543
|
+
"false",
|
|
13544
|
+
"null_literal",
|
|
13545
|
+
"decimal_integer_literal",
|
|
13546
|
+
"hex_integer_literal",
|
|
13547
|
+
"octal_integer_literal",
|
|
13548
|
+
"binary_integer_literal",
|
|
13549
|
+
"decimal_floating_point_literal",
|
|
13550
|
+
"hex_floating_point_literal",
|
|
13551
|
+
"character_literal",
|
|
13552
|
+
"string_literal",
|
|
13553
|
+
"number",
|
|
13554
|
+
"string"
|
|
13555
|
+
]);
|
|
13556
|
+
for (const child of node.children) {
|
|
13557
|
+
if (child.type !== "variable_declarator")
|
|
13558
|
+
continue;
|
|
13559
|
+
const value = child.childForFieldName("value");
|
|
13560
|
+
if (!value)
|
|
13561
|
+
continue;
|
|
13562
|
+
if (!primitive.has(value.type))
|
|
13563
|
+
return false;
|
|
13564
|
+
}
|
|
13565
|
+
return true;
|
|
13566
|
+
}
|
|
13567
|
+
getSafePatternFields() {
|
|
13568
|
+
if (this.safePatternFieldsCache !== null)
|
|
13569
|
+
return this.safePatternFieldsCache;
|
|
13570
|
+
const set = new Set;
|
|
13571
|
+
const re = /\b(?:public\s+|private\s+|protected\s+)?(?:static\s+final|final\s+static)\s+(?:java\.util\.regex\.)?Pattern\s+(\w+)\s*=\s*(?:java\.util\.regex\.)?Pattern\s*\.\s*compile\s*\(\s*"((?:[^"\\]|\\.)*)"/g;
|
|
13572
|
+
let m;
|
|
13573
|
+
while ((m = re.exec(this.source)) !== null) {
|
|
13574
|
+
const name2 = m[1];
|
|
13575
|
+
const regex = m[2];
|
|
13576
|
+
if (this.isStrictAnchoredRegex(regex))
|
|
13577
|
+
set.add(name2);
|
|
13578
|
+
}
|
|
13579
|
+
this.safePatternFieldsCache = set;
|
|
13580
|
+
return set;
|
|
13581
|
+
}
|
|
13582
|
+
isStrictAnchoredRegex(re) {
|
|
13583
|
+
if (!re.startsWith("^") || !re.endsWith("$"))
|
|
13584
|
+
return false;
|
|
13585
|
+
const stripped = re.replace(/\[(?:[^\]\\]|\\.)*\]/g, "");
|
|
13586
|
+
const cleaned = stripped.replace(/\\./g, "");
|
|
13587
|
+
if (cleaned.includes("."))
|
|
13588
|
+
return false;
|
|
13589
|
+
if (cleaned.includes("|"))
|
|
13590
|
+
return false;
|
|
13591
|
+
return true;
|
|
13592
|
+
}
|
|
13593
|
+
detectRegexAllowlistGuard(condition, consequence) {
|
|
13594
|
+
if (!consequence)
|
|
13595
|
+
return null;
|
|
13596
|
+
let condText = getNodeText2(condition, this.source).replace(/\s+/g, "");
|
|
13597
|
+
while (condText.startsWith("(") && condText.endsWith(")")) {
|
|
13598
|
+
const inner = condText.slice(1, -1);
|
|
13599
|
+
let depth = 0;
|
|
13600
|
+
let balanced = true;
|
|
13601
|
+
for (let i2 = 0;i2 < inner.length; i2++) {
|
|
13602
|
+
if (inner[i2] === "(")
|
|
13603
|
+
depth++;
|
|
13604
|
+
else if (inner[i2] === ")")
|
|
13605
|
+
depth--;
|
|
13606
|
+
if (depth < 0) {
|
|
13607
|
+
balanced = false;
|
|
13608
|
+
break;
|
|
13609
|
+
}
|
|
13610
|
+
}
|
|
13611
|
+
if (!balanced || depth !== 0)
|
|
13612
|
+
break;
|
|
13613
|
+
condText = inner;
|
|
13614
|
+
}
|
|
13615
|
+
const m = condText.match(/^!(\w+)\.matcher\((\w+)\)\.matches\(\)$/);
|
|
13616
|
+
if (!m)
|
|
13617
|
+
return null;
|
|
13618
|
+
const patternName = m[1];
|
|
13619
|
+
const varName = m[2];
|
|
13620
|
+
if (!this.getSafePatternFields().has(patternName))
|
|
13621
|
+
return null;
|
|
13622
|
+
if (!this.consequenceContainsThrow(consequence))
|
|
13623
|
+
return null;
|
|
13624
|
+
return varName;
|
|
13625
|
+
}
|
|
13626
|
+
consequenceContainsThrow(node) {
|
|
13627
|
+
if (node.type === "throw_statement")
|
|
13628
|
+
return true;
|
|
13629
|
+
const stack = [node];
|
|
13630
|
+
while (stack.length > 0) {
|
|
13631
|
+
const n = stack.pop();
|
|
13632
|
+
if (!n)
|
|
13633
|
+
continue;
|
|
13634
|
+
if (n.type === "throw_statement")
|
|
13635
|
+
return true;
|
|
13636
|
+
if (n.type === "if_statement" || n.type === "switch_statement")
|
|
13637
|
+
continue;
|
|
13638
|
+
for (const c of n.children)
|
|
13639
|
+
stack.push(c);
|
|
13640
|
+
}
|
|
13641
|
+
return false;
|
|
13642
|
+
}
|
|
13643
|
+
seedPythonModuleConstants(root) {
|
|
13644
|
+
if (root.type !== "module")
|
|
13645
|
+
return;
|
|
13646
|
+
for (const child of root.children) {
|
|
13647
|
+
const target = child.type === "assignment" ? child : child.type === "expression_statement" && child.children.length > 0 ? child.children[0] : null;
|
|
13648
|
+
if (!target || target.type !== "assignment")
|
|
13649
|
+
continue;
|
|
13650
|
+
const left = target.childForFieldName("left");
|
|
13651
|
+
const right = target.childForFieldName("right");
|
|
13652
|
+
if (!left || !right)
|
|
13653
|
+
continue;
|
|
13654
|
+
if (left.type !== "identifier")
|
|
13655
|
+
continue;
|
|
13656
|
+
const allowed = new Set([
|
|
13657
|
+
"true",
|
|
13658
|
+
"false",
|
|
13659
|
+
"none",
|
|
13660
|
+
"integer",
|
|
13661
|
+
"float",
|
|
13662
|
+
"string"
|
|
13663
|
+
]);
|
|
13664
|
+
if (!allowed.has(right.type))
|
|
13665
|
+
continue;
|
|
13666
|
+
const name2 = getNodeText2(left, this.source);
|
|
13667
|
+
if (!name2)
|
|
13668
|
+
continue;
|
|
13669
|
+
const value = this.evaluateExpression(right);
|
|
13670
|
+
if (!isKnown(value))
|
|
13671
|
+
continue;
|
|
13672
|
+
this.symbols.set(name2, value);
|
|
13673
|
+
}
|
|
13674
|
+
}
|
|
13413
13675
|
findAllMethods(node) {
|
|
13414
13676
|
const methods = [];
|
|
13415
13677
|
const stack = [node];
|
|
@@ -13495,6 +13757,11 @@ class ConstantPropagator {
|
|
|
13495
13757
|
case "local_variable_declaration":
|
|
13496
13758
|
this.handleVariableDeclaration(node);
|
|
13497
13759
|
return false;
|
|
13760
|
+
case "field_declaration":
|
|
13761
|
+
if (this.fieldDeclHasPrimitiveLiteralValue(node)) {
|
|
13762
|
+
this.handleVariableDeclaration(node);
|
|
13763
|
+
}
|
|
13764
|
+
return false;
|
|
13498
13765
|
case "assignment_expression":
|
|
13499
13766
|
this.handleAssignment(node);
|
|
13500
13767
|
return false;
|
|
@@ -13964,6 +14231,16 @@ class ConstantPropagator {
|
|
|
13964
14231
|
}
|
|
13965
14232
|
this.inConditionalBranch = wasInConditional;
|
|
13966
14233
|
this.tainted = new Set([...taintedBefore, ...taintedAfterThen, ...taintedAfterElse]);
|
|
14234
|
+
const guardedVar = this.detectRegexAllowlistGuard(condition, consequence);
|
|
14235
|
+
if (guardedVar) {
|
|
14236
|
+
this.tainted.delete(guardedVar);
|
|
14237
|
+
this.sanitizedVars.add(guardedVar);
|
|
14238
|
+
const scoped = this.getScopedName(guardedVar);
|
|
14239
|
+
if (scoped !== guardedVar) {
|
|
14240
|
+
this.tainted.delete(scoped);
|
|
14241
|
+
this.sanitizedVars.add(scoped);
|
|
14242
|
+
}
|
|
14243
|
+
}
|
|
13967
14244
|
}
|
|
13968
14245
|
}
|
|
13969
14246
|
normalizeCondition(cond) {
|
|
@@ -14135,15 +14412,26 @@ class ConstantPropagator {
|
|
|
14135
14412
|
return null;
|
|
14136
14413
|
}
|
|
14137
14414
|
isSanitizerMethodCall(node) {
|
|
14138
|
-
|
|
14415
|
+
const methodName = this.extractCallName(node);
|
|
14416
|
+
if (!methodName)
|
|
14139
14417
|
return false;
|
|
14418
|
+
return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
|
|
14419
|
+
}
|
|
14420
|
+
extractCallName(node) {
|
|
14421
|
+
let fnNode = null;
|
|
14422
|
+
if (node.type === "method_invocation") {
|
|
14423
|
+
fnNode = node.childForFieldName("name");
|
|
14424
|
+
} else if (node.type === "call_expression" || node.type === "call") {
|
|
14425
|
+
fnNode = node.childForFieldName("function");
|
|
14140
14426
|
}
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14427
|
+
if (!fnNode)
|
|
14428
|
+
return null;
|
|
14429
|
+
if (fnNode.type === "selector_expression" || fnNode.type === "member_expression" || fnNode.type === "attribute") {
|
|
14430
|
+
const tail = fnNode.childForFieldName("field") || fnNode.childForFieldName("property") || fnNode.childForFieldName("attribute");
|
|
14431
|
+
if (tail)
|
|
14432
|
+
return getNodeText2(tail, this.source);
|
|
14144
14433
|
}
|
|
14145
|
-
|
|
14146
|
-
return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
|
|
14434
|
+
return getNodeText2(fnNode, this.source);
|
|
14147
14435
|
}
|
|
14148
14436
|
isAntiSanitizerCall(node) {
|
|
14149
14437
|
if (node.type !== "method_invocation") {
|
|
@@ -20350,6 +20638,7 @@ class LanguageSourcesPass {
|
|
|
20350
20638
|
const additionalSources = [];
|
|
20351
20639
|
const additionalSinks = [];
|
|
20352
20640
|
additionalSources.push(...findGetterSources(types, constProp.instanceFieldTaint, code));
|
|
20641
|
+
additionalSources.push(...findOopFieldReadSources(types, code, language));
|
|
20353
20642
|
additionalSources.push(...findJavaScriptAssignmentSources(code, language));
|
|
20354
20643
|
const jsDOMSinks = findJavaScriptDOMSinks(code, language);
|
|
20355
20644
|
for (const s of jsDOMSinks) {
|
|
@@ -20455,6 +20744,128 @@ function findGetterSources(types, instanceFieldTaint, _sourceCode) {
|
|
|
20455
20744
|
}
|
|
20456
20745
|
return sources;
|
|
20457
20746
|
}
|
|
20747
|
+
function findOopFieldReadSources(types, sourceCode, language) {
|
|
20748
|
+
if (language !== "java" && language !== "python")
|
|
20749
|
+
return [];
|
|
20750
|
+
const sources = [];
|
|
20751
|
+
const lines = sourceCode.split(`
|
|
20752
|
+
`);
|
|
20753
|
+
const isPython = language === "python";
|
|
20754
|
+
const SELF = isPython ? "self" : "this";
|
|
20755
|
+
const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
|
|
20756
|
+
const fieldAssignRe = new RegExp(`^\\s*${SELF}\\.([A-Za-z_]\\w*)\\s*=\\s*(.+?)(?:;\\s*)?$`);
|
|
20757
|
+
const commentPrefix = isPython ? "#" : "//";
|
|
20758
|
+
for (const type of types) {
|
|
20759
|
+
if (type.kind !== "class")
|
|
20760
|
+
continue;
|
|
20761
|
+
if (type.name === "<module>")
|
|
20762
|
+
continue;
|
|
20763
|
+
let ctor;
|
|
20764
|
+
for (const m of type.methods) {
|
|
20765
|
+
if (isPython) {
|
|
20766
|
+
if (m.name === "__init__") {
|
|
20767
|
+
ctor = m;
|
|
20768
|
+
break;
|
|
20769
|
+
}
|
|
20770
|
+
} else {
|
|
20771
|
+
if (m.name === type.name) {
|
|
20772
|
+
ctor = m;
|
|
20773
|
+
break;
|
|
20774
|
+
}
|
|
20775
|
+
}
|
|
20776
|
+
}
|
|
20777
|
+
if (!ctor)
|
|
20778
|
+
continue;
|
|
20779
|
+
const paramNames = new Set;
|
|
20780
|
+
for (const p of ctor.parameters) {
|
|
20781
|
+
if (p.name === "self" || p.name === "this")
|
|
20782
|
+
continue;
|
|
20783
|
+
paramNames.add(p.name);
|
|
20784
|
+
}
|
|
20785
|
+
const fieldTaint = new Map;
|
|
20786
|
+
const ctorStart = ctor.start_line;
|
|
20787
|
+
const ctorEnd = ctor.end_line;
|
|
20788
|
+
for (let i2 = ctorStart - 1;i2 < Math.min(ctorEnd, lines.length); i2++) {
|
|
20789
|
+
const line = lines[i2] ?? "";
|
|
20790
|
+
if (line.trim().startsWith(commentPrefix))
|
|
20791
|
+
continue;
|
|
20792
|
+
const m = line.match(fieldAssignRe);
|
|
20793
|
+
if (!m)
|
|
20794
|
+
continue;
|
|
20795
|
+
const fieldName = m[1];
|
|
20796
|
+
const rhs = m[2].trim().replace(/;\s*$/, "");
|
|
20797
|
+
let sourceType = null;
|
|
20798
|
+
if (paramNames.has(rhs)) {
|
|
20799
|
+
sourceType = "interprocedural_param";
|
|
20800
|
+
} else if (!isPython && javaHttpPattern.test(rhs)) {
|
|
20801
|
+
sourceType = "http_param";
|
|
20802
|
+
} else if (isPython) {
|
|
20803
|
+
for (const { pattern, type: type2 } of PYTHON_TAINTED_PATTERNS2) {
|
|
20804
|
+
if (pattern.test(rhs)) {
|
|
20805
|
+
sourceType = type2;
|
|
20806
|
+
break;
|
|
20807
|
+
}
|
|
20808
|
+
}
|
|
20809
|
+
}
|
|
20810
|
+
if (sourceType) {
|
|
20811
|
+
fieldTaint.set(fieldName, { line: i2 + 1, type: sourceType });
|
|
20812
|
+
}
|
|
20813
|
+
}
|
|
20814
|
+
if (fieldTaint.size === 0)
|
|
20815
|
+
continue;
|
|
20816
|
+
for (const [fieldName, info2] of fieldTaint) {
|
|
20817
|
+
sources.push({
|
|
20818
|
+
type: info2.type,
|
|
20819
|
+
location: `${type.name}.${SELF}.${fieldName} (constructor-injected field, #78)`,
|
|
20820
|
+
severity: "high",
|
|
20821
|
+
line: info2.line,
|
|
20822
|
+
confidence: 0.85,
|
|
20823
|
+
variable: `${SELF}.${fieldName}`
|
|
20824
|
+
});
|
|
20825
|
+
}
|
|
20826
|
+
for (const m of type.methods) {
|
|
20827
|
+
if (m === ctor)
|
|
20828
|
+
continue;
|
|
20829
|
+
const nonSelfParams = m.parameters.filter((p) => p.name !== "self" && p.name !== "this");
|
|
20830
|
+
if (nonSelfParams.length !== 0)
|
|
20831
|
+
continue;
|
|
20832
|
+
const mStart = m.start_line;
|
|
20833
|
+
const mEnd = m.end_line;
|
|
20834
|
+
let returnedField = null;
|
|
20835
|
+
let returnStatementCount = 0;
|
|
20836
|
+
const returnRe = new RegExp(`\\breturn\\s+${SELF}\\.([A-Za-z_]\\w*)\\s*[;}]?`);
|
|
20837
|
+
for (let i2 = mStart - 1;i2 < Math.min(mEnd, lines.length); i2++) {
|
|
20838
|
+
const raw = lines[i2] ?? "";
|
|
20839
|
+
const trimmed = raw.trim();
|
|
20840
|
+
if (!trimmed)
|
|
20841
|
+
continue;
|
|
20842
|
+
if (trimmed.startsWith(commentPrefix))
|
|
20843
|
+
continue;
|
|
20844
|
+
const rm = trimmed.match(returnRe);
|
|
20845
|
+
if (rm) {
|
|
20846
|
+
returnedField = rm[1];
|
|
20847
|
+
returnStatementCount++;
|
|
20848
|
+
} else if (/\breturn\b/.test(trimmed)) {
|
|
20849
|
+
returnStatementCount = 99;
|
|
20850
|
+
break;
|
|
20851
|
+
}
|
|
20852
|
+
}
|
|
20853
|
+
if (returnStatementCount === 1 && returnedField && fieldTaint.has(returnedField)) {
|
|
20854
|
+
const fieldInfo = fieldTaint.get(returnedField);
|
|
20855
|
+
const getterVar = isPython ? `${SELF}.${m.name}` : m.name;
|
|
20856
|
+
sources.push({
|
|
20857
|
+
type: fieldInfo.type,
|
|
20858
|
+
location: `${type.name}.${m.name} returns tainted field '${returnedField}' (#78)`,
|
|
20859
|
+
severity: "high",
|
|
20860
|
+
line: m.start_line,
|
|
20861
|
+
confidence: 0.85,
|
|
20862
|
+
variable: getterVar
|
|
20863
|
+
});
|
|
20864
|
+
}
|
|
20865
|
+
}
|
|
20866
|
+
}
|
|
20867
|
+
return sources;
|
|
20868
|
+
}
|
|
20458
20869
|
function findJavaScriptAssignmentSources(sourceCode, language) {
|
|
20459
20870
|
if (!["javascript", "typescript"].includes(language))
|
|
20460
20871
|
return [];
|
|
@@ -20821,26 +21232,44 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
20821
21232
|
const lines = sourceCode.split(`
|
|
20822
21233
|
`);
|
|
20823
21234
|
const definedVars = new Set(dfg.defs.filter((d) => d.kind === "local").map((d) => d.variable));
|
|
21235
|
+
const fnHeaderRe = /^\s*(?:function\s+)?[A-Za-z_][\w-]*\s*\(\s*\)\s*\{?\s*$|^\s*function\s+[A-Za-z_][\w-]*\s*\{?\s*$/;
|
|
21236
|
+
let braceDepth = 0;
|
|
20824
21237
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
20825
21238
|
const line = lines[i2];
|
|
20826
21239
|
const trimmed = line.trim();
|
|
20827
21240
|
const lineNumber = i2 + 1;
|
|
20828
21241
|
if (trimmed.startsWith("#"))
|
|
20829
21242
|
continue;
|
|
20830
|
-
const
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
sources.
|
|
20837
|
-
|
|
20838
|
-
|
|
20839
|
-
|
|
20840
|
-
|
|
20841
|
-
|
|
20842
|
-
|
|
20843
|
-
|
|
21243
|
+
const insideFunction = braceDepth > 0;
|
|
21244
|
+
if (!insideFunction) {
|
|
21245
|
+
const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
|
|
21246
|
+
let m;
|
|
21247
|
+
while ((m = positionalRe.exec(line)) !== null) {
|
|
21248
|
+
const param = m[1] ?? m[2];
|
|
21249
|
+
const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
|
|
21250
|
+
if (!alreadyExists) {
|
|
21251
|
+
sources.push({
|
|
21252
|
+
type: "io_input",
|
|
21253
|
+
location: `positional parameter $${param}`,
|
|
21254
|
+
severity: "high",
|
|
21255
|
+
line: lineNumber,
|
|
21256
|
+
confidence: 1,
|
|
21257
|
+
variable: param
|
|
21258
|
+
});
|
|
21259
|
+
}
|
|
21260
|
+
}
|
|
21261
|
+
}
|
|
21262
|
+
if (fnHeaderRe.test(line) || /^\s*[A-Za-z_][\w-]*\s*\(\s*\)\s*\{/.test(line)) {
|
|
21263
|
+
const openBracesOnLine = (line.match(/\{/g) ?? []).length;
|
|
21264
|
+
const closeBracesOnLine = (line.match(/\}/g) ?? []).length;
|
|
21265
|
+
braceDepth += openBracesOnLine - closeBracesOnLine;
|
|
21266
|
+
} else {
|
|
21267
|
+
if (braceDepth > 0) {
|
|
21268
|
+
const openBracesOnLine = (line.match(/\{/g) ?? []).length;
|
|
21269
|
+
const closeBracesOnLine = (line.match(/\}/g) ?? []).length;
|
|
21270
|
+
braceDepth += openBracesOnLine - closeBracesOnLine;
|
|
21271
|
+
if (braceDepth < 0)
|
|
21272
|
+
braceDepth = 0;
|
|
20844
21273
|
}
|
|
20845
21274
|
}
|
|
20846
21275
|
const cmdSubAssign = trimmed.match(/^(\w+)=\$\((\w+)\s/);
|
|
@@ -21287,10 +21716,15 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
|
|
|
21287
21716
|
const callsAtSink = callsByLine.get(sink.line) ?? [];
|
|
21288
21717
|
const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
|
|
21289
21718
|
const relevantCalls = sink.method ? callsAtSink.filter((c) => c.method_name === sink.method) : callsAtSink;
|
|
21719
|
+
const trustArgPositions = language !== "bash" && language !== "shell";
|
|
21290
21720
|
for (const call of relevantCalls) {
|
|
21291
21721
|
let allArgsAreClean = true;
|
|
21722
|
+
let dangerousArgCount = 0;
|
|
21292
21723
|
const methodName = call.in_method;
|
|
21293
21724
|
for (const arg of call.arguments) {
|
|
21725
|
+
if (trustArgPositions && sink.argPositions && sink.argPositions.length > 0 && !sink.argPositions.includes(arg.position))
|
|
21726
|
+
continue;
|
|
21727
|
+
dangerousArgCount++;
|
|
21294
21728
|
if (language === "bash" && arg.expression === call.method_name && !arg.variable && arg.literal == null)
|
|
21295
21729
|
continue;
|
|
21296
21730
|
if (arg.variable && !arg.expression?.includes("[")) {
|
|
@@ -21318,7 +21752,7 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
|
|
|
21318
21752
|
allArgsAreClean = false;
|
|
21319
21753
|
}
|
|
21320
21754
|
}
|
|
21321
|
-
if (allArgsAreClean &&
|
|
21755
|
+
if (allArgsAreClean && dangerousArgCount > 0)
|
|
21322
21756
|
return false;
|
|
21323
21757
|
}
|
|
21324
21758
|
return true;
|
|
@@ -21369,6 +21803,17 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
|
21369
21803
|
}
|
|
21370
21804
|
|
|
21371
21805
|
// ../circle-ir/dist/analysis/taint-propagation.js
|
|
21806
|
+
function buildSanitizersByLine(sanitizers) {
|
|
21807
|
+
const out2 = new Map;
|
|
21808
|
+
for (const san of sanitizers) {
|
|
21809
|
+
const existing = out2.get(san.line);
|
|
21810
|
+
if (existing)
|
|
21811
|
+
existing.push(san);
|
|
21812
|
+
else
|
|
21813
|
+
out2.set(san.line, [san]);
|
|
21814
|
+
}
|
|
21815
|
+
return out2;
|
|
21816
|
+
}
|
|
21372
21817
|
function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
|
|
21373
21818
|
let graph;
|
|
21374
21819
|
let sources;
|
|
@@ -21404,7 +21849,7 @@ function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSani
|
|
|
21404
21849
|
const defsByLine = graph.defsByLine;
|
|
21405
21850
|
const usesByLine = graph.usesByLine;
|
|
21406
21851
|
const callsByLine = graph.callsByLine;
|
|
21407
|
-
const sanitizersByLine = graph.sanitizersByLine;
|
|
21852
|
+
const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
|
|
21408
21853
|
const defById = graph.defById;
|
|
21409
21854
|
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
|
|
21410
21855
|
const initialTaint = rawInitialTaint.filter((tv) => {
|
|
@@ -21672,7 +22117,7 @@ class TaintPropagationPass {
|
|
|
21672
22117
|
flows.push(f);
|
|
21673
22118
|
}
|
|
21674
22119
|
}
|
|
21675
|
-
const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines) ?? [];
|
|
22120
|
+
const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code) ?? [];
|
|
21676
22121
|
for (const f of collectionFlows) {
|
|
21677
22122
|
if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
|
|
21678
22123
|
continue;
|
|
@@ -21694,13 +22139,13 @@ class TaintPropagationPass {
|
|
|
21694
22139
|
continue;
|
|
21695
22140
|
flows.push(f);
|
|
21696
22141
|
}
|
|
21697
|
-
const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines) ?? [];
|
|
22142
|
+
const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
|
|
21698
22143
|
for (const f of paramFlows) {
|
|
21699
22144
|
if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
|
|
21700
22145
|
flows.push(f);
|
|
21701
22146
|
}
|
|
21702
22147
|
}
|
|
21703
|
-
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, ctx.code, ctx.language) ?? [];
|
|
22148
|
+
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
|
|
21704
22149
|
for (const f of exprScanFlows) {
|
|
21705
22150
|
if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
|
|
21706
22151
|
continue;
|
|
@@ -21722,10 +22167,25 @@ class TaintPropagationPass {
|
|
|
21722
22167
|
continue;
|
|
21723
22168
|
flows.push(f);
|
|
21724
22169
|
}
|
|
21725
|
-
|
|
22170
|
+
const sanitizedNames = constProp.sanitizedVars;
|
|
22171
|
+
const finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
|
|
22172
|
+
if (f.path.length === 0)
|
|
22173
|
+
return true;
|
|
22174
|
+
const sourceVar = f.path[0].variable;
|
|
22175
|
+
if (!sourceVar)
|
|
22176
|
+
return true;
|
|
22177
|
+
if (sanitizedNames.has(sourceVar))
|
|
22178
|
+
return false;
|
|
22179
|
+
for (const s of sanitizedNames) {
|
|
22180
|
+
if (s.endsWith(`:${sourceVar}`))
|
|
22181
|
+
return false;
|
|
22182
|
+
}
|
|
22183
|
+
return true;
|
|
22184
|
+
});
|
|
22185
|
+
return { flows: finalFlows };
|
|
21726
22186
|
}
|
|
21727
22187
|
}
|
|
21728
|
-
function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines) {
|
|
22188
|
+
function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code) {
|
|
21729
22189
|
const flows = [];
|
|
21730
22190
|
const callsByLine = new Map;
|
|
21731
22191
|
for (const call of calls) {
|
|
@@ -21748,6 +22208,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
21748
22208
|
if (taintedVars.has(varName) || taintedVars.has(scopedName)) {
|
|
21749
22209
|
const source = sources[0];
|
|
21750
22210
|
if (source) {
|
|
22211
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line)) {
|
|
22212
|
+
continue;
|
|
22213
|
+
}
|
|
21751
22214
|
flows.push({
|
|
21752
22215
|
source_line: source.line,
|
|
21753
22216
|
sink_line: sink.line,
|
|
@@ -21782,6 +22245,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
21782
22245
|
if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
|
|
21783
22246
|
const source = sources[0];
|
|
21784
22247
|
if (source) {
|
|
22248
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line)) {
|
|
22249
|
+
continue;
|
|
22250
|
+
}
|
|
21785
22251
|
flows.push({
|
|
21786
22252
|
source_line: source.line,
|
|
21787
22253
|
sink_line: sink.line,
|
|
@@ -21852,7 +22318,7 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
|
|
|
21852
22318
|
}
|
|
21853
22319
|
return flows;
|
|
21854
22320
|
}
|
|
21855
|
-
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines) {
|
|
22321
|
+
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
|
|
21856
22322
|
const flows = [];
|
|
21857
22323
|
const paramSourcesByMethod = new Map;
|
|
21858
22324
|
for (const source of sources) {
|
|
@@ -21898,6 +22364,9 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
21898
22364
|
if (paramSource) {
|
|
21899
22365
|
const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line);
|
|
21900
22366
|
if (!exists) {
|
|
22367
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
|
|
22368
|
+
continue;
|
|
22369
|
+
}
|
|
21901
22370
|
flows.push({
|
|
21902
22371
|
source_line: paramSource.line,
|
|
21903
22372
|
sink_line: sink.line,
|
|
@@ -21918,7 +22387,28 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
21918
22387
|
}
|
|
21919
22388
|
return flows;
|
|
21920
22389
|
}
|
|
21921
|
-
function
|
|
22390
|
+
function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
|
|
22391
|
+
if (!variable || sinkLine - srcLine < 2)
|
|
22392
|
+
return false;
|
|
22393
|
+
if (!/^[A-Za-z_][\w]*$/.test(variable))
|
|
22394
|
+
return false;
|
|
22395
|
+
const lines = code.split(`
|
|
22396
|
+
`);
|
|
22397
|
+
const lo = Math.max(0, srcLine);
|
|
22398
|
+
const hi = Math.min(lines.length, sinkLine - 1);
|
|
22399
|
+
const strLit = `(?:"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|\`[^\`\\\\]*(?:\\\\.[^\`\\\\]*)*\`)`;
|
|
22400
|
+
const reNaked = new RegExp(`^\\s*${variable}\\s*(?::?=)\\s*${strLit}\\s*;?\\s*$`);
|
|
22401
|
+
const reGuarded = new RegExp(`^\\s*if\\b.*\\b${variable}\\s*=\\s*${strLit}\\s*;?\\s*$`);
|
|
22402
|
+
for (let i2 = lo;i2 < hi; i2++) {
|
|
22403
|
+
const line = lines[i2];
|
|
22404
|
+
if (!line)
|
|
22405
|
+
continue;
|
|
22406
|
+
if (reNaked.test(line) || reGuarded.test(line))
|
|
22407
|
+
return true;
|
|
22408
|
+
}
|
|
22409
|
+
return false;
|
|
22410
|
+
}
|
|
22411
|
+
function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language) {
|
|
21922
22412
|
const flows = [];
|
|
21923
22413
|
const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
|
|
21924
22414
|
const aliasSanitizedFor = new Map;
|
|
@@ -21973,10 +22463,10 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
21973
22463
|
continue;
|
|
21974
22464
|
const rhs = rhsMatch[1];
|
|
21975
22465
|
for (const san of lineSans) {
|
|
21976
|
-
const sanMatch = san.method.match(
|
|
22466
|
+
const sanMatch = san.method.match(/(\w+)\(\)$/);
|
|
21977
22467
|
if (!sanMatch)
|
|
21978
22468
|
continue;
|
|
21979
|
-
const sanName = sanMatch[1]
|
|
22469
|
+
const sanName = sanMatch[1];
|
|
21980
22470
|
if (!rhs.includes(`${sanName}(`))
|
|
21981
22471
|
continue;
|
|
21982
22472
|
let set = aliasSanitizedFor.get(varName);
|
|
@@ -22048,6 +22538,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
22048
22538
|
if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
|
|
22049
22539
|
break;
|
|
22050
22540
|
}
|
|
22541
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, source.variable, source.line, sink.line)) {
|
|
22542
|
+
break;
|
|
22543
|
+
}
|
|
22051
22544
|
flows.push({
|
|
22052
22545
|
source_line: source.line,
|
|
22053
22546
|
sink_line: sink.line,
|
|
@@ -22082,6 +22575,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
22082
22575
|
for (const source of colocSources) {
|
|
22083
22576
|
if (!canSourceReachSink(source.type, sink.type))
|
|
22084
22577
|
continue;
|
|
22578
|
+
if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
|
|
22579
|
+
continue;
|
|
22580
|
+
}
|
|
22085
22581
|
if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
|
|
22086
22582
|
continue;
|
|
22087
22583
|
flows.push({
|
|
@@ -26325,6 +26821,25 @@ var JS_ROUTE_METHODS = new Set([
|
|
|
26325
26821
|
"head",
|
|
26326
26822
|
"options"
|
|
26327
26823
|
]);
|
|
26824
|
+
var SECURITY_MIDDLEWARE_METHODS = new Set([
|
|
26825
|
+
"helmet",
|
|
26826
|
+
"frameguard",
|
|
26827
|
+
"contentSecurityPolicy",
|
|
26828
|
+
"hsts",
|
|
26829
|
+
"noSniff",
|
|
26830
|
+
"xssFilter",
|
|
26831
|
+
"referrerPolicy",
|
|
26832
|
+
"permittedCrossDomainPolicies",
|
|
26833
|
+
"dnsPrefetchControl",
|
|
26834
|
+
"frameOptions",
|
|
26835
|
+
"headers",
|
|
26836
|
+
"httpStrictTransportSecurity",
|
|
26837
|
+
"contentTypeOptions",
|
|
26838
|
+
"xssProtection",
|
|
26839
|
+
"Talisman",
|
|
26840
|
+
"Secure"
|
|
26841
|
+
]);
|
|
26842
|
+
var SECURITY_MIDDLEWARE_ANNOTATIONS_RE = /\b(EnableWebSecurity|SecurityFilterChain|after_request|before_request)\b/;
|
|
26328
26843
|
|
|
26329
26844
|
class SecurityHeadersPass {
|
|
26330
26845
|
name = "security-headers";
|
|
@@ -26355,6 +26870,7 @@ class SecurityHeadersPass {
|
|
|
26355
26870
|
list.push(call);
|
|
26356
26871
|
}
|
|
26357
26872
|
const hasHandler = detectHandler(graph, calls);
|
|
26873
|
+
const hasGlobalMiddleware = detectGlobalSecurityMiddleware(graph, calls);
|
|
26358
26874
|
for (const rule of this.rules) {
|
|
26359
26875
|
const headerKey = rule.header.toLowerCase();
|
|
26360
26876
|
const writes = writtenHeaders.get(headerKey) ?? [];
|
|
@@ -26363,6 +26879,8 @@ class SecurityHeadersPass {
|
|
|
26363
26879
|
continue;
|
|
26364
26880
|
if (rule.requiresHandler !== false && !hasHandler)
|
|
26365
26881
|
continue;
|
|
26882
|
+
if (hasGlobalMiddleware)
|
|
26883
|
+
continue;
|
|
26366
26884
|
ctx.addFinding({
|
|
26367
26885
|
id: `${rule.rule_id}-${file}`,
|
|
26368
26886
|
pass: this.name,
|
|
@@ -26537,6 +27055,28 @@ function detectHandler(graph, calls) {
|
|
|
26537
27055
|
}
|
|
26538
27056
|
return false;
|
|
26539
27057
|
}
|
|
27058
|
+
function detectGlobalSecurityMiddleware(graph, calls) {
|
|
27059
|
+
for (const call of calls) {
|
|
27060
|
+
if (SECURITY_MIDDLEWARE_METHODS.has(call.method_name))
|
|
27061
|
+
return true;
|
|
27062
|
+
if (call.method_name === "use" && call.arguments.length > 0) {
|
|
27063
|
+
const firstArg = call.arguments[0].expression ?? "";
|
|
27064
|
+
if (/\b(helmet|Talisman|secure)\b/.test(firstArg))
|
|
27065
|
+
return true;
|
|
27066
|
+
}
|
|
27067
|
+
}
|
|
27068
|
+
for (const type of graph.ir.types) {
|
|
27069
|
+
if (type.annotations.some((a) => SECURITY_MIDDLEWARE_ANNOTATIONS_RE.test(a)))
|
|
27070
|
+
return true;
|
|
27071
|
+
for (const method of type.methods) {
|
|
27072
|
+
if (method.annotations.some((a) => SECURITY_MIDDLEWARE_ANNOTATIONS_RE.test(a)))
|
|
27073
|
+
return true;
|
|
27074
|
+
if (/^security[A-Za-z]*FilterChain$/i.test(method.name))
|
|
27075
|
+
return true;
|
|
27076
|
+
}
|
|
27077
|
+
}
|
|
27078
|
+
return false;
|
|
27079
|
+
}
|
|
26540
27080
|
function checkInheritedCorsHeaders(fileAnalyses, typeHierarchy, sourceLines) {
|
|
26541
27081
|
const findings = [];
|
|
26542
27082
|
for (const { file: parentFile, analysis: parentIR } of fileAnalyses) {
|
|
@@ -30023,7 +30563,7 @@ var colors = {
|
|
|
30023
30563
|
};
|
|
30024
30564
|
|
|
30025
30565
|
// src/version.ts
|
|
30026
|
-
var version = "3.
|
|
30566
|
+
var version = "3.59.0";
|
|
30027
30567
|
|
|
30028
30568
|
// src/formatters.ts
|
|
30029
30569
|
var SINK_SEVERITY = {
|
|
@@ -30797,7 +31337,8 @@ var TEST_PATTERNS = [
|
|
|
30797
31337
|
/_test\.py$/,
|
|
30798
31338
|
/_tests\.py$/,
|
|
30799
31339
|
/test_.*\.py$/,
|
|
30800
|
-
/_test\.rs
|
|
31340
|
+
/_test\.rs$/,
|
|
31341
|
+
/_test\.go$/
|
|
30801
31342
|
];
|
|
30802
31343
|
function isTestFile2(filePath) {
|
|
30803
31344
|
return TEST_PATTERNS.some((pattern) => pattern.test(filePath));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.59.0",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"circle-ir": "^3.
|
|
68
|
+
"circle-ir": "^3.59.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|