cognium-dev 3.134.0 → 3.136.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 +343 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -22643,6 +22643,12 @@ class LanguageSourcesPass {
|
|
|
22643
22643
|
for (const finding of findPythonMongoengineWhereNosqlInjectionFindings(code, graph.ir.meta.file)) {
|
|
22644
22644
|
ctx.addFinding(finding);
|
|
22645
22645
|
}
|
|
22646
|
+
for (const finding of findPythonTaintedFormatStringFindings(code, graph.ir.meta.file)) {
|
|
22647
|
+
ctx.addFinding(finding);
|
|
22648
|
+
}
|
|
22649
|
+
for (const finding of findPythonHeaderCrlfInjectionFindings(code, graph.ir.meta.file)) {
|
|
22650
|
+
ctx.addFinding(finding);
|
|
22651
|
+
}
|
|
22646
22652
|
}
|
|
22647
22653
|
if (language === "rust") {
|
|
22648
22654
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
@@ -22694,6 +22700,9 @@ class LanguageSourcesPass {
|
|
|
22694
22700
|
for (const finding of findJsIndirectEvalCodeInjectionFindings(code, graph.ir.meta.file)) {
|
|
22695
22701
|
ctx.addFinding(finding);
|
|
22696
22702
|
}
|
|
22703
|
+
for (const finding of findJsUtilFormatFormatStringFindings(code, graph.ir.meta.file)) {
|
|
22704
|
+
ctx.addFinding(finding);
|
|
22705
|
+
}
|
|
22697
22706
|
}
|
|
22698
22707
|
if (language === "java") {
|
|
22699
22708
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
@@ -22709,6 +22718,9 @@ class LanguageSourcesPass {
|
|
|
22709
22718
|
for (const finding of findJavaMongoNosqlInjectionFindings(code, graph.ir.meta.file)) {
|
|
22710
22719
|
ctx.addFinding(finding);
|
|
22711
22720
|
}
|
|
22721
|
+
for (const finding of findJavaUrlOpenStreamSsrfFindings(code, graph.ir.meta.file)) {
|
|
22722
|
+
ctx.addFinding(finding);
|
|
22723
|
+
}
|
|
22712
22724
|
}
|
|
22713
22725
|
if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
|
|
22714
22726
|
const exfilFindings = findExternalSecretExfiltrationFindings(code, graph.ir.meta.file, language);
|
|
@@ -26949,6 +26961,336 @@ function findPythonMongoengineWhereNosqlInjectionFindings(code, file) {
|
|
|
26949
26961
|
}
|
|
26950
26962
|
return findings;
|
|
26951
26963
|
}
|
|
26964
|
+
function findJavaUrlOpenStreamSsrfFindings(code, file) {
|
|
26965
|
+
const findings = [];
|
|
26966
|
+
if (typeof code !== "string" || code.length === 0)
|
|
26967
|
+
return findings;
|
|
26968
|
+
if (!/\bnew\s+URL\s*\(/.test(code) && !/\bURI\s*\.\s*create\s*\(/.test(code)) {
|
|
26969
|
+
return findings;
|
|
26970
|
+
}
|
|
26971
|
+
if (!/\.\s*(?:openStream|openConnection|getContent)\s*\(/.test(code)) {
|
|
26972
|
+
return findings;
|
|
26973
|
+
}
|
|
26974
|
+
const lines = code.split(`
|
|
26975
|
+
`);
|
|
26976
|
+
const reqExtractRe = /\b\w+\s*\.\s*(?:getParameter|getParameterValues|getHeader|getHeaders|getCookies|getReader|getQueryString|getRequestURI|getInputStream|getPart|getParts)\s*\(/;
|
|
26977
|
+
const taintedVars = new Set;
|
|
26978
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
26979
|
+
const before = taintedVars.size;
|
|
26980
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26981
|
+
const t = lines[i2].trim();
|
|
26982
|
+
const a = t.match(/^(?:final\s+)?(?:[\w<>?,\[\]]+\s+)?(\w+)\s*=\s*(.+?);?$/);
|
|
26983
|
+
if (!a)
|
|
26984
|
+
continue;
|
|
26985
|
+
const lhs = a[1];
|
|
26986
|
+
const rhs = a[2];
|
|
26987
|
+
if (taintedVars.has(lhs))
|
|
26988
|
+
continue;
|
|
26989
|
+
if (reqExtractRe.test(rhs)) {
|
|
26990
|
+
taintedVars.add(lhs);
|
|
26991
|
+
continue;
|
|
26992
|
+
}
|
|
26993
|
+
for (const v of taintedVars) {
|
|
26994
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
26995
|
+
taintedVars.add(lhs);
|
|
26996
|
+
break;
|
|
26997
|
+
}
|
|
26998
|
+
}
|
|
26999
|
+
}
|
|
27000
|
+
if (taintedVars.size === before)
|
|
27001
|
+
break;
|
|
27002
|
+
}
|
|
27003
|
+
if (taintedVars.size === 0)
|
|
27004
|
+
return findings;
|
|
27005
|
+
const sinkRe = /\.\s*(openStream|openConnection|getContent)\s*\(/;
|
|
27006
|
+
const seen = new Set;
|
|
27007
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27008
|
+
const line = lines[i2];
|
|
27009
|
+
const m = line.match(sinkRe);
|
|
27010
|
+
if (!m)
|
|
27011
|
+
continue;
|
|
27012
|
+
const op = m[1];
|
|
27013
|
+
const sinkIdx = line.indexOf(`.${op}`);
|
|
27014
|
+
if (sinkIdx < 0)
|
|
27015
|
+
continue;
|
|
27016
|
+
const head = line.substring(0, sinkIdx + 1);
|
|
27017
|
+
let tainted = reqExtractRe.test(head);
|
|
27018
|
+
if (!tainted) {
|
|
27019
|
+
for (const v of taintedVars) {
|
|
27020
|
+
if (new RegExp(`\\b${v}\\b`).test(head)) {
|
|
27021
|
+
tainted = true;
|
|
27022
|
+
break;
|
|
27023
|
+
}
|
|
27024
|
+
}
|
|
27025
|
+
}
|
|
27026
|
+
if (!tainted)
|
|
27027
|
+
continue;
|
|
27028
|
+
const key = `${i2 + 1}:${op}`;
|
|
27029
|
+
if (seen.has(key))
|
|
27030
|
+
continue;
|
|
27031
|
+
seen.add(key);
|
|
27032
|
+
findings.push({
|
|
27033
|
+
id: `ssrf-${file}-${i2 + 1}-java-url-${op.toLowerCase()}`,
|
|
27034
|
+
pass: "language-sources",
|
|
27035
|
+
category: "security",
|
|
27036
|
+
rule_id: "ssrf",
|
|
27037
|
+
cwe: "CWE-918",
|
|
27038
|
+
severity: "critical",
|
|
27039
|
+
level: "error",
|
|
27040
|
+
message: `SSRF: Java \`URL.${op}()\` invoked on a URL derived from servlet ` + 'request input. Even when an `if (url.startsWith("https://"))` ' + "guard is present, the host portion remains attacker-controlled — " + "use a strict host allowlist (parse the URL, check `getHost()`) " + "before issuing the request.",
|
|
27041
|
+
file,
|
|
27042
|
+
line: i2 + 1,
|
|
27043
|
+
snippet: line.trim()
|
|
27044
|
+
});
|
|
27045
|
+
}
|
|
27046
|
+
return findings;
|
|
27047
|
+
}
|
|
27048
|
+
function findPythonTaintedFormatStringFindings(code, file) {
|
|
27049
|
+
const findings = [];
|
|
27050
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27051
|
+
return findings;
|
|
27052
|
+
if (!/\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)|flask\.request)\b/.test(code)) {
|
|
27053
|
+
return findings;
|
|
27054
|
+
}
|
|
27055
|
+
const lines = code.split(`
|
|
27056
|
+
`);
|
|
27057
|
+
const reqExtractRe = /\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)\b|flask\.request\b)/;
|
|
27058
|
+
const taintedVars = new Set;
|
|
27059
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
27060
|
+
const before = taintedVars.size;
|
|
27061
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27062
|
+
const t = lines[i2].trim();
|
|
27063
|
+
if (t.startsWith("#"))
|
|
27064
|
+
continue;
|
|
27065
|
+
const a = t.match(/^(\w+)\s*=\s*(.+?)$/);
|
|
27066
|
+
if (!a)
|
|
27067
|
+
continue;
|
|
27068
|
+
const lhs = a[1];
|
|
27069
|
+
const rhs = a[2];
|
|
27070
|
+
if (taintedVars.has(lhs))
|
|
27071
|
+
continue;
|
|
27072
|
+
if (reqExtractRe.test(rhs)) {
|
|
27073
|
+
taintedVars.add(lhs);
|
|
27074
|
+
continue;
|
|
27075
|
+
}
|
|
27076
|
+
for (const v of taintedVars) {
|
|
27077
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
27078
|
+
taintedVars.add(lhs);
|
|
27079
|
+
break;
|
|
27080
|
+
}
|
|
27081
|
+
}
|
|
27082
|
+
}
|
|
27083
|
+
if (taintedVars.size === before)
|
|
27084
|
+
break;
|
|
27085
|
+
}
|
|
27086
|
+
if (taintedVars.size === 0)
|
|
27087
|
+
return findings;
|
|
27088
|
+
const percentRe = /\b(\w+)\s*%\s*[\(\[\{"'\w]/;
|
|
27089
|
+
const dotFormatRe = /\b(\w+)\s*\.\s*format\s*\(/;
|
|
27090
|
+
const seen = new Set;
|
|
27091
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27092
|
+
const line = lines[i2];
|
|
27093
|
+
const t = line.trim();
|
|
27094
|
+
if (t.startsWith("#"))
|
|
27095
|
+
continue;
|
|
27096
|
+
const pm = line.match(percentRe);
|
|
27097
|
+
if (pm && taintedVars.has(pm[1])) {
|
|
27098
|
+
const key = `${i2 + 1}:percent`;
|
|
27099
|
+
if (!seen.has(key)) {
|
|
27100
|
+
seen.add(key);
|
|
27101
|
+
findings.push({
|
|
27102
|
+
id: `format_string-${file}-${i2 + 1}-py-percent`,
|
|
27103
|
+
pass: "language-sources",
|
|
27104
|
+
category: "security",
|
|
27105
|
+
rule_id: "format_string",
|
|
27106
|
+
cwe: "CWE-134",
|
|
27107
|
+
severity: "high",
|
|
27108
|
+
level: "error",
|
|
27109
|
+
message: "Format-string injection: Python `<tainted> % args` uses an " + "HTTP-request-controlled format string. Malformed specifiers can " + "crash the handler (TypeError) and `%(name)s` access can reveal " + "arbitrary mapping keys. Use a literal format string and pass " + "untrusted values as arguments only.",
|
|
27110
|
+
file,
|
|
27111
|
+
line: i2 + 1,
|
|
27112
|
+
snippet: line.trim()
|
|
27113
|
+
});
|
|
27114
|
+
}
|
|
27115
|
+
}
|
|
27116
|
+
const dm = line.match(dotFormatRe);
|
|
27117
|
+
if (dm && taintedVars.has(dm[1])) {
|
|
27118
|
+
const key = `${i2 + 1}:dotformat`;
|
|
27119
|
+
if (!seen.has(key)) {
|
|
27120
|
+
seen.add(key);
|
|
27121
|
+
findings.push({
|
|
27122
|
+
id: `format_string-${file}-${i2 + 1}-py-strformat`,
|
|
27123
|
+
pass: "language-sources",
|
|
27124
|
+
category: "security",
|
|
27125
|
+
rule_id: "format_string",
|
|
27126
|
+
cwe: "CWE-134",
|
|
27127
|
+
severity: "high",
|
|
27128
|
+
level: "error",
|
|
27129
|
+
message: "Format-string injection: Python `<tainted>.format(...)` lets " + "the attacker control the format template. Field-access shapes " + "such as `{0.__class__.__init__.__globals__}` can leak module " + "globals (e.g. secret keys). Use a literal template and pass " + "untrusted values as positional/keyword arguments only.",
|
|
27130
|
+
file,
|
|
27131
|
+
line: i2 + 1,
|
|
27132
|
+
snippet: line.trim()
|
|
27133
|
+
});
|
|
27134
|
+
}
|
|
27135
|
+
}
|
|
27136
|
+
}
|
|
27137
|
+
return findings;
|
|
27138
|
+
}
|
|
27139
|
+
function findJsUtilFormatFormatStringFindings(code, file) {
|
|
27140
|
+
const findings = [];
|
|
27141
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27142
|
+
return findings;
|
|
27143
|
+
if (!/\butil\s*\.\s*format\s*\(/.test(code))
|
|
27144
|
+
return findings;
|
|
27145
|
+
if (!/\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/.test(code)) {
|
|
27146
|
+
return findings;
|
|
27147
|
+
}
|
|
27148
|
+
const lines = code.split(`
|
|
27149
|
+
`);
|
|
27150
|
+
const reqExtractRe = /\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/;
|
|
27151
|
+
const taintedVars = new Set;
|
|
27152
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
27153
|
+
const before = taintedVars.size;
|
|
27154
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27155
|
+
const t = lines[i2].trim();
|
|
27156
|
+
if (t.startsWith("//"))
|
|
27157
|
+
continue;
|
|
27158
|
+
const a = t.match(/^(?:const|let|var)\s+(\w+)\s*=\s*(.+?);?$/);
|
|
27159
|
+
if (!a)
|
|
27160
|
+
continue;
|
|
27161
|
+
const lhs = a[1];
|
|
27162
|
+
const rhs = a[2];
|
|
27163
|
+
if (taintedVars.has(lhs))
|
|
27164
|
+
continue;
|
|
27165
|
+
if (reqExtractRe.test(rhs)) {
|
|
27166
|
+
taintedVars.add(lhs);
|
|
27167
|
+
continue;
|
|
27168
|
+
}
|
|
27169
|
+
for (const v of taintedVars) {
|
|
27170
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
27171
|
+
taintedVars.add(lhs);
|
|
27172
|
+
break;
|
|
27173
|
+
}
|
|
27174
|
+
}
|
|
27175
|
+
}
|
|
27176
|
+
if (taintedVars.size === before)
|
|
27177
|
+
break;
|
|
27178
|
+
}
|
|
27179
|
+
if (taintedVars.size === 0)
|
|
27180
|
+
return findings;
|
|
27181
|
+
const callRe = /\butil\s*\.\s*format\s*\(\s*([\w.]+)\s*[,)]/;
|
|
27182
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27183
|
+
const line = lines[i2];
|
|
27184
|
+
const m = line.match(callRe);
|
|
27185
|
+
if (!m)
|
|
27186
|
+
continue;
|
|
27187
|
+
const arg = m[1];
|
|
27188
|
+
const root = arg.split(".")[0];
|
|
27189
|
+
if (!taintedVars.has(root) && !reqExtractRe.test(arg))
|
|
27190
|
+
continue;
|
|
27191
|
+
findings.push({
|
|
27192
|
+
id: `format_string-${file}-${i2 + 1}-js-util-format`,
|
|
27193
|
+
pass: "language-sources",
|
|
27194
|
+
category: "security",
|
|
27195
|
+
rule_id: "format_string",
|
|
27196
|
+
cwe: "CWE-134",
|
|
27197
|
+
severity: "medium",
|
|
27198
|
+
level: "error",
|
|
27199
|
+
message: "Format-string injection: Node `util.format(<tainted>, ...)` uses " + "an HTTP-request-controlled format string. The attacker can " + "manipulate `%s`/`%j`/`%O` specifiers to alter the rendered " + "output. Pass user input as a subsequent argument with a literal " + "format string.",
|
|
27200
|
+
file,
|
|
27201
|
+
line: i2 + 1,
|
|
27202
|
+
snippet: line.trim()
|
|
27203
|
+
});
|
|
27204
|
+
}
|
|
27205
|
+
return findings;
|
|
27206
|
+
}
|
|
27207
|
+
function findPythonHeaderCrlfInjectionFindings(code, file) {
|
|
27208
|
+
const findings = [];
|
|
27209
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27210
|
+
return findings;
|
|
27211
|
+
if (!/\b\w+\s*\.\s*headers\b/.test(code))
|
|
27212
|
+
return findings;
|
|
27213
|
+
if (!/\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)|flask\.request)\b/.test(code)) {
|
|
27214
|
+
return findings;
|
|
27215
|
+
}
|
|
27216
|
+
const lines = code.split(`
|
|
27217
|
+
`);
|
|
27218
|
+
const reqExtractRe = /\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)\b|flask\.request\b)/;
|
|
27219
|
+
const taintedVars = new Set;
|
|
27220
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
27221
|
+
const before = taintedVars.size;
|
|
27222
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27223
|
+
const t = lines[i2].trim();
|
|
27224
|
+
if (t.startsWith("#"))
|
|
27225
|
+
continue;
|
|
27226
|
+
const a = t.match(/^(\w+)\s*=\s*(.+?)$/);
|
|
27227
|
+
if (!a)
|
|
27228
|
+
continue;
|
|
27229
|
+
const lhs = a[1];
|
|
27230
|
+
const rhs = a[2];
|
|
27231
|
+
if (taintedVars.has(lhs))
|
|
27232
|
+
continue;
|
|
27233
|
+
if (reqExtractRe.test(rhs)) {
|
|
27234
|
+
taintedVars.add(lhs);
|
|
27235
|
+
continue;
|
|
27236
|
+
}
|
|
27237
|
+
for (const v of taintedVars) {
|
|
27238
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
27239
|
+
taintedVars.add(lhs);
|
|
27240
|
+
break;
|
|
27241
|
+
}
|
|
27242
|
+
}
|
|
27243
|
+
}
|
|
27244
|
+
if (taintedVars.size === before)
|
|
27245
|
+
break;
|
|
27246
|
+
}
|
|
27247
|
+
if (taintedVars.size === 0)
|
|
27248
|
+
return findings;
|
|
27249
|
+
const subscriptRe = /\b\w+\s*\.\s*headers\s*\[\s*['"][^'"]+['"]\s*\]\s*=\s*(.+)$/;
|
|
27250
|
+
const methodRe = /\b\w+\s*\.\s*headers\s*\.\s*(?:add|set|setdefault|append)\s*\(\s*['"][^'"]+['"]\s*,\s*(.+?)\)/;
|
|
27251
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27252
|
+
const line = lines[i2];
|
|
27253
|
+
const t = line.trim();
|
|
27254
|
+
if (t.startsWith("#"))
|
|
27255
|
+
continue;
|
|
27256
|
+
let expr = null;
|
|
27257
|
+
const sm = line.match(subscriptRe);
|
|
27258
|
+
if (sm)
|
|
27259
|
+
expr = sm[1];
|
|
27260
|
+
else {
|
|
27261
|
+
const mm = line.match(methodRe);
|
|
27262
|
+
if (mm)
|
|
27263
|
+
expr = mm[1];
|
|
27264
|
+
}
|
|
27265
|
+
if (!expr)
|
|
27266
|
+
continue;
|
|
27267
|
+
let tainted = reqExtractRe.test(expr);
|
|
27268
|
+
if (!tainted) {
|
|
27269
|
+
for (const v of taintedVars) {
|
|
27270
|
+
if (new RegExp(`\\b${v}\\b`).test(expr)) {
|
|
27271
|
+
tainted = true;
|
|
27272
|
+
break;
|
|
27273
|
+
}
|
|
27274
|
+
}
|
|
27275
|
+
}
|
|
27276
|
+
if (!tainted)
|
|
27277
|
+
continue;
|
|
27278
|
+
findings.push({
|
|
27279
|
+
id: `crlf-${file}-${i2 + 1}-py-headers`,
|
|
27280
|
+
pass: "language-sources",
|
|
27281
|
+
category: "security",
|
|
27282
|
+
rule_id: "crlf",
|
|
27283
|
+
cwe: "CWE-113",
|
|
27284
|
+
severity: "medium",
|
|
27285
|
+
level: "error",
|
|
27286
|
+
message: "CRLF / header injection: Python Flask/Werkzeug " + "`response.headers[...] = <tainted>` lets an attacker inject a " + "`\\r\\n` sequence and forge additional headers or split the " + "response body. Validate the value (reject control characters) or " + "use a fixed allowlist.",
|
|
27287
|
+
file,
|
|
27288
|
+
line: i2 + 1,
|
|
27289
|
+
snippet: line.trim()
|
|
27290
|
+
});
|
|
27291
|
+
}
|
|
27292
|
+
return findings;
|
|
27293
|
+
}
|
|
26952
27294
|
|
|
26953
27295
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
26954
27296
|
var JS_XSS_SANITIZERS = [
|
|
@@ -40076,7 +40418,7 @@ var colors = {
|
|
|
40076
40418
|
};
|
|
40077
40419
|
|
|
40078
40420
|
// src/version.ts
|
|
40079
|
-
var version = "3.
|
|
40421
|
+
var version = "3.136.0";
|
|
40080
40422
|
|
|
40081
40423
|
// src/formatters.ts
|
|
40082
40424
|
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.136.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.136.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|