cognium-dev 3.128.0 → 3.131.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 +763 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -22600,6 +22600,13 @@ class LanguageSourcesPass {
|
|
|
22600
22600
|
if (language === "go") {
|
|
22601
22601
|
additionalSanitizers.push(...findGoMapAllowlistGuardSanitizers(code));
|
|
22602
22602
|
additionalSanitizers.push(...findGoHtmlTemplateImportSanitizers(code));
|
|
22603
|
+
const goMisconfigFindings = findGoPatternFindings(code, graph.ir.meta.file);
|
|
22604
|
+
for (const finding of goMisconfigFindings) {
|
|
22605
|
+
ctx.addFinding(finding);
|
|
22606
|
+
}
|
|
22607
|
+
for (const finding of findGoXssFindings(code, graph.ir.meta.file)) {
|
|
22608
|
+
ctx.addFinding(finding);
|
|
22609
|
+
}
|
|
22603
22610
|
}
|
|
22604
22611
|
if (language === "python") {
|
|
22605
22612
|
additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
|
|
@@ -22612,6 +22619,12 @@ class LanguageSourcesPass {
|
|
|
22612
22619
|
for (const finding of pyMisconfigFindings) {
|
|
22613
22620
|
ctx.addFinding(finding);
|
|
22614
22621
|
}
|
|
22622
|
+
for (const finding of findPythonFlaskStringConcatXssFindings(code, graph.ir.meta.file)) {
|
|
22623
|
+
ctx.addFinding(finding);
|
|
22624
|
+
}
|
|
22625
|
+
for (const finding of findPythonJinjaMarkupXssFindings(code, graph.ir.meta.file)) {
|
|
22626
|
+
ctx.addFinding(finding);
|
|
22627
|
+
}
|
|
22615
22628
|
}
|
|
22616
22629
|
if (language === "rust") {
|
|
22617
22630
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
@@ -22621,6 +22634,18 @@ class LanguageSourcesPass {
|
|
|
22621
22634
|
for (const finding of rustMisconfigFindings) {
|
|
22622
22635
|
ctx.addFinding(finding);
|
|
22623
22636
|
}
|
|
22637
|
+
for (const finding of findRustHardcodedCredentialFindings(code, graph.ir.meta.file)) {
|
|
22638
|
+
ctx.addFinding(finding);
|
|
22639
|
+
}
|
|
22640
|
+
for (const finding of findRustInsecureCookieFindings(code, graph.ir.meta.file)) {
|
|
22641
|
+
ctx.addFinding(finding);
|
|
22642
|
+
}
|
|
22643
|
+
for (const finding of findRustJwtVerifyDisabledFindings(code, graph.ir.meta.file)) {
|
|
22644
|
+
ctx.addFinding(finding);
|
|
22645
|
+
}
|
|
22646
|
+
for (const finding of findRustWeakCryptoEcbFindings(code, graph.ir.meta.file)) {
|
|
22647
|
+
ctx.addFinding(finding);
|
|
22648
|
+
}
|
|
22624
22649
|
}
|
|
22625
22650
|
if (language === "javascript" || language === "typescript" || language === "htmljs") {
|
|
22626
22651
|
additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
|
|
@@ -22628,12 +22653,29 @@ class LanguageSourcesPass {
|
|
|
22628
22653
|
additionalSanitizers.push(...findJsCsvFormulaPrefixSanitizers(code));
|
|
22629
22654
|
additionalSanitizers.push(...findJsWrapperFunctionSanitizers(code));
|
|
22630
22655
|
additionalSanitizers.push(...findJsSsrfAllowlistGuardSanitizers(code));
|
|
22656
|
+
additionalSanitizers.push(...findJsArgvFormExecSanitizers(code));
|
|
22657
|
+
additionalSanitizers.push(...findJsParameterizedSqlSanitizers(code));
|
|
22658
|
+
for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
|
|
22659
|
+
ctx.addFinding(finding);
|
|
22660
|
+
}
|
|
22661
|
+
for (const finding of findJsVueVHtmlXssFindings(code, graph.ir.meta.file)) {
|
|
22662
|
+
ctx.addFinding(finding);
|
|
22663
|
+
}
|
|
22664
|
+
for (const finding of findTsAngularBypassXssFindings(code, graph.ir.meta.file)) {
|
|
22665
|
+
ctx.addFinding(finding);
|
|
22666
|
+
}
|
|
22631
22667
|
}
|
|
22632
22668
|
if (language === "java") {
|
|
22633
22669
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
22634
22670
|
additionalSanitizers.push(...findJavaPathNormalizeStartsWithGuardSanitizers(code));
|
|
22635
22671
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
22636
22672
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
22673
|
+
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
22674
|
+
ctx.addFinding(finding);
|
|
22675
|
+
}
|
|
22676
|
+
for (const finding of findJavaResponseWriterXssFindings(code, graph.ir.meta.file)) {
|
|
22677
|
+
ctx.addFinding(finding);
|
|
22678
|
+
}
|
|
22637
22679
|
}
|
|
22638
22680
|
if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
|
|
22639
22681
|
const exfilFindings = findExternalSecretExfiltrationFindings(code, graph.ir.meta.file, language);
|
|
@@ -24585,6 +24627,64 @@ function findJsSsrfAllowlistGuardSanitizers(code) {
|
|
|
24585
24627
|
}
|
|
24586
24628
|
return sanitizers;
|
|
24587
24629
|
}
|
|
24630
|
+
function findJsArgvFormExecSanitizers(code) {
|
|
24631
|
+
const sanitizers = [];
|
|
24632
|
+
const lines = code.split(`
|
|
24633
|
+
`);
|
|
24634
|
+
const argvExecRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*(?:'[^']*'|"[^"]*"|`[^`]*`)\s*,\s*\[/;
|
|
24635
|
+
const shellArgvRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*['"`](?:[\w./-]*\/)?(?:sh|bash|zsh|ksh|dash|cmd(?:\.exe)?|powershell|pwsh)['"`]\s*,\s*\[\s*['"`]-c['"`]/;
|
|
24636
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
24637
|
+
const text = lines[i2];
|
|
24638
|
+
if (!argvExecRe.test(text))
|
|
24639
|
+
continue;
|
|
24640
|
+
if (shellArgvRe.test(text))
|
|
24641
|
+
continue;
|
|
24642
|
+
sanitizers.push({
|
|
24643
|
+
type: "js_argv_form_exec",
|
|
24644
|
+
method: "execFile",
|
|
24645
|
+
line: i2 + 1,
|
|
24646
|
+
sanitizes: ["command_injection", "external_taint_escape"]
|
|
24647
|
+
});
|
|
24648
|
+
}
|
|
24649
|
+
return sanitizers;
|
|
24650
|
+
}
|
|
24651
|
+
function findJsParameterizedSqlSanitizers(code) {
|
|
24652
|
+
const sanitizers = [];
|
|
24653
|
+
const lines = code.split(`
|
|
24654
|
+
`);
|
|
24655
|
+
const singleRe = /\.\s*(?:query|execute)\s*\(\s*'([^'\\]*(?:\\.[^'\\]*)*)'\s*,\s*\[/;
|
|
24656
|
+
const doubleRe = /\.\s*(?:query|execute)\s*\(\s*"([^"\\]*(?:\\.[^"\\]*)*)"\s*,\s*\[/;
|
|
24657
|
+
const tickRe = /\.\s*(?:query|execute)\s*\(\s*`([^`]*)`\s*,\s*\[/;
|
|
24658
|
+
const placeholderRe = /(?:\$\d+|\?)/;
|
|
24659
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
24660
|
+
const text = lines[i2];
|
|
24661
|
+
let sql = null;
|
|
24662
|
+
const s = singleRe.exec(text);
|
|
24663
|
+
if (s)
|
|
24664
|
+
sql = s[1];
|
|
24665
|
+
if (sql == null) {
|
|
24666
|
+
const d = doubleRe.exec(text);
|
|
24667
|
+
if (d)
|
|
24668
|
+
sql = d[1];
|
|
24669
|
+
}
|
|
24670
|
+
if (sql == null) {
|
|
24671
|
+
const t = tickRe.exec(text);
|
|
24672
|
+
if (t && !/\$\{/.test(t[1]))
|
|
24673
|
+
sql = t[1];
|
|
24674
|
+
}
|
|
24675
|
+
if (sql == null)
|
|
24676
|
+
continue;
|
|
24677
|
+
if (!placeholderRe.test(sql))
|
|
24678
|
+
continue;
|
|
24679
|
+
sanitizers.push({
|
|
24680
|
+
type: "js_parameterized_sql",
|
|
24681
|
+
method: "query",
|
|
24682
|
+
line: i2 + 1,
|
|
24683
|
+
sanitizes: ["sql_injection", "external_taint_escape"]
|
|
24684
|
+
});
|
|
24685
|
+
}
|
|
24686
|
+
return sanitizers;
|
|
24687
|
+
}
|
|
24588
24688
|
function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
|
|
24589
24689
|
const sanitizers = [];
|
|
24590
24690
|
const lines = code.split(`
|
|
@@ -25145,6 +25245,668 @@ function findRustPatternFindings(code, file) {
|
|
|
25145
25245
|
}
|
|
25146
25246
|
return out2;
|
|
25147
25247
|
}
|
|
25248
|
+
function findRustHardcodedCredentialFindings(code, file) {
|
|
25249
|
+
const out2 = [];
|
|
25250
|
+
const lines = code.split(`
|
|
25251
|
+
`);
|
|
25252
|
+
const re = /\b(?:pub\s+)?(?:const|static)\s+([A-Z][A-Z0-9_]*)\s*:\s*&\s*'?[a-z_]*\s*str\s*=\s*"([^"]+)"/;
|
|
25253
|
+
const nameRe = /(?:^|_)(?:API[_]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|AUTH)(?:_|$)/i;
|
|
25254
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25255
|
+
const raw = lines[i2];
|
|
25256
|
+
const trimmed = raw.trim();
|
|
25257
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
25258
|
+
continue;
|
|
25259
|
+
const m = trimmed.match(re);
|
|
25260
|
+
if (!m)
|
|
25261
|
+
continue;
|
|
25262
|
+
const name2 = m[1];
|
|
25263
|
+
const value = m[2];
|
|
25264
|
+
if (!nameRe.test(name2))
|
|
25265
|
+
continue;
|
|
25266
|
+
if (value.length < 8)
|
|
25267
|
+
continue;
|
|
25268
|
+
if (/^(?:xxx|todo|fixme|placeholder|changeme)/i.test(value))
|
|
25269
|
+
continue;
|
|
25270
|
+
out2.push({
|
|
25271
|
+
id: `hardcoded-credential-${file}-${i2 + 1}`,
|
|
25272
|
+
pass: "language-sources",
|
|
25273
|
+
category: "security",
|
|
25274
|
+
rule_id: "hardcoded-credential",
|
|
25275
|
+
cwe: "CWE-798",
|
|
25276
|
+
severity: "high",
|
|
25277
|
+
level: "error",
|
|
25278
|
+
message: `Hardcoded credential: const ${name2} contains a literal secret value`,
|
|
25279
|
+
file,
|
|
25280
|
+
line: i2 + 1,
|
|
25281
|
+
snippet: trimmed.substring(0, 100)
|
|
25282
|
+
});
|
|
25283
|
+
}
|
|
25284
|
+
return out2;
|
|
25285
|
+
}
|
|
25286
|
+
function findRustInsecureCookieFindings(code, file) {
|
|
25287
|
+
const out2 = [];
|
|
25288
|
+
const lines = code.split(`
|
|
25289
|
+
`);
|
|
25290
|
+
const builderRe = /\bCookie\s*::\s*build\s*\(/;
|
|
25291
|
+
const insecureFlagRe = /\.\s*(?:secure|http_only)\s*\(\s*false\s*\)/;
|
|
25292
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25293
|
+
const raw = lines[i2];
|
|
25294
|
+
const trimmed = raw.trim();
|
|
25295
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
25296
|
+
continue;
|
|
25297
|
+
if (!builderRe.test(trimmed))
|
|
25298
|
+
continue;
|
|
25299
|
+
if (!insecureFlagRe.test(trimmed))
|
|
25300
|
+
continue;
|
|
25301
|
+
out2.push({
|
|
25302
|
+
id: `insecure-cookie-${file}-${i2 + 1}`,
|
|
25303
|
+
pass: "language-sources",
|
|
25304
|
+
category: "security",
|
|
25305
|
+
rule_id: "insecure-cookie",
|
|
25306
|
+
cwe: "CWE-1004",
|
|
25307
|
+
severity: "medium",
|
|
25308
|
+
level: "warning",
|
|
25309
|
+
message: "Insecure cookie: Cookie::build chain disables Secure / HttpOnly flag(s)",
|
|
25310
|
+
file,
|
|
25311
|
+
line: i2 + 1,
|
|
25312
|
+
snippet: trimmed.substring(0, 100)
|
|
25313
|
+
});
|
|
25314
|
+
}
|
|
25315
|
+
return out2;
|
|
25316
|
+
}
|
|
25317
|
+
function findRustJwtVerifyDisabledFindings(code, file) {
|
|
25318
|
+
const out2 = [];
|
|
25319
|
+
const lines = code.split(`
|
|
25320
|
+
`);
|
|
25321
|
+
const re = /\.\s*insecure_disable_signature_validation\s*\(/;
|
|
25322
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25323
|
+
const raw = lines[i2];
|
|
25324
|
+
const trimmed = raw.trim();
|
|
25325
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
25326
|
+
continue;
|
|
25327
|
+
if (!re.test(trimmed))
|
|
25328
|
+
continue;
|
|
25329
|
+
out2.push({
|
|
25330
|
+
id: `jwt-verify-disabled-${file}-${i2 + 1}`,
|
|
25331
|
+
pass: "language-sources",
|
|
25332
|
+
category: "security",
|
|
25333
|
+
rule_id: "jwt-verify-disabled",
|
|
25334
|
+
cwe: "CWE-347",
|
|
25335
|
+
severity: "critical",
|
|
25336
|
+
level: "error",
|
|
25337
|
+
message: "JWT signature verification disabled: " + "Validation::insecure_disable_signature_validation() forfeits signature enforcement",
|
|
25338
|
+
file,
|
|
25339
|
+
line: i2 + 1,
|
|
25340
|
+
snippet: trimmed.substring(0, 100)
|
|
25341
|
+
});
|
|
25342
|
+
}
|
|
25343
|
+
return out2;
|
|
25344
|
+
}
|
|
25345
|
+
function findRustWeakCryptoEcbFindings(code, file) {
|
|
25346
|
+
const out2 = [];
|
|
25347
|
+
const lines = code.split(`
|
|
25348
|
+
`);
|
|
25349
|
+
const ctorRe = /\bAes(?:128|192|256)(?:Ecb)?\s*::\s*new\s*\(/;
|
|
25350
|
+
const blockOpRe = /\.\s*(encrypt_block|decrypt_block)\s*\(/;
|
|
25351
|
+
let sawCtor = false;
|
|
25352
|
+
for (const line of lines) {
|
|
25353
|
+
if (ctorRe.test(line)) {
|
|
25354
|
+
sawCtor = true;
|
|
25355
|
+
break;
|
|
25356
|
+
}
|
|
25357
|
+
}
|
|
25358
|
+
if (!sawCtor)
|
|
25359
|
+
return out2;
|
|
25360
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25361
|
+
const raw = lines[i2];
|
|
25362
|
+
const trimmed = raw.trim();
|
|
25363
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
25364
|
+
continue;
|
|
25365
|
+
if (!blockOpRe.test(trimmed))
|
|
25366
|
+
continue;
|
|
25367
|
+
out2.push({
|
|
25368
|
+
id: `weak-crypto-${file}-${i2 + 1}`,
|
|
25369
|
+
pass: "language-sources",
|
|
25370
|
+
category: "security",
|
|
25371
|
+
rule_id: "weak-crypto",
|
|
25372
|
+
cwe: "CWE-327",
|
|
25373
|
+
severity: "high",
|
|
25374
|
+
level: "error",
|
|
25375
|
+
message: "Weak crypto (ECB mode): raw Aes::encrypt_block/decrypt_block " + "leaks repeating-block patterns. Use AES-GCM, AES-CTR, or " + "AES-CBC with an HMAC.",
|
|
25376
|
+
file,
|
|
25377
|
+
line: i2 + 1,
|
|
25378
|
+
snippet: trimmed.substring(0, 100)
|
|
25379
|
+
});
|
|
25380
|
+
}
|
|
25381
|
+
return out2;
|
|
25382
|
+
}
|
|
25383
|
+
function findJavaPatternFindings(code, file) {
|
|
25384
|
+
const out2 = [];
|
|
25385
|
+
const lines = code.split(`
|
|
25386
|
+
`);
|
|
25387
|
+
const jwtDecodeRe = /\bJWT\s*\.\s*decode\s*\(/;
|
|
25388
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25389
|
+
const raw = lines[i2];
|
|
25390
|
+
const trimmed = raw.trim();
|
|
25391
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
25392
|
+
continue;
|
|
25393
|
+
if (!jwtDecodeRe.test(trimmed))
|
|
25394
|
+
continue;
|
|
25395
|
+
if (/\.\s*verify\s*\(/.test(trimmed))
|
|
25396
|
+
continue;
|
|
25397
|
+
out2.push({
|
|
25398
|
+
id: `jwt-verify-disabled-${file}-${i2 + 1}-decode`,
|
|
25399
|
+
pass: "language-sources",
|
|
25400
|
+
category: "security",
|
|
25401
|
+
rule_id: "jwt-verify-disabled",
|
|
25402
|
+
cwe: "CWE-347",
|
|
25403
|
+
severity: "critical",
|
|
25404
|
+
level: "error",
|
|
25405
|
+
message: "JWT signature not verified: auth0 `JWT.decode(token)` parses " + "without checking the signature. Use `JWT.require(<algorithm>)" + ".build().verify(token)` to enforce verification.",
|
|
25406
|
+
file,
|
|
25407
|
+
line: i2 + 1,
|
|
25408
|
+
snippet: trimmed.substring(0, 100)
|
|
25409
|
+
});
|
|
25410
|
+
}
|
|
25411
|
+
const anonStartRe = /\bnew\s+X509TrustManager\s*\(\s*\)\s*\{/;
|
|
25412
|
+
const checkServerSig = /\bcheckServerTrusted\s*\([^)]*\)\s*(?:throws\s+[^\{]*)?\{\s*\}/;
|
|
25413
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25414
|
+
const raw = lines[i2];
|
|
25415
|
+
if (!anonStartRe.test(raw))
|
|
25416
|
+
continue;
|
|
25417
|
+
const end = Math.min(lines.length, i2 + 16);
|
|
25418
|
+
let foundAt = -1;
|
|
25419
|
+
for (let j = i2;j < end; j++) {
|
|
25420
|
+
if (checkServerSig.test(lines[j])) {
|
|
25421
|
+
foundAt = j;
|
|
25422
|
+
break;
|
|
25423
|
+
}
|
|
25424
|
+
}
|
|
25425
|
+
if (foundAt < 0)
|
|
25426
|
+
continue;
|
|
25427
|
+
out2.push({
|
|
25428
|
+
id: `tls-verify-disabled-${file}-${foundAt + 1}`,
|
|
25429
|
+
pass: "language-sources",
|
|
25430
|
+
category: "security",
|
|
25431
|
+
rule_id: "tls-verify-disabled",
|
|
25432
|
+
cwe: "CWE-295",
|
|
25433
|
+
severity: "high",
|
|
25434
|
+
level: "error",
|
|
25435
|
+
message: "TLS certificate verification disabled: anonymous X509TrustManager " + "with empty checkServerTrusted body accepts every certificate.",
|
|
25436
|
+
file,
|
|
25437
|
+
line: foundAt + 1,
|
|
25438
|
+
snippet: lines[foundAt].trim().substring(0, 100)
|
|
25439
|
+
});
|
|
25440
|
+
}
|
|
25441
|
+
return out2;
|
|
25442
|
+
}
|
|
25443
|
+
function findGoPatternFindings(code, file) {
|
|
25444
|
+
const out2 = [];
|
|
25445
|
+
const lines = code.split(`
|
|
25446
|
+
`);
|
|
25447
|
+
const cipherVars = new Set;
|
|
25448
|
+
const ctorRe = /\b([a-zA-Z_]\w*)\s*(?:,\s*[a-zA-Z_]\w*)?\s*:?=\s*aes\.NewCipher\s*\(/;
|
|
25449
|
+
for (const line of lines) {
|
|
25450
|
+
const m = line.match(ctorRe);
|
|
25451
|
+
if (m)
|
|
25452
|
+
cipherVars.add(m[1]);
|
|
25453
|
+
}
|
|
25454
|
+
if (cipherVars.size === 0)
|
|
25455
|
+
return out2;
|
|
25456
|
+
for (const v of Array.from(cipherVars)) {
|
|
25457
|
+
const wrapRe = new RegExp(`\\bcipher\\.New(?:GCM|CBCEncrypter|CBCDecrypter|CTR|OFB|CFBEncrypter|CFBDecrypter)\\s*\\(\\s*${v}\\b`);
|
|
25458
|
+
for (const line of lines) {
|
|
25459
|
+
if (wrapRe.test(line)) {
|
|
25460
|
+
cipherVars.delete(v);
|
|
25461
|
+
break;
|
|
25462
|
+
}
|
|
25463
|
+
}
|
|
25464
|
+
}
|
|
25465
|
+
if (cipherVars.size === 0)
|
|
25466
|
+
return out2;
|
|
25467
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25468
|
+
const raw = lines[i2];
|
|
25469
|
+
const trimmed = raw.trim();
|
|
25470
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
25471
|
+
continue;
|
|
25472
|
+
for (const v of cipherVars) {
|
|
25473
|
+
const opRe = new RegExp(`\\b${v}\\s*\\.\\s*(?:Encrypt|Decrypt)\\s*\\(`);
|
|
25474
|
+
if (!opRe.test(trimmed))
|
|
25475
|
+
continue;
|
|
25476
|
+
out2.push({
|
|
25477
|
+
id: `weak-crypto-${file}-${i2 + 1}`,
|
|
25478
|
+
pass: "language-sources",
|
|
25479
|
+
category: "security",
|
|
25480
|
+
rule_id: "weak-crypto",
|
|
25481
|
+
cwe: "CWE-327",
|
|
25482
|
+
severity: "high",
|
|
25483
|
+
level: "error",
|
|
25484
|
+
message: "Weak crypto (ECB mode): raw aes.Cipher.Encrypt/Decrypt on a " + "block leaks repeating-block patterns. Wrap with cipher.NewGCM, " + "cipher.NewCTR, or cipher.NewCBCEncrypter + HMAC.",
|
|
25485
|
+
file,
|
|
25486
|
+
line: i2 + 1,
|
|
25487
|
+
snippet: trimmed.substring(0, 100)
|
|
25488
|
+
});
|
|
25489
|
+
break;
|
|
25490
|
+
}
|
|
25491
|
+
}
|
|
25492
|
+
return out2;
|
|
25493
|
+
}
|
|
25494
|
+
function findJsPatternFindings(code, file) {
|
|
25495
|
+
const out2 = [];
|
|
25496
|
+
const lines = code.split(`
|
|
25497
|
+
`);
|
|
25498
|
+
const parseRe = /\blibxml(?:js)?\s*\.\s*parseXml(?:String)?\s*\(/;
|
|
25499
|
+
const noentTrueRe = /\bnoent\s*:\s*true\b/;
|
|
25500
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25501
|
+
const raw = lines[i2];
|
|
25502
|
+
const trimmed = raw.trim();
|
|
25503
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
25504
|
+
continue;
|
|
25505
|
+
if (!parseRe.test(trimmed))
|
|
25506
|
+
continue;
|
|
25507
|
+
if (!noentTrueRe.test(trimmed))
|
|
25508
|
+
continue;
|
|
25509
|
+
out2.push({
|
|
25510
|
+
id: `xml-entity-expansion-${file}-${i2 + 1}`,
|
|
25511
|
+
pass: "language-sources",
|
|
25512
|
+
category: "security",
|
|
25513
|
+
rule_id: "xml-entity-expansion",
|
|
25514
|
+
cwe: "CWE-611",
|
|
25515
|
+
severity: "high",
|
|
25516
|
+
level: "error",
|
|
25517
|
+
message: "XML external entity resolution enabled: libxmljs parseXml " + "called with `noent: true` resolves external entities (XXE / " + "billion-laughs). Omit the flag or set `noent: false`.",
|
|
25518
|
+
file,
|
|
25519
|
+
line: i2 + 1,
|
|
25520
|
+
snippet: trimmed.substring(0, 100)
|
|
25521
|
+
});
|
|
25522
|
+
}
|
|
25523
|
+
return out2;
|
|
25524
|
+
}
|
|
25525
|
+
function findGoXssFindings(code, file) {
|
|
25526
|
+
const out2 = [];
|
|
25527
|
+
const lines = code.split(`
|
|
25528
|
+
`);
|
|
25529
|
+
const rwNames = new Set;
|
|
25530
|
+
const sigRe = /\(\s*([A-Za-z_]\w*)\s+http\.ResponseWriter\b/g;
|
|
25531
|
+
for (const line of lines) {
|
|
25532
|
+
let m;
|
|
25533
|
+
sigRe.lastIndex = 0;
|
|
25534
|
+
while ((m = sigRe.exec(line)) !== null)
|
|
25535
|
+
rwNames.add(m[1]);
|
|
25536
|
+
}
|
|
25537
|
+
if (rwNames.size === 0)
|
|
25538
|
+
return out2;
|
|
25539
|
+
const escaperRe = /\b(?:html|template)\.(?:EscapeString|HTMLEscapeString|HTMLEscaper|JSEscapeString|URLQueryEscaper)\s*\(/;
|
|
25540
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25541
|
+
const raw = lines[i2];
|
|
25542
|
+
const trimmed = raw.trim();
|
|
25543
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
25544
|
+
continue;
|
|
25545
|
+
const callRe = /\bfmt\.Fprint(?:f|ln)?\s*\(\s*([A-Za-z_]\w*)\s*,\s*([\s\S]*)\)\s*(?:\/\/.*)?$/;
|
|
25546
|
+
const m = trimmed.match(callRe);
|
|
25547
|
+
if (!m)
|
|
25548
|
+
continue;
|
|
25549
|
+
if (!rwNames.has(m[1]))
|
|
25550
|
+
continue;
|
|
25551
|
+
const tail = m[2];
|
|
25552
|
+
if (escaperRe.test(tail))
|
|
25553
|
+
continue;
|
|
25554
|
+
if (!/[A-Za-z_]\w*/.test(tail.replace(/"[^"]*"|`[^`]*`/g, "")))
|
|
25555
|
+
continue;
|
|
25556
|
+
out2.push({
|
|
25557
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
25558
|
+
pass: "language-sources",
|
|
25559
|
+
category: "security",
|
|
25560
|
+
rule_id: "xss",
|
|
25561
|
+
cwe: "CWE-79",
|
|
25562
|
+
severity: "high",
|
|
25563
|
+
level: "error",
|
|
25564
|
+
message: "Reflected XSS: fmt.Fprint writes data to http.ResponseWriter " + "without HTML escaping. Wrap user-controlled args with " + "html.EscapeString / template.HTMLEscapeString.",
|
|
25565
|
+
file,
|
|
25566
|
+
line: i2 + 1,
|
|
25567
|
+
snippet: trimmed.substring(0, 100)
|
|
25568
|
+
});
|
|
25569
|
+
}
|
|
25570
|
+
return out2;
|
|
25571
|
+
}
|
|
25572
|
+
function findJavaResponseWriterXssFindings(code, file) {
|
|
25573
|
+
const out2 = [];
|
|
25574
|
+
const lines = code.split(`
|
|
25575
|
+
`);
|
|
25576
|
+
const respNames = new Set;
|
|
25577
|
+
const sigRe = /\bHttpServletResponse\s+([A-Za-z_]\w*)\b/g;
|
|
25578
|
+
for (const line of lines) {
|
|
25579
|
+
let m;
|
|
25580
|
+
sigRe.lastIndex = 0;
|
|
25581
|
+
while ((m = sigRe.exec(line)) !== null)
|
|
25582
|
+
respNames.add(m[1]);
|
|
25583
|
+
}
|
|
25584
|
+
if (respNames.size === 0)
|
|
25585
|
+
return out2;
|
|
25586
|
+
const safeWrapRe = /\b(?:Encode\.(?:forHtml|forHtmlAttribute|forHtmlContent|forJavaScript)|StringEscapeUtils\.escape(?:Html3|Html4|EcmaScript|Xml)|HtmlUtils\.htmlEscape(?:Decimal|Hex)?|Escaper\.escapeHtml|HtmlEscapers\.(?:escapeHtml|htmlEscaper)|Encoder\.encodeForHTML(?:Attribute)?|Jsoup\.clean)\s*\(/;
|
|
25587
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25588
|
+
const raw = lines[i2];
|
|
25589
|
+
const trimmed = raw.trim();
|
|
25590
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
25591
|
+
continue;
|
|
25592
|
+
const chainRe = /\b([A-Za-z_]\w*)\s*\.\s*getWriter\s*\(\s*\)\s*\.\s*(print|println|write|printf|format|append)\s*\(([\s\S]*)\)\s*;?\s*(?:\/\/.*)?$/;
|
|
25593
|
+
const m = trimmed.match(chainRe);
|
|
25594
|
+
if (!m)
|
|
25595
|
+
continue;
|
|
25596
|
+
const recv = m[1];
|
|
25597
|
+
if (!respNames.has(recv))
|
|
25598
|
+
continue;
|
|
25599
|
+
const args2 = m[3];
|
|
25600
|
+
if (safeWrapRe.test(args2))
|
|
25601
|
+
continue;
|
|
25602
|
+
const argsTrim = args2.trim();
|
|
25603
|
+
if (/^"(?:\\.|[^"\\])*"$/.test(argsTrim))
|
|
25604
|
+
continue;
|
|
25605
|
+
if (!/[A-Za-z_]\w*/.test(argsTrim))
|
|
25606
|
+
continue;
|
|
25607
|
+
out2.push({
|
|
25608
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
25609
|
+
pass: "language-sources",
|
|
25610
|
+
category: "security",
|
|
25611
|
+
rule_id: "xss",
|
|
25612
|
+
cwe: "CWE-79",
|
|
25613
|
+
severity: "high",
|
|
25614
|
+
level: "error",
|
|
25615
|
+
message: "Reflected XSS: HttpServletResponse.getWriter()." + m[2] + "(...) writes data to the response without HTML escaping. " + "Wrap user-controlled values with OWASP Encode.forHtml / " + "StringEscapeUtils.escapeHtml4 / Jsoup.clean.",
|
|
25616
|
+
file,
|
|
25617
|
+
line: i2 + 1,
|
|
25618
|
+
snippet: trimmed.substring(0, 100)
|
|
25619
|
+
});
|
|
25620
|
+
}
|
|
25621
|
+
return out2;
|
|
25622
|
+
}
|
|
25623
|
+
function findJsVueVHtmlXssFindings(code, file) {
|
|
25624
|
+
const out2 = [];
|
|
25625
|
+
const lines = code.split(`
|
|
25626
|
+
`);
|
|
25627
|
+
const sourceRe = /\b(?:URLSearchParams|location\s*\.\s*(?:search|hash|href|pathname)|window\s*\.\s*location|route\s*\.\s*(?:query|params)|router\s*\.\s*(?:currentRoute|query)|\$route\s*\.\s*(?:query|params)|fetch\s*\(|axios\s*\.\s*(?:get|post)|XMLHttpRequest|document\s*\.\s*location)\b/;
|
|
25628
|
+
const tplRe = /\btemplate\s*:\s*(['"`])([\s\S]*?)\1/g;
|
|
25629
|
+
let tm;
|
|
25630
|
+
const boundVars = new Set;
|
|
25631
|
+
while ((tm = tplRe.exec(code)) !== null) {
|
|
25632
|
+
const tpl = tm[2];
|
|
25633
|
+
const vhRe = /\bv-html\s*=\s*"([^"]+)"/g;
|
|
25634
|
+
let vm;
|
|
25635
|
+
while ((vm = vhRe.exec(tpl)) !== null) {
|
|
25636
|
+
const expr = vm[1].trim();
|
|
25637
|
+
const idMatch = expr.match(/^([A-Za-z_$][\w$]*)/);
|
|
25638
|
+
if (idMatch)
|
|
25639
|
+
boundVars.add(idMatch[1]);
|
|
25640
|
+
}
|
|
25641
|
+
}
|
|
25642
|
+
if (boundVars.size === 0)
|
|
25643
|
+
return out2;
|
|
25644
|
+
const taintedSet = new Set;
|
|
25645
|
+
const assignRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g;
|
|
25646
|
+
let am;
|
|
25647
|
+
assignRe.lastIndex = 0;
|
|
25648
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
25649
|
+
if (sourceRe.test(am[2]))
|
|
25650
|
+
taintedSet.add(am[1]);
|
|
25651
|
+
}
|
|
25652
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
25653
|
+
assignRe.lastIndex = 0;
|
|
25654
|
+
const before = taintedSet.size;
|
|
25655
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
25656
|
+
if (taintedSet.has(am[1]))
|
|
25657
|
+
continue;
|
|
25658
|
+
for (const tv of taintedSet) {
|
|
25659
|
+
if (new RegExp(`\\b${tv}\\b`).test(am[2])) {
|
|
25660
|
+
taintedSet.add(am[1]);
|
|
25661
|
+
break;
|
|
25662
|
+
}
|
|
25663
|
+
}
|
|
25664
|
+
}
|
|
25665
|
+
if (taintedSet.size === before)
|
|
25666
|
+
break;
|
|
25667
|
+
}
|
|
25668
|
+
const taintedBindings = new Set;
|
|
25669
|
+
for (const v of boundVars) {
|
|
25670
|
+
const bindRe = new RegExp(`\\b${v}\\s*[:=]\\s*([^,;\\n]+)`);
|
|
25671
|
+
for (const line of lines) {
|
|
25672
|
+
const m = line.match(bindRe);
|
|
25673
|
+
if (!m)
|
|
25674
|
+
continue;
|
|
25675
|
+
const val = m[1];
|
|
25676
|
+
if (sourceRe.test(val)) {
|
|
25677
|
+
taintedBindings.add(v);
|
|
25678
|
+
break;
|
|
25679
|
+
}
|
|
25680
|
+
let found = false;
|
|
25681
|
+
for (const tv of taintedSet) {
|
|
25682
|
+
if (new RegExp(`\\b${tv}\\b`).test(val)) {
|
|
25683
|
+
taintedBindings.add(v);
|
|
25684
|
+
found = true;
|
|
25685
|
+
break;
|
|
25686
|
+
}
|
|
25687
|
+
}
|
|
25688
|
+
if (found)
|
|
25689
|
+
break;
|
|
25690
|
+
}
|
|
25691
|
+
const propsRe = new RegExp(`\\bprops\\s*:\\s*\\[[^\\]]*['"\`]${v}['"\`][^\\]]*\\]`);
|
|
25692
|
+
if (propsRe.test(code))
|
|
25693
|
+
taintedBindings.add(v);
|
|
25694
|
+
}
|
|
25695
|
+
if (taintedBindings.size === 0)
|
|
25696
|
+
return out2;
|
|
25697
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25698
|
+
const raw = lines[i2];
|
|
25699
|
+
if (!/v-html\s*=/.test(raw))
|
|
25700
|
+
continue;
|
|
25701
|
+
const vm = raw.match(/v-html\s*=\s*"([^"]+)"/);
|
|
25702
|
+
if (!vm)
|
|
25703
|
+
continue;
|
|
25704
|
+
const idMatch = vm[1].trim().match(/^([A-Za-z_$][\w$]*)/);
|
|
25705
|
+
if (!idMatch || !taintedBindings.has(idMatch[1]))
|
|
25706
|
+
continue;
|
|
25707
|
+
out2.push({
|
|
25708
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
25709
|
+
pass: "language-sources",
|
|
25710
|
+
category: "security",
|
|
25711
|
+
rule_id: "xss",
|
|
25712
|
+
cwe: "CWE-79",
|
|
25713
|
+
severity: "high",
|
|
25714
|
+
level: "error",
|
|
25715
|
+
message: 'Vue v-html XSS: directive binds to "' + idMatch[1] + '" which is sourced from user-controlled input. Use {{ }} ' + "interpolation (auto-escapes) or sanitize the HTML with " + "DOMPurify before binding.",
|
|
25716
|
+
file,
|
|
25717
|
+
line: i2 + 1,
|
|
25718
|
+
snippet: raw.trim().substring(0, 100)
|
|
25719
|
+
});
|
|
25720
|
+
}
|
|
25721
|
+
return out2;
|
|
25722
|
+
}
|
|
25723
|
+
function findTsAngularBypassXssFindings(code, file) {
|
|
25724
|
+
const out2 = [];
|
|
25725
|
+
const lines = code.split(`
|
|
25726
|
+
`);
|
|
25727
|
+
const sanitizerNames = new Set;
|
|
25728
|
+
const declRe = /\b(?:public|private|protected|readonly|\s)?\s*([A-Za-z_$][\w$]*)\s*:\s*DomSanitizer\b/g;
|
|
25729
|
+
let m;
|
|
25730
|
+
declRe.lastIndex = 0;
|
|
25731
|
+
while ((m = declRe.exec(code)) !== null)
|
|
25732
|
+
sanitizerNames.add(m[1]);
|
|
25733
|
+
if (sanitizerNames.size === 0)
|
|
25734
|
+
return out2;
|
|
25735
|
+
const assignRe = /\bthis\s*\.\s*([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\b/g;
|
|
25736
|
+
let am;
|
|
25737
|
+
assignRe.lastIndex = 0;
|
|
25738
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
25739
|
+
if (sanitizerNames.has(am[2]))
|
|
25740
|
+
sanitizerNames.add(am[1]);
|
|
25741
|
+
}
|
|
25742
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25743
|
+
const raw = lines[i2];
|
|
25744
|
+
const trimmed = raw.trim();
|
|
25745
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
25746
|
+
continue;
|
|
25747
|
+
const callRe = /\b(?:this\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\.\s*bypassSecurityTrust(Html|Script|Url|ResourceUrl|Style)\s*\(([\s\S]*?)\)/;
|
|
25748
|
+
const cm = trimmed.match(callRe);
|
|
25749
|
+
if (!cm)
|
|
25750
|
+
continue;
|
|
25751
|
+
const recv = cm[1];
|
|
25752
|
+
if (!sanitizerNames.has(recv))
|
|
25753
|
+
continue;
|
|
25754
|
+
const arg = cm[3].trim();
|
|
25755
|
+
if (/^(['"`])[^'"`]*\1$/.test(arg))
|
|
25756
|
+
continue;
|
|
25757
|
+
out2.push({
|
|
25758
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
25759
|
+
pass: "language-sources",
|
|
25760
|
+
category: "security",
|
|
25761
|
+
rule_id: "xss",
|
|
25762
|
+
cwe: "CWE-79",
|
|
25763
|
+
severity: "high",
|
|
25764
|
+
level: "error",
|
|
25765
|
+
message: "Angular XSS: DomSanitizer.bypassSecurityTrust" + cm[2] + "() opts out of Angular's built-in sanitization for a " + "non-literal argument. Prefer leaving Angular's default " + "sanitizer in place, or sanitize the input with DOMPurify " + "before bypassing.",
|
|
25766
|
+
file,
|
|
25767
|
+
line: i2 + 1,
|
|
25768
|
+
snippet: trimmed.substring(0, 100)
|
|
25769
|
+
});
|
|
25770
|
+
}
|
|
25771
|
+
return out2;
|
|
25772
|
+
}
|
|
25773
|
+
function findPythonFlaskStringConcatXssFindings(code, file) {
|
|
25774
|
+
const out2 = [];
|
|
25775
|
+
const lines = code.split(`
|
|
25776
|
+
`);
|
|
25777
|
+
const fns = [];
|
|
25778
|
+
const defRe = /^(\s*)def\s+([A-Za-z_]\w*)\s*\(/;
|
|
25779
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25780
|
+
const raw = lines[i2];
|
|
25781
|
+
const dm = raw.match(defRe);
|
|
25782
|
+
if (!dm)
|
|
25783
|
+
continue;
|
|
25784
|
+
const indent = dm[1].length;
|
|
25785
|
+
let hasRoute = false;
|
|
25786
|
+
for (let j = i2 - 1;j >= 0; j--) {
|
|
25787
|
+
const prev = lines[j].trim();
|
|
25788
|
+
if (prev === "" || prev.startsWith("#"))
|
|
25789
|
+
continue;
|
|
25790
|
+
if (!prev.startsWith("@"))
|
|
25791
|
+
break;
|
|
25792
|
+
if (/@\s*[A-Za-z_]\w*\s*\.\s*route\s*\(/.test(prev) || /@\s*route\s*\(/.test(prev)) {
|
|
25793
|
+
hasRoute = true;
|
|
25794
|
+
}
|
|
25795
|
+
}
|
|
25796
|
+
if (!hasRoute)
|
|
25797
|
+
continue;
|
|
25798
|
+
let end = lines.length;
|
|
25799
|
+
for (let k = i2 + 1;k < lines.length; k++) {
|
|
25800
|
+
const ln = lines[k];
|
|
25801
|
+
if (!ln.trim())
|
|
25802
|
+
continue;
|
|
25803
|
+
const ind = ln.match(/^(\s*)/)?.[1].length ?? 0;
|
|
25804
|
+
if (ind <= indent) {
|
|
25805
|
+
end = k;
|
|
25806
|
+
break;
|
|
25807
|
+
}
|
|
25808
|
+
}
|
|
25809
|
+
fns.push({ name: dm[2], startLine: i2 + 1, endLine: end });
|
|
25810
|
+
}
|
|
25811
|
+
if (fns.length === 0)
|
|
25812
|
+
return out2;
|
|
25813
|
+
const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
|
|
25814
|
+
const escapeWrapRe = /\b(?:html\.escape|markupsafe\.escape|escape|markupsafe\.Markup\s*\.\s*escape|werkzeug\.utils\.escape)\s*\(/;
|
|
25815
|
+
for (const fn of fns) {
|
|
25816
|
+
const taintedVars = new Set;
|
|
25817
|
+
for (let li = fn.startLine;li < fn.endLine; li++) {
|
|
25818
|
+
const raw = lines[li];
|
|
25819
|
+
const rm = raw.match(requestSourceRe);
|
|
25820
|
+
if (rm)
|
|
25821
|
+
taintedVars.add(rm[1]);
|
|
25822
|
+
}
|
|
25823
|
+
for (let li = fn.startLine;li < fn.endLine; li++) {
|
|
25824
|
+
const raw = lines[li];
|
|
25825
|
+
const trimmed = raw.trim();
|
|
25826
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
25827
|
+
continue;
|
|
25828
|
+
const retMatch = trimmed.match(/^(?:return|yield)\s+(.+)$/);
|
|
25829
|
+
if (!retMatch)
|
|
25830
|
+
continue;
|
|
25831
|
+
const expr = retMatch[1];
|
|
25832
|
+
const inlineSource = /\brequest\s*\.\s*(?:args|form|values|files|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/.test(expr);
|
|
25833
|
+
const hasTaintedVar = [...taintedVars].some((v) => new RegExp(`\\b${v}\\b`).test(expr));
|
|
25834
|
+
if (!hasTaintedVar && !inlineSource)
|
|
25835
|
+
continue;
|
|
25836
|
+
if (escapeWrapRe.test(expr))
|
|
25837
|
+
continue;
|
|
25838
|
+
const buildsHtml = /['"`]\s*\+/.test(expr) || /\+\s*['"`]/.test(expr) || /\bf['"`][^{\n]*\{[^}]+\}/.test(expr) || /['"`]\s*%\s*[^=]/.test(expr) || /['"`]\.\s*format\s*\(/.test(expr);
|
|
25839
|
+
if (!buildsHtml)
|
|
25840
|
+
continue;
|
|
25841
|
+
out2.push({
|
|
25842
|
+
id: `xss-${file}-${li + 1}`,
|
|
25843
|
+
pass: "language-sources",
|
|
25844
|
+
category: "security",
|
|
25845
|
+
rule_id: "xss",
|
|
25846
|
+
cwe: "CWE-79",
|
|
25847
|
+
severity: "high",
|
|
25848
|
+
level: "error",
|
|
25849
|
+
message: "Reflected XSS: Flask route returns HTML built from request " + "input via string concatenation / f-string / format. Wrap " + "user input with markupsafe.escape() or render via a " + "Jinja2 template (autoescape on by default).",
|
|
25850
|
+
file,
|
|
25851
|
+
line: li + 1,
|
|
25852
|
+
snippet: trimmed.substring(0, 100)
|
|
25853
|
+
});
|
|
25854
|
+
break;
|
|
25855
|
+
}
|
|
25856
|
+
}
|
|
25857
|
+
return out2;
|
|
25858
|
+
}
|
|
25859
|
+
function findPythonJinjaMarkupXssFindings(code, file) {
|
|
25860
|
+
const out2 = [];
|
|
25861
|
+
const lines = code.split(`
|
|
25862
|
+
`);
|
|
25863
|
+
const importRe = /^\s*from\s+(?:markupsafe|flask)\s+import\s+(?:[^#\n]*\b)?Markup\b/m;
|
|
25864
|
+
const classDefRe = /^\s*class\s+Markup\b/m;
|
|
25865
|
+
if (!importRe.test(code))
|
|
25866
|
+
return out2;
|
|
25867
|
+
if (classDefRe.test(code))
|
|
25868
|
+
return out2;
|
|
25869
|
+
const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
|
|
25870
|
+
const taintedVars = new Set;
|
|
25871
|
+
for (const line of lines) {
|
|
25872
|
+
const m = line.match(requestSourceRe);
|
|
25873
|
+
if (m)
|
|
25874
|
+
taintedVars.add(m[1]);
|
|
25875
|
+
}
|
|
25876
|
+
if (taintedVars.size === 0)
|
|
25877
|
+
return out2;
|
|
25878
|
+
if (!/\.\s*render\s*\(/.test(code))
|
|
25879
|
+
return out2;
|
|
25880
|
+
const markupCallRe = /\bMarkup\s*\(\s*([A-Za-z_]\w*)\s*[,)]/g;
|
|
25881
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25882
|
+
const raw = lines[i2];
|
|
25883
|
+
const trimmed = raw.trim();
|
|
25884
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
25885
|
+
continue;
|
|
25886
|
+
let mm;
|
|
25887
|
+
markupCallRe.lastIndex = 0;
|
|
25888
|
+
while ((mm = markupCallRe.exec(trimmed)) !== null) {
|
|
25889
|
+
const argName = mm[1];
|
|
25890
|
+
if (!taintedVars.has(argName))
|
|
25891
|
+
continue;
|
|
25892
|
+
out2.push({
|
|
25893
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
25894
|
+
pass: "language-sources",
|
|
25895
|
+
category: "security",
|
|
25896
|
+
rule_id: "xss",
|
|
25897
|
+
cwe: "CWE-79",
|
|
25898
|
+
severity: "high",
|
|
25899
|
+
level: "error",
|
|
25900
|
+
message: "Jinja2 autoescape bypass: Markup(" + argName + ") wraps a request-derived value, disabling autoescape when " + "rendered. Pass the raw value to render() and let Jinja2 " + "auto-escape, or sanitize with bleach.clean first.",
|
|
25901
|
+
file,
|
|
25902
|
+
line: i2 + 1,
|
|
25903
|
+
snippet: trimmed.substring(0, 100)
|
|
25904
|
+
});
|
|
25905
|
+
break;
|
|
25906
|
+
}
|
|
25907
|
+
}
|
|
25908
|
+
return out2;
|
|
25909
|
+
}
|
|
25148
25910
|
|
|
25149
25911
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
25150
25912
|
var JS_XSS_SANITIZERS = [
|
|
@@ -38272,7 +39034,7 @@ var colors = {
|
|
|
38272
39034
|
};
|
|
38273
39035
|
|
|
38274
39036
|
// src/version.ts
|
|
38275
|
-
var version = "3.
|
|
39037
|
+
var version = "3.131.0";
|
|
38276
39038
|
|
|
38277
39039
|
// src/formatters.ts
|
|
38278
39040
|
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.131.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.131.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|