cognium-dev 3.56.0 → 3.58.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 +496 -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;
|
|
@@ -13622,6 +13889,27 @@ class ConstantPropagator {
|
|
|
13622
13889
|
if (nameNode) {
|
|
13623
13890
|
const varName = getNodeText2(nameNode, this.source);
|
|
13624
13891
|
loopVarNames.add(varName);
|
|
13892
|
+
const collectionNode = node.childForFieldName("value");
|
|
13893
|
+
if (collectionNode) {
|
|
13894
|
+
const collectionName = getNodeText2(collectionNode, this.source);
|
|
13895
|
+
const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
|
|
13896
|
+
let elementIsTainted = this.tainted.has(collectionName) || this.tainted.has(scopedCollection);
|
|
13897
|
+
if (!elementIsTainted) {
|
|
13898
|
+
const taintedIndices = this.taintedArrayElements.get(collectionName);
|
|
13899
|
+
if (taintedIndices && taintedIndices.size > 0)
|
|
13900
|
+
elementIsTainted = true;
|
|
13901
|
+
}
|
|
13902
|
+
if (!elementIsTainted) {
|
|
13903
|
+
const taintedKeys = this.taintedCollections.get(collectionName);
|
|
13904
|
+
if (taintedKeys && taintedKeys.size > 0)
|
|
13905
|
+
elementIsTainted = true;
|
|
13906
|
+
}
|
|
13907
|
+
if (elementIsTainted) {
|
|
13908
|
+
const scopedVar = this.currentMethod ? `${this.currentMethod}:${varName}` : varName;
|
|
13909
|
+
this.tainted.add(varName);
|
|
13910
|
+
this.tainted.add(scopedVar);
|
|
13911
|
+
}
|
|
13912
|
+
}
|
|
13625
13913
|
}
|
|
13626
13914
|
}
|
|
13627
13915
|
for (const varName of loopVarNames) {
|
|
@@ -13943,6 +14231,16 @@ class ConstantPropagator {
|
|
|
13943
14231
|
}
|
|
13944
14232
|
this.inConditionalBranch = wasInConditional;
|
|
13945
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
|
+
}
|
|
13946
14244
|
}
|
|
13947
14245
|
}
|
|
13948
14246
|
normalizeCondition(cond) {
|
|
@@ -14114,15 +14412,26 @@ class ConstantPropagator {
|
|
|
14114
14412
|
return null;
|
|
14115
14413
|
}
|
|
14116
14414
|
isSanitizerMethodCall(node) {
|
|
14117
|
-
|
|
14415
|
+
const methodName = this.extractCallName(node);
|
|
14416
|
+
if (!methodName)
|
|
14118
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");
|
|
14119
14426
|
}
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
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);
|
|
14123
14433
|
}
|
|
14124
|
-
|
|
14125
|
-
return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
|
|
14434
|
+
return getNodeText2(fnNode, this.source);
|
|
14126
14435
|
}
|
|
14127
14436
|
isAntiSanitizerCall(node) {
|
|
14128
14437
|
if (node.type !== "method_invocation") {
|
|
@@ -14514,9 +14823,21 @@ class ConstantPropagator {
|
|
|
14514
14823
|
this.taintedCollections.set(collectionName, new Set);
|
|
14515
14824
|
}
|
|
14516
14825
|
this.taintedCollections.get(collectionName).add(keyStr);
|
|
14826
|
+
const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
|
|
14827
|
+
this.tainted.add(scopedCollection);
|
|
14828
|
+
this.tainted.add(collectionName);
|
|
14517
14829
|
}
|
|
14518
14830
|
}
|
|
14519
14831
|
}
|
|
14832
|
+
if (methodName === "append" || methodName === "insert") {
|
|
14833
|
+
const args2 = argsNode.children.filter((c) => c.type !== "(" && c.type !== ")" && c.type !== ",");
|
|
14834
|
+
const valueArg = methodName === "insert" && args2.length >= 2 ? args2[1] : args2[0];
|
|
14835
|
+
if (valueArg && this.isTaintedExpression(valueArg)) {
|
|
14836
|
+
const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
|
|
14837
|
+
this.tainted.add(scopedCollection);
|
|
14838
|
+
this.tainted.add(collectionName);
|
|
14839
|
+
}
|
|
14840
|
+
}
|
|
14520
14841
|
if (methodName === "add" || methodName === "addLast") {
|
|
14521
14842
|
const args2 = argsNode.children.filter((c) => c.type !== "(" && c.type !== ")" && c.type !== ",");
|
|
14522
14843
|
if (args2.length >= 1) {
|
|
@@ -20788,26 +21109,44 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
20788
21109
|
const lines = sourceCode.split(`
|
|
20789
21110
|
`);
|
|
20790
21111
|
const definedVars = new Set(dfg.defs.filter((d) => d.kind === "local").map((d) => d.variable));
|
|
21112
|
+
const fnHeaderRe = /^\s*(?:function\s+)?[A-Za-z_][\w-]*\s*\(\s*\)\s*\{?\s*$|^\s*function\s+[A-Za-z_][\w-]*\s*\{?\s*$/;
|
|
21113
|
+
let braceDepth = 0;
|
|
20791
21114
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
20792
21115
|
const line = lines[i2];
|
|
20793
21116
|
const trimmed = line.trim();
|
|
20794
21117
|
const lineNumber = i2 + 1;
|
|
20795
21118
|
if (trimmed.startsWith("#"))
|
|
20796
21119
|
continue;
|
|
20797
|
-
const
|
|
20798
|
-
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20803
|
-
sources.
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
|
|
20807
|
-
|
|
20808
|
-
|
|
20809
|
-
|
|
20810
|
-
|
|
21120
|
+
const insideFunction = braceDepth > 0;
|
|
21121
|
+
if (!insideFunction) {
|
|
21122
|
+
const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
|
|
21123
|
+
let m;
|
|
21124
|
+
while ((m = positionalRe.exec(line)) !== null) {
|
|
21125
|
+
const param = m[1] ?? m[2];
|
|
21126
|
+
const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
|
|
21127
|
+
if (!alreadyExists) {
|
|
21128
|
+
sources.push({
|
|
21129
|
+
type: "io_input",
|
|
21130
|
+
location: `positional parameter $${param}`,
|
|
21131
|
+
severity: "high",
|
|
21132
|
+
line: lineNumber,
|
|
21133
|
+
confidence: 1,
|
|
21134
|
+
variable: param
|
|
21135
|
+
});
|
|
21136
|
+
}
|
|
21137
|
+
}
|
|
21138
|
+
}
|
|
21139
|
+
if (fnHeaderRe.test(line) || /^\s*[A-Za-z_][\w-]*\s*\(\s*\)\s*\{/.test(line)) {
|
|
21140
|
+
const openBracesOnLine = (line.match(/\{/g) ?? []).length;
|
|
21141
|
+
const closeBracesOnLine = (line.match(/\}/g) ?? []).length;
|
|
21142
|
+
braceDepth += openBracesOnLine - closeBracesOnLine;
|
|
21143
|
+
} else {
|
|
21144
|
+
if (braceDepth > 0) {
|
|
21145
|
+
const openBracesOnLine = (line.match(/\{/g) ?? []).length;
|
|
21146
|
+
const closeBracesOnLine = (line.match(/\}/g) ?? []).length;
|
|
21147
|
+
braceDepth += openBracesOnLine - closeBracesOnLine;
|
|
21148
|
+
if (braceDepth < 0)
|
|
21149
|
+
braceDepth = 0;
|
|
20811
21150
|
}
|
|
20812
21151
|
}
|
|
20813
21152
|
const cmdSubAssign = trimmed.match(/^(\w+)=\$\((\w+)\s/);
|
|
@@ -21254,10 +21593,15 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
|
|
|
21254
21593
|
const callsAtSink = callsByLine.get(sink.line) ?? [];
|
|
21255
21594
|
const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
|
|
21256
21595
|
const relevantCalls = sink.method ? callsAtSink.filter((c) => c.method_name === sink.method) : callsAtSink;
|
|
21596
|
+
const trustArgPositions = language !== "bash" && language !== "shell";
|
|
21257
21597
|
for (const call of relevantCalls) {
|
|
21258
21598
|
let allArgsAreClean = true;
|
|
21599
|
+
let dangerousArgCount = 0;
|
|
21259
21600
|
const methodName = call.in_method;
|
|
21260
21601
|
for (const arg of call.arguments) {
|
|
21602
|
+
if (trustArgPositions && sink.argPositions && sink.argPositions.length > 0 && !sink.argPositions.includes(arg.position))
|
|
21603
|
+
continue;
|
|
21604
|
+
dangerousArgCount++;
|
|
21261
21605
|
if (language === "bash" && arg.expression === call.method_name && !arg.variable && arg.literal == null)
|
|
21262
21606
|
continue;
|
|
21263
21607
|
if (arg.variable && !arg.expression?.includes("[")) {
|
|
@@ -21285,7 +21629,7 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
|
|
|
21285
21629
|
allArgsAreClean = false;
|
|
21286
21630
|
}
|
|
21287
21631
|
}
|
|
21288
|
-
if (allArgsAreClean &&
|
|
21632
|
+
if (allArgsAreClean && dangerousArgCount > 0)
|
|
21289
21633
|
return false;
|
|
21290
21634
|
}
|
|
21291
21635
|
return true;
|
|
@@ -21336,6 +21680,17 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
|
21336
21680
|
}
|
|
21337
21681
|
|
|
21338
21682
|
// ../circle-ir/dist/analysis/taint-propagation.js
|
|
21683
|
+
function buildSanitizersByLine(sanitizers) {
|
|
21684
|
+
const out2 = new Map;
|
|
21685
|
+
for (const san of sanitizers) {
|
|
21686
|
+
const existing = out2.get(san.line);
|
|
21687
|
+
if (existing)
|
|
21688
|
+
existing.push(san);
|
|
21689
|
+
else
|
|
21690
|
+
out2.set(san.line, [san]);
|
|
21691
|
+
}
|
|
21692
|
+
return out2;
|
|
21693
|
+
}
|
|
21339
21694
|
function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
|
|
21340
21695
|
let graph;
|
|
21341
21696
|
let sources;
|
|
@@ -21371,7 +21726,7 @@ function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSani
|
|
|
21371
21726
|
const defsByLine = graph.defsByLine;
|
|
21372
21727
|
const usesByLine = graph.usesByLine;
|
|
21373
21728
|
const callsByLine = graph.callsByLine;
|
|
21374
|
-
const sanitizersByLine = graph.sanitizersByLine;
|
|
21729
|
+
const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
|
|
21375
21730
|
const defById = graph.defById;
|
|
21376
21731
|
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
|
|
21377
21732
|
const initialTaint = rawInitialTaint.filter((tv) => {
|
|
@@ -21639,7 +21994,7 @@ class TaintPropagationPass {
|
|
|
21639
21994
|
flows.push(f);
|
|
21640
21995
|
}
|
|
21641
21996
|
}
|
|
21642
|
-
const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines) ?? [];
|
|
21997
|
+
const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code) ?? [];
|
|
21643
21998
|
for (const f of collectionFlows) {
|
|
21644
21999
|
if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
|
|
21645
22000
|
continue;
|
|
@@ -21661,13 +22016,13 @@ class TaintPropagationPass {
|
|
|
21661
22016
|
continue;
|
|
21662
22017
|
flows.push(f);
|
|
21663
22018
|
}
|
|
21664
|
-
const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines) ?? [];
|
|
22019
|
+
const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
|
|
21665
22020
|
for (const f of paramFlows) {
|
|
21666
22021
|
if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
|
|
21667
22022
|
flows.push(f);
|
|
21668
22023
|
}
|
|
21669
22024
|
}
|
|
21670
|
-
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, ctx.code, ctx.language) ?? [];
|
|
22025
|
+
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
|
|
21671
22026
|
for (const f of exprScanFlows) {
|
|
21672
22027
|
if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
|
|
21673
22028
|
continue;
|
|
@@ -21689,10 +22044,25 @@ class TaintPropagationPass {
|
|
|
21689
22044
|
continue;
|
|
21690
22045
|
flows.push(f);
|
|
21691
22046
|
}
|
|
21692
|
-
|
|
22047
|
+
const sanitizedNames = constProp.sanitizedVars;
|
|
22048
|
+
const finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
|
|
22049
|
+
if (f.path.length === 0)
|
|
22050
|
+
return true;
|
|
22051
|
+
const sourceVar = f.path[0].variable;
|
|
22052
|
+
if (!sourceVar)
|
|
22053
|
+
return true;
|
|
22054
|
+
if (sanitizedNames.has(sourceVar))
|
|
22055
|
+
return false;
|
|
22056
|
+
for (const s of sanitizedNames) {
|
|
22057
|
+
if (s.endsWith(`:${sourceVar}`))
|
|
22058
|
+
return false;
|
|
22059
|
+
}
|
|
22060
|
+
return true;
|
|
22061
|
+
});
|
|
22062
|
+
return { flows: finalFlows };
|
|
21693
22063
|
}
|
|
21694
22064
|
}
|
|
21695
|
-
function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines) {
|
|
22065
|
+
function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code) {
|
|
21696
22066
|
const flows = [];
|
|
21697
22067
|
const callsByLine = new Map;
|
|
21698
22068
|
for (const call of calls) {
|
|
@@ -21715,6 +22085,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
21715
22085
|
if (taintedVars.has(varName) || taintedVars.has(scopedName)) {
|
|
21716
22086
|
const source = sources[0];
|
|
21717
22087
|
if (source) {
|
|
22088
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line)) {
|
|
22089
|
+
continue;
|
|
22090
|
+
}
|
|
21718
22091
|
flows.push({
|
|
21719
22092
|
source_line: source.line,
|
|
21720
22093
|
sink_line: sink.line,
|
|
@@ -21749,6 +22122,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
21749
22122
|
if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
|
|
21750
22123
|
const source = sources[0];
|
|
21751
22124
|
if (source) {
|
|
22125
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line)) {
|
|
22126
|
+
continue;
|
|
22127
|
+
}
|
|
21752
22128
|
flows.push({
|
|
21753
22129
|
source_line: source.line,
|
|
21754
22130
|
sink_line: sink.line,
|
|
@@ -21819,7 +22195,7 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
|
|
|
21819
22195
|
}
|
|
21820
22196
|
return flows;
|
|
21821
22197
|
}
|
|
21822
|
-
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines) {
|
|
22198
|
+
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
|
|
21823
22199
|
const flows = [];
|
|
21824
22200
|
const paramSourcesByMethod = new Map;
|
|
21825
22201
|
for (const source of sources) {
|
|
@@ -21865,6 +22241,9 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
21865
22241
|
if (paramSource) {
|
|
21866
22242
|
const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line);
|
|
21867
22243
|
if (!exists) {
|
|
22244
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
|
|
22245
|
+
continue;
|
|
22246
|
+
}
|
|
21868
22247
|
flows.push({
|
|
21869
22248
|
source_line: paramSource.line,
|
|
21870
22249
|
sink_line: sink.line,
|
|
@@ -21885,7 +22264,28 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
21885
22264
|
}
|
|
21886
22265
|
return flows;
|
|
21887
22266
|
}
|
|
21888
|
-
function
|
|
22267
|
+
function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
|
|
22268
|
+
if (!variable || sinkLine - srcLine < 2)
|
|
22269
|
+
return false;
|
|
22270
|
+
if (!/^[A-Za-z_][\w]*$/.test(variable))
|
|
22271
|
+
return false;
|
|
22272
|
+
const lines = code.split(`
|
|
22273
|
+
`);
|
|
22274
|
+
const lo = Math.max(0, srcLine);
|
|
22275
|
+
const hi = Math.min(lines.length, sinkLine - 1);
|
|
22276
|
+
const strLit = `(?:"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|\`[^\`\\\\]*(?:\\\\.[^\`\\\\]*)*\`)`;
|
|
22277
|
+
const reNaked = new RegExp(`^\\s*${variable}\\s*(?::?=)\\s*${strLit}\\s*;?\\s*$`);
|
|
22278
|
+
const reGuarded = new RegExp(`^\\s*if\\b.*\\b${variable}\\s*=\\s*${strLit}\\s*;?\\s*$`);
|
|
22279
|
+
for (let i2 = lo;i2 < hi; i2++) {
|
|
22280
|
+
const line = lines[i2];
|
|
22281
|
+
if (!line)
|
|
22282
|
+
continue;
|
|
22283
|
+
if (reNaked.test(line) || reGuarded.test(line))
|
|
22284
|
+
return true;
|
|
22285
|
+
}
|
|
22286
|
+
return false;
|
|
22287
|
+
}
|
|
22288
|
+
function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language) {
|
|
21889
22289
|
const flows = [];
|
|
21890
22290
|
const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
|
|
21891
22291
|
const aliasSanitizedFor = new Map;
|
|
@@ -21940,10 +22340,10 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
21940
22340
|
continue;
|
|
21941
22341
|
const rhs = rhsMatch[1];
|
|
21942
22342
|
for (const san of lineSans) {
|
|
21943
|
-
const sanMatch = san.method.match(
|
|
22343
|
+
const sanMatch = san.method.match(/(\w+)\(\)$/);
|
|
21944
22344
|
if (!sanMatch)
|
|
21945
22345
|
continue;
|
|
21946
|
-
const sanName = sanMatch[1]
|
|
22346
|
+
const sanName = sanMatch[1];
|
|
21947
22347
|
if (!rhs.includes(`${sanName}(`))
|
|
21948
22348
|
continue;
|
|
21949
22349
|
let set = aliasSanitizedFor.get(varName);
|
|
@@ -22015,6 +22415,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
22015
22415
|
if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
|
|
22016
22416
|
break;
|
|
22017
22417
|
}
|
|
22418
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, source.variable, source.line, sink.line)) {
|
|
22419
|
+
break;
|
|
22420
|
+
}
|
|
22018
22421
|
flows.push({
|
|
22019
22422
|
source_line: source.line,
|
|
22020
22423
|
sink_line: sink.line,
|
|
@@ -22049,6 +22452,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
22049
22452
|
for (const source of colocSources) {
|
|
22050
22453
|
if (!canSourceReachSink(source.type, sink.type))
|
|
22051
22454
|
continue;
|
|
22455
|
+
if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
|
|
22456
|
+
continue;
|
|
22457
|
+
}
|
|
22052
22458
|
if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
|
|
22053
22459
|
continue;
|
|
22054
22460
|
flows.push({
|
|
@@ -26292,6 +26698,25 @@ var JS_ROUTE_METHODS = new Set([
|
|
|
26292
26698
|
"head",
|
|
26293
26699
|
"options"
|
|
26294
26700
|
]);
|
|
26701
|
+
var SECURITY_MIDDLEWARE_METHODS = new Set([
|
|
26702
|
+
"helmet",
|
|
26703
|
+
"frameguard",
|
|
26704
|
+
"contentSecurityPolicy",
|
|
26705
|
+
"hsts",
|
|
26706
|
+
"noSniff",
|
|
26707
|
+
"xssFilter",
|
|
26708
|
+
"referrerPolicy",
|
|
26709
|
+
"permittedCrossDomainPolicies",
|
|
26710
|
+
"dnsPrefetchControl",
|
|
26711
|
+
"frameOptions",
|
|
26712
|
+
"headers",
|
|
26713
|
+
"httpStrictTransportSecurity",
|
|
26714
|
+
"contentTypeOptions",
|
|
26715
|
+
"xssProtection",
|
|
26716
|
+
"Talisman",
|
|
26717
|
+
"Secure"
|
|
26718
|
+
]);
|
|
26719
|
+
var SECURITY_MIDDLEWARE_ANNOTATIONS_RE = /\b(EnableWebSecurity|SecurityFilterChain|after_request|before_request)\b/;
|
|
26295
26720
|
|
|
26296
26721
|
class SecurityHeadersPass {
|
|
26297
26722
|
name = "security-headers";
|
|
@@ -26322,6 +26747,7 @@ class SecurityHeadersPass {
|
|
|
26322
26747
|
list.push(call);
|
|
26323
26748
|
}
|
|
26324
26749
|
const hasHandler = detectHandler(graph, calls);
|
|
26750
|
+
const hasGlobalMiddleware = detectGlobalSecurityMiddleware(graph, calls);
|
|
26325
26751
|
for (const rule of this.rules) {
|
|
26326
26752
|
const headerKey = rule.header.toLowerCase();
|
|
26327
26753
|
const writes = writtenHeaders.get(headerKey) ?? [];
|
|
@@ -26330,6 +26756,8 @@ class SecurityHeadersPass {
|
|
|
26330
26756
|
continue;
|
|
26331
26757
|
if (rule.requiresHandler !== false && !hasHandler)
|
|
26332
26758
|
continue;
|
|
26759
|
+
if (hasGlobalMiddleware)
|
|
26760
|
+
continue;
|
|
26333
26761
|
ctx.addFinding({
|
|
26334
26762
|
id: `${rule.rule_id}-${file}`,
|
|
26335
26763
|
pass: this.name,
|
|
@@ -26504,6 +26932,28 @@ function detectHandler(graph, calls) {
|
|
|
26504
26932
|
}
|
|
26505
26933
|
return false;
|
|
26506
26934
|
}
|
|
26935
|
+
function detectGlobalSecurityMiddleware(graph, calls) {
|
|
26936
|
+
for (const call of calls) {
|
|
26937
|
+
if (SECURITY_MIDDLEWARE_METHODS.has(call.method_name))
|
|
26938
|
+
return true;
|
|
26939
|
+
if (call.method_name === "use" && call.arguments.length > 0) {
|
|
26940
|
+
const firstArg = call.arguments[0].expression ?? "";
|
|
26941
|
+
if (/\b(helmet|Talisman|secure)\b/.test(firstArg))
|
|
26942
|
+
return true;
|
|
26943
|
+
}
|
|
26944
|
+
}
|
|
26945
|
+
for (const type of graph.ir.types) {
|
|
26946
|
+
if (type.annotations.some((a) => SECURITY_MIDDLEWARE_ANNOTATIONS_RE.test(a)))
|
|
26947
|
+
return true;
|
|
26948
|
+
for (const method of type.methods) {
|
|
26949
|
+
if (method.annotations.some((a) => SECURITY_MIDDLEWARE_ANNOTATIONS_RE.test(a)))
|
|
26950
|
+
return true;
|
|
26951
|
+
if (/^security[A-Za-z]*FilterChain$/i.test(method.name))
|
|
26952
|
+
return true;
|
|
26953
|
+
}
|
|
26954
|
+
}
|
|
26955
|
+
return false;
|
|
26956
|
+
}
|
|
26507
26957
|
function checkInheritedCorsHeaders(fileAnalyses, typeHierarchy, sourceLines) {
|
|
26508
26958
|
const findings = [];
|
|
26509
26959
|
for (const { file: parentFile, analysis: parentIR } of fileAnalyses) {
|
|
@@ -29990,7 +30440,7 @@ var colors = {
|
|
|
29990
30440
|
};
|
|
29991
30441
|
|
|
29992
30442
|
// src/version.ts
|
|
29993
|
-
var version = "3.
|
|
30443
|
+
var version = "3.58.0";
|
|
29994
30444
|
|
|
29995
30445
|
// src/formatters.ts
|
|
29996
30446
|
var SINK_SEVERITY = {
|
|
@@ -30764,7 +31214,8 @@ var TEST_PATTERNS = [
|
|
|
30764
31214
|
/_test\.py$/,
|
|
30765
31215
|
/_tests\.py$/,
|
|
30766
31216
|
/test_.*\.py$/,
|
|
30767
|
-
/_test\.rs
|
|
31217
|
+
/_test\.rs$/,
|
|
31218
|
+
/_test\.go$/
|
|
30768
31219
|
];
|
|
30769
31220
|
function isTestFile2(filePath) {
|
|
30770
31221
|
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.58.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.58.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|