cognium-dev 3.138.0 → 3.140.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 +182 -8
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -5678,8 +5678,32 @@ function resolveReceiverType(receiver, context) {
|
|
|
5678
5678
|
return resolveFqn(simple, context);
|
|
5679
5679
|
}
|
|
5680
5680
|
}
|
|
5681
|
+
const chained = receiver.match(/^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)\s*\([^()]*\)$/);
|
|
5682
|
+
if (chained) {
|
|
5683
|
+
const varName = chained[1];
|
|
5684
|
+
const methodName = chained[2];
|
|
5685
|
+
const varType = context.localVarTypes.get(varName) ?? context.paramTypes.get(varName) ?? context.fieldTypes.get(varName);
|
|
5686
|
+
if (varType) {
|
|
5687
|
+
const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[stripGenerics(varType)]?.[methodName];
|
|
5688
|
+
if (returnType)
|
|
5689
|
+
return resolveFqn(returnType, context);
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5681
5692
|
return { simpleName: null, fqn: null };
|
|
5682
5693
|
}
|
|
5694
|
+
var JAVA_CHAINED_FACTORY_RETURN_TYPES = {
|
|
5695
|
+
HttpServletRequest: {
|
|
5696
|
+
getSession: "HttpSession",
|
|
5697
|
+
getServletContext: "ServletContext",
|
|
5698
|
+
getRequestDispatcher: "RequestDispatcher"
|
|
5699
|
+
},
|
|
5700
|
+
HttpSession: {
|
|
5701
|
+
getServletContext: "ServletContext"
|
|
5702
|
+
},
|
|
5703
|
+
ServletContext: {
|
|
5704
|
+
getRequestDispatcher: "RequestDispatcher"
|
|
5705
|
+
}
|
|
5706
|
+
};
|
|
5683
5707
|
function resolveFqn(simpleName, context) {
|
|
5684
5708
|
if (!simpleName)
|
|
5685
5709
|
return { simpleName: null, fqn: null };
|
|
@@ -13400,19 +13424,19 @@ function formatCallCode(call) {
|
|
|
13400
13424
|
// ../circle-ir/dist/analysis/findings.js
|
|
13401
13425
|
function canSourceReachSink(sourceType, sinkType) {
|
|
13402
13426
|
const sourceToSinkMapping = {
|
|
13403
|
-
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect"],
|
|
13404
|
-
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect"],
|
|
13405
|
-
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect"],
|
|
13406
|
-
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect"],
|
|
13407
|
-
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect"],
|
|
13408
|
-
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect"],
|
|
13427
|
+
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
13428
|
+
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
13429
|
+
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
|
|
13430
|
+
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
|
|
13431
|
+
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary"],
|
|
13432
|
+
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
13409
13433
|
io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf"],
|
|
13410
13434
|
env_input: ["command_injection", "path_traversal"],
|
|
13411
13435
|
db_input: ["xss", "sql_injection"],
|
|
13412
13436
|
file_input: ["deserialization", "xxe", "path_traversal", "command_injection", "code_injection"],
|
|
13413
13437
|
network_input: ["sql_injection", "command_injection", "xss", "ssrf"],
|
|
13414
13438
|
config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf"],
|
|
13415
|
-
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect"],
|
|
13439
|
+
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
13416
13440
|
plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection"]
|
|
13417
13441
|
};
|
|
13418
13442
|
const validSinks = sourceToSinkMapping[sourceType];
|
|
@@ -38092,6 +38116,154 @@ class UnrestrictedFileUploadPass {
|
|
|
38092
38116
|
}
|
|
38093
38117
|
}
|
|
38094
38118
|
|
|
38119
|
+
// ../circle-ir/dist/analysis/passes/missing-sanitizer-gate-pass.js
|
|
38120
|
+
var HTML_OUTPUT_SINKS = new Set([
|
|
38121
|
+
"addAttribute",
|
|
38122
|
+
"setAttribute",
|
|
38123
|
+
"write",
|
|
38124
|
+
"writeAttribute",
|
|
38125
|
+
"writeRaw",
|
|
38126
|
+
"writeStartElement",
|
|
38127
|
+
"writeEndElement",
|
|
38128
|
+
"writeCharacters",
|
|
38129
|
+
"printXML",
|
|
38130
|
+
"printXMLElement",
|
|
38131
|
+
"printXMLStartElement",
|
|
38132
|
+
"printXMLEndElement",
|
|
38133
|
+
"printRaw",
|
|
38134
|
+
"print",
|
|
38135
|
+
"println",
|
|
38136
|
+
"printf",
|
|
38137
|
+
"append",
|
|
38138
|
+
"format"
|
|
38139
|
+
]);
|
|
38140
|
+
var SANITIZER_METHODS2 = new Set([
|
|
38141
|
+
"isAttributeAllowed",
|
|
38142
|
+
"isElementAllowed",
|
|
38143
|
+
"isSafe",
|
|
38144
|
+
"sanitize",
|
|
38145
|
+
"clean",
|
|
38146
|
+
"escape",
|
|
38147
|
+
"encode",
|
|
38148
|
+
"forHtml",
|
|
38149
|
+
"forHtmlAttribute",
|
|
38150
|
+
"forHtmlContent",
|
|
38151
|
+
"encodeForHTML",
|
|
38152
|
+
"encodeForHTMLAttribute",
|
|
38153
|
+
"escapeHtml",
|
|
38154
|
+
"escapeHtml4",
|
|
38155
|
+
"escapeXml",
|
|
38156
|
+
"htmlEscape",
|
|
38157
|
+
"xmlEscape"
|
|
38158
|
+
]);
|
|
38159
|
+
var SANITIZER_PREFIX_RE = /^(?:clean|sanitize|escape|encode)[A-Z]/;
|
|
38160
|
+
function isSanitizerCall(methodName) {
|
|
38161
|
+
return SANITIZER_METHODS2.has(methodName) || SANITIZER_PREFIX_RE.test(methodName);
|
|
38162
|
+
}
|
|
38163
|
+
var STRING_PARAM_NAME_PATTERN = /attr|attribute|html|xml|body|content/i;
|
|
38164
|
+
|
|
38165
|
+
class MissingSanitizerGatePass {
|
|
38166
|
+
name = "missing-sanitizer-gate";
|
|
38167
|
+
category = "security";
|
|
38168
|
+
run(ctx) {
|
|
38169
|
+
const { graph, language } = ctx;
|
|
38170
|
+
if (language !== "java")
|
|
38171
|
+
return { findings: 0 };
|
|
38172
|
+
const { cfg, calls, types } = graph.ir;
|
|
38173
|
+
if (cfg.blocks.length === 0 || cfg.edges.length === 0)
|
|
38174
|
+
return { findings: 0 };
|
|
38175
|
+
if (calls.length === 0)
|
|
38176
|
+
return { findings: 0 };
|
|
38177
|
+
const file = graph.ir.meta.file;
|
|
38178
|
+
const sinkCalls = [];
|
|
38179
|
+
const sanitizerLines = [];
|
|
38180
|
+
for (const call of calls) {
|
|
38181
|
+
if (HTML_OUTPUT_SINKS.has(call.method_name)) {
|
|
38182
|
+
sinkCalls.push({ line: call.location.line, method: call.method_name });
|
|
38183
|
+
}
|
|
38184
|
+
if (isSanitizerCall(call.method_name)) {
|
|
38185
|
+
sanitizerLines.push(call.location.line);
|
|
38186
|
+
}
|
|
38187
|
+
}
|
|
38188
|
+
if (sinkCalls.length === 0)
|
|
38189
|
+
return { findings: 0 };
|
|
38190
|
+
const blockContainingLine = (line) => cfg.blocks.find((b) => b.start_line <= line && line <= b.end_line) ?? null;
|
|
38191
|
+
const domByMethodKey = new Map;
|
|
38192
|
+
const getDomForMethod = (methodKey, method) => {
|
|
38193
|
+
if (domByMethodKey.has(methodKey))
|
|
38194
|
+
return domByMethodKey.get(methodKey) ?? null;
|
|
38195
|
+
const entry = cfg.blocks.find((b) => b.type === "entry" && b.start_line >= method.start_line && b.end_line <= method.end_line) ?? cfg.blocks.find((b) => b.start_line >= method.start_line && b.end_line <= method.end_line);
|
|
38196
|
+
const dom = entry ? new DominatorGraph2(cfg, entry.id) : null;
|
|
38197
|
+
domByMethodKey.set(methodKey, dom);
|
|
38198
|
+
return dom;
|
|
38199
|
+
};
|
|
38200
|
+
const reportedMethods = new Set;
|
|
38201
|
+
let count = 0;
|
|
38202
|
+
for (const sink of sinkCalls) {
|
|
38203
|
+
const sinkBlock = blockContainingLine(sink.line);
|
|
38204
|
+
if (!sinkBlock)
|
|
38205
|
+
continue;
|
|
38206
|
+
const methodInfo = graph.methodAtLine(sink.line);
|
|
38207
|
+
if (!methodInfo)
|
|
38208
|
+
continue;
|
|
38209
|
+
const methodKey = `${methodInfo.type.name}::${methodInfo.method.name}`;
|
|
38210
|
+
if (reportedMethods.has(methodKey))
|
|
38211
|
+
continue;
|
|
38212
|
+
const tier = classifyEntryPointTier(methodInfo.method, methodInfo.type, {
|
|
38213
|
+
types,
|
|
38214
|
+
language
|
|
38215
|
+
});
|
|
38216
|
+
if (tier === "TIER_1_ENTRY_POINT")
|
|
38217
|
+
continue;
|
|
38218
|
+
if (!methodAcceptsHtmlShapedParam(methodInfo.method))
|
|
38219
|
+
continue;
|
|
38220
|
+
const dom = getDomForMethod(methodKey, methodInfo.method);
|
|
38221
|
+
if (!dom)
|
|
38222
|
+
continue;
|
|
38223
|
+
const { start_line, end_line } = methodInfo.method;
|
|
38224
|
+
const sanitizersInMethod = sanitizerLines.filter((l) => l >= start_line && l <= end_line);
|
|
38225
|
+
const dominated = sanitizersInMethod.some((sanLine) => {
|
|
38226
|
+
const sanBlock = blockContainingLine(sanLine);
|
|
38227
|
+
return sanBlock !== null && dom.dominates(sanBlock.id, sinkBlock.id);
|
|
38228
|
+
});
|
|
38229
|
+
if (!dominated) {
|
|
38230
|
+
reportedMethods.add(methodKey);
|
|
38231
|
+
count++;
|
|
38232
|
+
ctx.addFinding({
|
|
38233
|
+
id: `missing-sanitizer-gate-${file}-${sink.line}`,
|
|
38234
|
+
pass: this.name,
|
|
38235
|
+
category: this.category,
|
|
38236
|
+
rule_id: this.name,
|
|
38237
|
+
cwe: "CWE-79",
|
|
38238
|
+
severity: "medium",
|
|
38239
|
+
level: "note",
|
|
38240
|
+
confidence: "medium",
|
|
38241
|
+
message: `HTML output sink \`${sink.method}()\` at line ${sink.line} is not ` + `dominated by any sanitizer call in method \`${methodInfo.method.name}\`. ` + `Caller-supplied attribute/HTML values may reach the sink unescaped.`,
|
|
38242
|
+
file,
|
|
38243
|
+
line: sink.line,
|
|
38244
|
+
fix: `Gate the sink on all paths with an allow-list check ` + `(e.g. \`isAttributeAllowed(...)\`) or wrap the argument in an HTML/XML ` + `escaper (\`escapeHtml\`, \`encodeForHTMLAttribute\`, \`forHtmlAttribute\`).`,
|
|
38245
|
+
evidence: { sink: sink.method, method: methodInfo.method.name }
|
|
38246
|
+
});
|
|
38247
|
+
}
|
|
38248
|
+
}
|
|
38249
|
+
return { findings: count };
|
|
38250
|
+
}
|
|
38251
|
+
}
|
|
38252
|
+
function methodAcceptsHtmlShapedParam(method) {
|
|
38253
|
+
for (const p of method.parameters) {
|
|
38254
|
+
if (!p.type)
|
|
38255
|
+
continue;
|
|
38256
|
+
const type = p.type.replace(/\s+/g, "");
|
|
38257
|
+
if (type === "Attributes")
|
|
38258
|
+
return true;
|
|
38259
|
+
if (type.startsWith("Map<String,String"))
|
|
38260
|
+
return true;
|
|
38261
|
+
if (type === "String" && STRING_PARAM_NAME_PATTERN.test(p.name))
|
|
38262
|
+
return true;
|
|
38263
|
+
}
|
|
38264
|
+
return false;
|
|
38265
|
+
}
|
|
38266
|
+
|
|
38095
38267
|
// ../circle-ir/dist/analysis/passes/plaintext-password-storage-pass.js
|
|
38096
38268
|
function isWriteStorageCall(call, language) {
|
|
38097
38269
|
const method = call.method_name ?? "";
|
|
@@ -40678,6 +40850,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
40678
40850
|
pipeline.add(new InfoDisclosureStacktracePass);
|
|
40679
40851
|
if (!disabledPasses.has("unrestricted-file-upload"))
|
|
40680
40852
|
pipeline.add(new UnrestrictedFileUploadPass);
|
|
40853
|
+
if (!disabledPasses.has("missing-sanitizer-gate"))
|
|
40854
|
+
pipeline.add(new MissingSanitizerGatePass);
|
|
40681
40855
|
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
40682
40856
|
const sinkFilter = results.get("sink-filter");
|
|
40683
40857
|
const interProc = results.get("interprocedural");
|
|
@@ -41491,7 +41665,7 @@ var colors = {
|
|
|
41491
41665
|
};
|
|
41492
41666
|
|
|
41493
41667
|
// src/version.ts
|
|
41494
|
-
var version = "3.
|
|
41668
|
+
var version = "3.140.0";
|
|
41495
41669
|
|
|
41496
41670
|
// src/formatters.ts
|
|
41497
41671
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.140.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",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.140.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|