circle-ir 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/analysis/passes/language-sources-pass.d.ts +133 -0
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +1017 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +672 -0
- package/package.json +1 -1
|
@@ -23650,6 +23650,13 @@ var LanguageSourcesPass = class {
|
|
|
23650
23650
|
if (language === "go") {
|
|
23651
23651
|
additionalSanitizers.push(...findGoMapAllowlistGuardSanitizers(code));
|
|
23652
23652
|
additionalSanitizers.push(...findGoHtmlTemplateImportSanitizers(code));
|
|
23653
|
+
const goMisconfigFindings = findGoPatternFindings(code, graph.ir.meta.file);
|
|
23654
|
+
for (const finding of goMisconfigFindings) {
|
|
23655
|
+
ctx.addFinding(finding);
|
|
23656
|
+
}
|
|
23657
|
+
for (const finding of findGoXssFindings(code, graph.ir.meta.file)) {
|
|
23658
|
+
ctx.addFinding(finding);
|
|
23659
|
+
}
|
|
23653
23660
|
}
|
|
23654
23661
|
if (language === "python") {
|
|
23655
23662
|
additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
|
|
@@ -23662,6 +23669,12 @@ var LanguageSourcesPass = class {
|
|
|
23662
23669
|
for (const finding of pyMisconfigFindings) {
|
|
23663
23670
|
ctx.addFinding(finding);
|
|
23664
23671
|
}
|
|
23672
|
+
for (const finding of findPythonFlaskStringConcatXssFindings(code, graph.ir.meta.file)) {
|
|
23673
|
+
ctx.addFinding(finding);
|
|
23674
|
+
}
|
|
23675
|
+
for (const finding of findPythonJinjaMarkupXssFindings(code, graph.ir.meta.file)) {
|
|
23676
|
+
ctx.addFinding(finding);
|
|
23677
|
+
}
|
|
23665
23678
|
}
|
|
23666
23679
|
if (language === "rust") {
|
|
23667
23680
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
@@ -23671,6 +23684,18 @@ var LanguageSourcesPass = class {
|
|
|
23671
23684
|
for (const finding of rustMisconfigFindings) {
|
|
23672
23685
|
ctx.addFinding(finding);
|
|
23673
23686
|
}
|
|
23687
|
+
for (const finding of findRustHardcodedCredentialFindings(code, graph.ir.meta.file)) {
|
|
23688
|
+
ctx.addFinding(finding);
|
|
23689
|
+
}
|
|
23690
|
+
for (const finding of findRustInsecureCookieFindings(code, graph.ir.meta.file)) {
|
|
23691
|
+
ctx.addFinding(finding);
|
|
23692
|
+
}
|
|
23693
|
+
for (const finding of findRustJwtVerifyDisabledFindings(code, graph.ir.meta.file)) {
|
|
23694
|
+
ctx.addFinding(finding);
|
|
23695
|
+
}
|
|
23696
|
+
for (const finding of findRustWeakCryptoEcbFindings(code, graph.ir.meta.file)) {
|
|
23697
|
+
ctx.addFinding(finding);
|
|
23698
|
+
}
|
|
23674
23699
|
}
|
|
23675
23700
|
if (language === "javascript" || language === "typescript" || language === "htmljs") {
|
|
23676
23701
|
additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
|
|
@@ -23678,6 +23703,17 @@ var LanguageSourcesPass = class {
|
|
|
23678
23703
|
additionalSanitizers.push(...findJsCsvFormulaPrefixSanitizers(code));
|
|
23679
23704
|
additionalSanitizers.push(...findJsWrapperFunctionSanitizers(code));
|
|
23680
23705
|
additionalSanitizers.push(...findJsSsrfAllowlistGuardSanitizers(code));
|
|
23706
|
+
additionalSanitizers.push(...findJsArgvFormExecSanitizers(code));
|
|
23707
|
+
additionalSanitizers.push(...findJsParameterizedSqlSanitizers(code));
|
|
23708
|
+
for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
|
|
23709
|
+
ctx.addFinding(finding);
|
|
23710
|
+
}
|
|
23711
|
+
for (const finding of findJsVueVHtmlXssFindings(code, graph.ir.meta.file)) {
|
|
23712
|
+
ctx.addFinding(finding);
|
|
23713
|
+
}
|
|
23714
|
+
for (const finding of findTsAngularBypassXssFindings(code, graph.ir.meta.file)) {
|
|
23715
|
+
ctx.addFinding(finding);
|
|
23716
|
+
}
|
|
23681
23717
|
}
|
|
23682
23718
|
if (language === "java") {
|
|
23683
23719
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
@@ -23686,6 +23722,12 @@ var LanguageSourcesPass = class {
|
|
|
23686
23722
|
);
|
|
23687
23723
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
23688
23724
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
23725
|
+
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
23726
|
+
ctx.addFinding(finding);
|
|
23727
|
+
}
|
|
23728
|
+
for (const finding of findJavaResponseWriterXssFindings(code, graph.ir.meta.file)) {
|
|
23729
|
+
ctx.addFinding(finding);
|
|
23730
|
+
}
|
|
23689
23731
|
}
|
|
23690
23732
|
if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
|
|
23691
23733
|
const exfilFindings = findExternalSecretExfiltrationFindings(
|
|
@@ -25432,6 +25474,55 @@ function findJsSsrfAllowlistGuardSanitizers(code) {
|
|
|
25432
25474
|
}
|
|
25433
25475
|
return sanitizers;
|
|
25434
25476
|
}
|
|
25477
|
+
function findJsArgvFormExecSanitizers(code) {
|
|
25478
|
+
const sanitizers = [];
|
|
25479
|
+
const lines = code.split("\n");
|
|
25480
|
+
const argvExecRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*(?:'[^']*'|"[^"]*"|`[^`]*`)\s*,\s*\[/;
|
|
25481
|
+
const shellArgvRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*['"`](?:[\w./-]*\/)?(?:sh|bash|zsh|ksh|dash|cmd(?:\.exe)?|powershell|pwsh)['"`]\s*,\s*\[\s*['"`]-c['"`]/;
|
|
25482
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25483
|
+
const text = lines[i2];
|
|
25484
|
+
if (!argvExecRe.test(text)) continue;
|
|
25485
|
+
if (shellArgvRe.test(text)) continue;
|
|
25486
|
+
sanitizers.push({
|
|
25487
|
+
type: "js_argv_form_exec",
|
|
25488
|
+
method: "execFile",
|
|
25489
|
+
line: i2 + 1,
|
|
25490
|
+
sanitizes: ["command_injection", "external_taint_escape"]
|
|
25491
|
+
});
|
|
25492
|
+
}
|
|
25493
|
+
return sanitizers;
|
|
25494
|
+
}
|
|
25495
|
+
function findJsParameterizedSqlSanitizers(code) {
|
|
25496
|
+
const sanitizers = [];
|
|
25497
|
+
const lines = code.split("\n");
|
|
25498
|
+
const singleRe = /\.\s*(?:query|execute)\s*\(\s*'([^'\\]*(?:\\.[^'\\]*)*)'\s*,\s*\[/;
|
|
25499
|
+
const doubleRe = /\.\s*(?:query|execute)\s*\(\s*"([^"\\]*(?:\\.[^"\\]*)*)"\s*,\s*\[/;
|
|
25500
|
+
const tickRe = /\.\s*(?:query|execute)\s*\(\s*`([^`]*)`\s*,\s*\[/;
|
|
25501
|
+
const placeholderRe = /(?:\$\d+|\?)/;
|
|
25502
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25503
|
+
const text = lines[i2];
|
|
25504
|
+
let sql = null;
|
|
25505
|
+
const s = singleRe.exec(text);
|
|
25506
|
+
if (s) sql = s[1];
|
|
25507
|
+
if (sql == null) {
|
|
25508
|
+
const d = doubleRe.exec(text);
|
|
25509
|
+
if (d) sql = d[1];
|
|
25510
|
+
}
|
|
25511
|
+
if (sql == null) {
|
|
25512
|
+
const t = tickRe.exec(text);
|
|
25513
|
+
if (t && !/\$\{/.test(t[1])) sql = t[1];
|
|
25514
|
+
}
|
|
25515
|
+
if (sql == null) continue;
|
|
25516
|
+
if (!placeholderRe.test(sql)) continue;
|
|
25517
|
+
sanitizers.push({
|
|
25518
|
+
type: "js_parameterized_sql",
|
|
25519
|
+
method: "query",
|
|
25520
|
+
line: i2 + 1,
|
|
25521
|
+
sanitizes: ["sql_injection", "external_taint_escape"]
|
|
25522
|
+
});
|
|
25523
|
+
}
|
|
25524
|
+
return sanitizers;
|
|
25525
|
+
}
|
|
25435
25526
|
function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
|
|
25436
25527
|
const sanitizers = [];
|
|
25437
25528
|
const lines = code.split("\n");
|
|
@@ -25944,6 +26035,587 @@ function findRustPatternFindings(code, file) {
|
|
|
25944
26035
|
}
|
|
25945
26036
|
return out2;
|
|
25946
26037
|
}
|
|
26038
|
+
function findRustHardcodedCredentialFindings(code, file) {
|
|
26039
|
+
const out2 = [];
|
|
26040
|
+
const lines = code.split("\n");
|
|
26041
|
+
const re = /\b(?:pub\s+)?(?:const|static)\s+([A-Z][A-Z0-9_]*)\s*:\s*&\s*'?[a-z_]*\s*str\s*=\s*"([^"]+)"/;
|
|
26042
|
+
const nameRe = /(?:^|_)(?:API[_]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|AUTH)(?:_|$)/i;
|
|
26043
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26044
|
+
const raw = lines[i2];
|
|
26045
|
+
const trimmed = raw.trim();
|
|
26046
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26047
|
+
const m = trimmed.match(re);
|
|
26048
|
+
if (!m) continue;
|
|
26049
|
+
const name2 = m[1];
|
|
26050
|
+
const value = m[2];
|
|
26051
|
+
if (!nameRe.test(name2)) continue;
|
|
26052
|
+
if (value.length < 8) continue;
|
|
26053
|
+
if (/^(?:xxx|todo|fixme|placeholder|changeme)/i.test(value)) continue;
|
|
26054
|
+
out2.push({
|
|
26055
|
+
id: `hardcoded-credential-${file}-${i2 + 1}`,
|
|
26056
|
+
pass: "language-sources",
|
|
26057
|
+
category: "security",
|
|
26058
|
+
rule_id: "hardcoded-credential",
|
|
26059
|
+
cwe: "CWE-798",
|
|
26060
|
+
severity: "high",
|
|
26061
|
+
level: "error",
|
|
26062
|
+
message: `Hardcoded credential: const ${name2} contains a literal secret value`,
|
|
26063
|
+
file,
|
|
26064
|
+
line: i2 + 1,
|
|
26065
|
+
snippet: trimmed.substring(0, 100)
|
|
26066
|
+
});
|
|
26067
|
+
}
|
|
26068
|
+
return out2;
|
|
26069
|
+
}
|
|
26070
|
+
function findRustInsecureCookieFindings(code, file) {
|
|
26071
|
+
const out2 = [];
|
|
26072
|
+
const lines = code.split("\n");
|
|
26073
|
+
const builderRe = /\bCookie\s*::\s*build\s*\(/;
|
|
26074
|
+
const insecureFlagRe = /\.\s*(?:secure|http_only)\s*\(\s*false\s*\)/;
|
|
26075
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26076
|
+
const raw = lines[i2];
|
|
26077
|
+
const trimmed = raw.trim();
|
|
26078
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26079
|
+
if (!builderRe.test(trimmed)) continue;
|
|
26080
|
+
if (!insecureFlagRe.test(trimmed)) continue;
|
|
26081
|
+
out2.push({
|
|
26082
|
+
id: `insecure-cookie-${file}-${i2 + 1}`,
|
|
26083
|
+
pass: "language-sources",
|
|
26084
|
+
category: "security",
|
|
26085
|
+
rule_id: "insecure-cookie",
|
|
26086
|
+
cwe: "CWE-1004",
|
|
26087
|
+
severity: "medium",
|
|
26088
|
+
level: "warning",
|
|
26089
|
+
message: "Insecure cookie: Cookie::build chain disables Secure / HttpOnly flag(s)",
|
|
26090
|
+
file,
|
|
26091
|
+
line: i2 + 1,
|
|
26092
|
+
snippet: trimmed.substring(0, 100)
|
|
26093
|
+
});
|
|
26094
|
+
}
|
|
26095
|
+
return out2;
|
|
26096
|
+
}
|
|
26097
|
+
function findRustJwtVerifyDisabledFindings(code, file) {
|
|
26098
|
+
const out2 = [];
|
|
26099
|
+
const lines = code.split("\n");
|
|
26100
|
+
const re = /\.\s*insecure_disable_signature_validation\s*\(/;
|
|
26101
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26102
|
+
const raw = lines[i2];
|
|
26103
|
+
const trimmed = raw.trim();
|
|
26104
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26105
|
+
if (!re.test(trimmed)) continue;
|
|
26106
|
+
out2.push({
|
|
26107
|
+
id: `jwt-verify-disabled-${file}-${i2 + 1}`,
|
|
26108
|
+
pass: "language-sources",
|
|
26109
|
+
category: "security",
|
|
26110
|
+
rule_id: "jwt-verify-disabled",
|
|
26111
|
+
cwe: "CWE-347",
|
|
26112
|
+
severity: "critical",
|
|
26113
|
+
level: "error",
|
|
26114
|
+
message: "JWT signature verification disabled: Validation::insecure_disable_signature_validation() forfeits signature enforcement",
|
|
26115
|
+
file,
|
|
26116
|
+
line: i2 + 1,
|
|
26117
|
+
snippet: trimmed.substring(0, 100)
|
|
26118
|
+
});
|
|
26119
|
+
}
|
|
26120
|
+
return out2;
|
|
26121
|
+
}
|
|
26122
|
+
function findRustWeakCryptoEcbFindings(code, file) {
|
|
26123
|
+
const out2 = [];
|
|
26124
|
+
const lines = code.split("\n");
|
|
26125
|
+
const ctorRe = /\bAes(?:128|192|256)(?:Ecb)?\s*::\s*new\s*\(/;
|
|
26126
|
+
const blockOpRe = /\.\s*(encrypt_block|decrypt_block)\s*\(/;
|
|
26127
|
+
let sawCtor = false;
|
|
26128
|
+
for (const line of lines) {
|
|
26129
|
+
if (ctorRe.test(line)) {
|
|
26130
|
+
sawCtor = true;
|
|
26131
|
+
break;
|
|
26132
|
+
}
|
|
26133
|
+
}
|
|
26134
|
+
if (!sawCtor) return out2;
|
|
26135
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26136
|
+
const raw = lines[i2];
|
|
26137
|
+
const trimmed = raw.trim();
|
|
26138
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26139
|
+
if (!blockOpRe.test(trimmed)) continue;
|
|
26140
|
+
out2.push({
|
|
26141
|
+
id: `weak-crypto-${file}-${i2 + 1}`,
|
|
26142
|
+
pass: "language-sources",
|
|
26143
|
+
category: "security",
|
|
26144
|
+
rule_id: "weak-crypto",
|
|
26145
|
+
cwe: "CWE-327",
|
|
26146
|
+
severity: "high",
|
|
26147
|
+
level: "error",
|
|
26148
|
+
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.",
|
|
26149
|
+
file,
|
|
26150
|
+
line: i2 + 1,
|
|
26151
|
+
snippet: trimmed.substring(0, 100)
|
|
26152
|
+
});
|
|
26153
|
+
}
|
|
26154
|
+
return out2;
|
|
26155
|
+
}
|
|
26156
|
+
function findJavaPatternFindings(code, file) {
|
|
26157
|
+
const out2 = [];
|
|
26158
|
+
const lines = code.split("\n");
|
|
26159
|
+
const jwtDecodeRe = /\bJWT\s*\.\s*decode\s*\(/;
|
|
26160
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26161
|
+
const raw = lines[i2];
|
|
26162
|
+
const trimmed = raw.trim();
|
|
26163
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
26164
|
+
if (!jwtDecodeRe.test(trimmed)) continue;
|
|
26165
|
+
if (/\.\s*verify\s*\(/.test(trimmed)) continue;
|
|
26166
|
+
out2.push({
|
|
26167
|
+
id: `jwt-verify-disabled-${file}-${i2 + 1}-decode`,
|
|
26168
|
+
pass: "language-sources",
|
|
26169
|
+
category: "security",
|
|
26170
|
+
rule_id: "jwt-verify-disabled",
|
|
26171
|
+
cwe: "CWE-347",
|
|
26172
|
+
severity: "critical",
|
|
26173
|
+
level: "error",
|
|
26174
|
+
message: "JWT signature not verified: auth0 `JWT.decode(token)` parses without checking the signature. Use `JWT.require(<algorithm>).build().verify(token)` to enforce verification.",
|
|
26175
|
+
file,
|
|
26176
|
+
line: i2 + 1,
|
|
26177
|
+
snippet: trimmed.substring(0, 100)
|
|
26178
|
+
});
|
|
26179
|
+
}
|
|
26180
|
+
const anonStartRe = /\bnew\s+X509TrustManager\s*\(\s*\)\s*\{/;
|
|
26181
|
+
const checkServerSig = /\bcheckServerTrusted\s*\([^)]*\)\s*(?:throws\s+[^\{]*)?\{\s*\}/;
|
|
26182
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26183
|
+
const raw = lines[i2];
|
|
26184
|
+
if (!anonStartRe.test(raw)) continue;
|
|
26185
|
+
const end = Math.min(lines.length, i2 + 16);
|
|
26186
|
+
let foundAt = -1;
|
|
26187
|
+
for (let j = i2; j < end; j++) {
|
|
26188
|
+
if (checkServerSig.test(lines[j])) {
|
|
26189
|
+
foundAt = j;
|
|
26190
|
+
break;
|
|
26191
|
+
}
|
|
26192
|
+
}
|
|
26193
|
+
if (foundAt < 0) continue;
|
|
26194
|
+
out2.push({
|
|
26195
|
+
id: `tls-verify-disabled-${file}-${foundAt + 1}`,
|
|
26196
|
+
pass: "language-sources",
|
|
26197
|
+
category: "security",
|
|
26198
|
+
rule_id: "tls-verify-disabled",
|
|
26199
|
+
cwe: "CWE-295",
|
|
26200
|
+
severity: "high",
|
|
26201
|
+
level: "error",
|
|
26202
|
+
message: "TLS certificate verification disabled: anonymous X509TrustManager with empty checkServerTrusted body accepts every certificate.",
|
|
26203
|
+
file,
|
|
26204
|
+
line: foundAt + 1,
|
|
26205
|
+
snippet: lines[foundAt].trim().substring(0, 100)
|
|
26206
|
+
});
|
|
26207
|
+
}
|
|
26208
|
+
return out2;
|
|
26209
|
+
}
|
|
26210
|
+
function findGoPatternFindings(code, file) {
|
|
26211
|
+
const out2 = [];
|
|
26212
|
+
const lines = code.split("\n");
|
|
26213
|
+
const cipherVars = /* @__PURE__ */ new Set();
|
|
26214
|
+
const ctorRe = /\b([a-zA-Z_]\w*)\s*(?:,\s*[a-zA-Z_]\w*)?\s*:?=\s*aes\.NewCipher\s*\(/;
|
|
26215
|
+
for (const line of lines) {
|
|
26216
|
+
const m = line.match(ctorRe);
|
|
26217
|
+
if (m) cipherVars.add(m[1]);
|
|
26218
|
+
}
|
|
26219
|
+
if (cipherVars.size === 0) return out2;
|
|
26220
|
+
for (const v of Array.from(cipherVars)) {
|
|
26221
|
+
const wrapRe = new RegExp(
|
|
26222
|
+
`\\bcipher\\.New(?:GCM|CBCEncrypter|CBCDecrypter|CTR|OFB|CFBEncrypter|CFBDecrypter)\\s*\\(\\s*${v}\\b`
|
|
26223
|
+
);
|
|
26224
|
+
for (const line of lines) {
|
|
26225
|
+
if (wrapRe.test(line)) {
|
|
26226
|
+
cipherVars.delete(v);
|
|
26227
|
+
break;
|
|
26228
|
+
}
|
|
26229
|
+
}
|
|
26230
|
+
}
|
|
26231
|
+
if (cipherVars.size === 0) return out2;
|
|
26232
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26233
|
+
const raw = lines[i2];
|
|
26234
|
+
const trimmed = raw.trim();
|
|
26235
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26236
|
+
for (const v of cipherVars) {
|
|
26237
|
+
const opRe = new RegExp(`\\b${v}\\s*\\.\\s*(?:Encrypt|Decrypt)\\s*\\(`);
|
|
26238
|
+
if (!opRe.test(trimmed)) continue;
|
|
26239
|
+
out2.push({
|
|
26240
|
+
id: `weak-crypto-${file}-${i2 + 1}`,
|
|
26241
|
+
pass: "language-sources",
|
|
26242
|
+
category: "security",
|
|
26243
|
+
rule_id: "weak-crypto",
|
|
26244
|
+
cwe: "CWE-327",
|
|
26245
|
+
severity: "high",
|
|
26246
|
+
level: "error",
|
|
26247
|
+
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.",
|
|
26248
|
+
file,
|
|
26249
|
+
line: i2 + 1,
|
|
26250
|
+
snippet: trimmed.substring(0, 100)
|
|
26251
|
+
});
|
|
26252
|
+
break;
|
|
26253
|
+
}
|
|
26254
|
+
}
|
|
26255
|
+
return out2;
|
|
26256
|
+
}
|
|
26257
|
+
function findJsPatternFindings(code, file) {
|
|
26258
|
+
const out2 = [];
|
|
26259
|
+
const lines = code.split("\n");
|
|
26260
|
+
const parseRe = /\blibxml(?:js)?\s*\.\s*parseXml(?:String)?\s*\(/;
|
|
26261
|
+
const noentTrueRe = /\bnoent\s*:\s*true\b/;
|
|
26262
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26263
|
+
const raw = lines[i2];
|
|
26264
|
+
const trimmed = raw.trim();
|
|
26265
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
26266
|
+
if (!parseRe.test(trimmed)) continue;
|
|
26267
|
+
if (!noentTrueRe.test(trimmed)) continue;
|
|
26268
|
+
out2.push({
|
|
26269
|
+
id: `xml-entity-expansion-${file}-${i2 + 1}`,
|
|
26270
|
+
pass: "language-sources",
|
|
26271
|
+
category: "security",
|
|
26272
|
+
rule_id: "xml-entity-expansion",
|
|
26273
|
+
cwe: "CWE-611",
|
|
26274
|
+
severity: "high",
|
|
26275
|
+
level: "error",
|
|
26276
|
+
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`.",
|
|
26277
|
+
file,
|
|
26278
|
+
line: i2 + 1,
|
|
26279
|
+
snippet: trimmed.substring(0, 100)
|
|
26280
|
+
});
|
|
26281
|
+
}
|
|
26282
|
+
return out2;
|
|
26283
|
+
}
|
|
26284
|
+
function findGoXssFindings(code, file) {
|
|
26285
|
+
const out2 = [];
|
|
26286
|
+
const lines = code.split("\n");
|
|
26287
|
+
const rwNames = /* @__PURE__ */ new Set();
|
|
26288
|
+
const sigRe = /\(\s*([A-Za-z_]\w*)\s+http\.ResponseWriter\b/g;
|
|
26289
|
+
for (const line of lines) {
|
|
26290
|
+
let m;
|
|
26291
|
+
sigRe.lastIndex = 0;
|
|
26292
|
+
while ((m = sigRe.exec(line)) !== null) rwNames.add(m[1]);
|
|
26293
|
+
}
|
|
26294
|
+
if (rwNames.size === 0) return out2;
|
|
26295
|
+
const escaperRe = /\b(?:html|template)\.(?:EscapeString|HTMLEscapeString|HTMLEscaper|JSEscapeString|URLQueryEscaper)\s*\(/;
|
|
26296
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26297
|
+
const raw = lines[i2];
|
|
26298
|
+
const trimmed = raw.trim();
|
|
26299
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26300
|
+
const callRe = /\bfmt\.Fprint(?:f|ln)?\s*\(\s*([A-Za-z_]\w*)\s*,\s*([\s\S]*)\)\s*(?:\/\/.*)?$/;
|
|
26301
|
+
const m = trimmed.match(callRe);
|
|
26302
|
+
if (!m) continue;
|
|
26303
|
+
if (!rwNames.has(m[1])) continue;
|
|
26304
|
+
const tail = m[2];
|
|
26305
|
+
if (escaperRe.test(tail)) continue;
|
|
26306
|
+
if (!/[A-Za-z_]\w*/.test(tail.replace(/"[^"]*"|`[^`]*`/g, ""))) continue;
|
|
26307
|
+
out2.push({
|
|
26308
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
26309
|
+
pass: "language-sources",
|
|
26310
|
+
category: "security",
|
|
26311
|
+
rule_id: "xss",
|
|
26312
|
+
cwe: "CWE-79",
|
|
26313
|
+
severity: "high",
|
|
26314
|
+
level: "error",
|
|
26315
|
+
message: "Reflected XSS: fmt.Fprint writes data to http.ResponseWriter without HTML escaping. Wrap user-controlled args with html.EscapeString / template.HTMLEscapeString.",
|
|
26316
|
+
file,
|
|
26317
|
+
line: i2 + 1,
|
|
26318
|
+
snippet: trimmed.substring(0, 100)
|
|
26319
|
+
});
|
|
26320
|
+
}
|
|
26321
|
+
return out2;
|
|
26322
|
+
}
|
|
26323
|
+
function findJavaResponseWriterXssFindings(code, file) {
|
|
26324
|
+
const out2 = [];
|
|
26325
|
+
const lines = code.split("\n");
|
|
26326
|
+
const respNames = /* @__PURE__ */ new Set();
|
|
26327
|
+
const sigRe = /\bHttpServletResponse\s+([A-Za-z_]\w*)\b/g;
|
|
26328
|
+
for (const line of lines) {
|
|
26329
|
+
let m;
|
|
26330
|
+
sigRe.lastIndex = 0;
|
|
26331
|
+
while ((m = sigRe.exec(line)) !== null) respNames.add(m[1]);
|
|
26332
|
+
}
|
|
26333
|
+
if (respNames.size === 0) return out2;
|
|
26334
|
+
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*\(/;
|
|
26335
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26336
|
+
const raw = lines[i2];
|
|
26337
|
+
const trimmed = raw.trim();
|
|
26338
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
26339
|
+
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*(?:\/\/.*)?$/;
|
|
26340
|
+
const m = trimmed.match(chainRe);
|
|
26341
|
+
if (!m) continue;
|
|
26342
|
+
const recv = m[1];
|
|
26343
|
+
if (!respNames.has(recv)) continue;
|
|
26344
|
+
const args2 = m[3];
|
|
26345
|
+
if (safeWrapRe.test(args2)) continue;
|
|
26346
|
+
const argsTrim = args2.trim();
|
|
26347
|
+
if (/^"(?:\\.|[^"\\])*"$/.test(argsTrim)) continue;
|
|
26348
|
+
if (!/[A-Za-z_]\w*/.test(argsTrim)) continue;
|
|
26349
|
+
out2.push({
|
|
26350
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
26351
|
+
pass: "language-sources",
|
|
26352
|
+
category: "security",
|
|
26353
|
+
rule_id: "xss",
|
|
26354
|
+
cwe: "CWE-79",
|
|
26355
|
+
severity: "high",
|
|
26356
|
+
level: "error",
|
|
26357
|
+
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.",
|
|
26358
|
+
file,
|
|
26359
|
+
line: i2 + 1,
|
|
26360
|
+
snippet: trimmed.substring(0, 100)
|
|
26361
|
+
});
|
|
26362
|
+
}
|
|
26363
|
+
return out2;
|
|
26364
|
+
}
|
|
26365
|
+
function findJsVueVHtmlXssFindings(code, file) {
|
|
26366
|
+
const out2 = [];
|
|
26367
|
+
const lines = code.split("\n");
|
|
26368
|
+
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/;
|
|
26369
|
+
const tplRe = /\btemplate\s*:\s*(['"`])([\s\S]*?)\1/g;
|
|
26370
|
+
let tm;
|
|
26371
|
+
const boundVars = /* @__PURE__ */ new Set();
|
|
26372
|
+
while ((tm = tplRe.exec(code)) !== null) {
|
|
26373
|
+
const tpl = tm[2];
|
|
26374
|
+
const vhRe = /\bv-html\s*=\s*"([^"]+)"/g;
|
|
26375
|
+
let vm;
|
|
26376
|
+
while ((vm = vhRe.exec(tpl)) !== null) {
|
|
26377
|
+
const expr = vm[1].trim();
|
|
26378
|
+
const idMatch = expr.match(/^([A-Za-z_$][\w$]*)/);
|
|
26379
|
+
if (idMatch) boundVars.add(idMatch[1]);
|
|
26380
|
+
}
|
|
26381
|
+
}
|
|
26382
|
+
if (boundVars.size === 0) return out2;
|
|
26383
|
+
const taintedSet = /* @__PURE__ */ new Set();
|
|
26384
|
+
const assignRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g;
|
|
26385
|
+
let am;
|
|
26386
|
+
assignRe.lastIndex = 0;
|
|
26387
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
26388
|
+
if (sourceRe.test(am[2])) taintedSet.add(am[1]);
|
|
26389
|
+
}
|
|
26390
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
26391
|
+
assignRe.lastIndex = 0;
|
|
26392
|
+
const before = taintedSet.size;
|
|
26393
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
26394
|
+
if (taintedSet.has(am[1])) continue;
|
|
26395
|
+
for (const tv of taintedSet) {
|
|
26396
|
+
if (new RegExp(`\\b${tv}\\b`).test(am[2])) {
|
|
26397
|
+
taintedSet.add(am[1]);
|
|
26398
|
+
break;
|
|
26399
|
+
}
|
|
26400
|
+
}
|
|
26401
|
+
}
|
|
26402
|
+
if (taintedSet.size === before) break;
|
|
26403
|
+
}
|
|
26404
|
+
const taintedBindings = /* @__PURE__ */ new Set();
|
|
26405
|
+
for (const v of boundVars) {
|
|
26406
|
+
const bindRe = new RegExp(`\\b${v}\\s*[:=]\\s*([^,;\\n]+)`);
|
|
26407
|
+
for (const line of lines) {
|
|
26408
|
+
const m = line.match(bindRe);
|
|
26409
|
+
if (!m) continue;
|
|
26410
|
+
const val = m[1];
|
|
26411
|
+
if (sourceRe.test(val)) {
|
|
26412
|
+
taintedBindings.add(v);
|
|
26413
|
+
break;
|
|
26414
|
+
}
|
|
26415
|
+
let found = false;
|
|
26416
|
+
for (const tv of taintedSet) {
|
|
26417
|
+
if (new RegExp(`\\b${tv}\\b`).test(val)) {
|
|
26418
|
+
taintedBindings.add(v);
|
|
26419
|
+
found = true;
|
|
26420
|
+
break;
|
|
26421
|
+
}
|
|
26422
|
+
}
|
|
26423
|
+
if (found) break;
|
|
26424
|
+
}
|
|
26425
|
+
const propsRe = new RegExp(
|
|
26426
|
+
`\\bprops\\s*:\\s*\\[[^\\]]*['"\`]${v}['"\`][^\\]]*\\]`
|
|
26427
|
+
);
|
|
26428
|
+
if (propsRe.test(code)) taintedBindings.add(v);
|
|
26429
|
+
}
|
|
26430
|
+
if (taintedBindings.size === 0) return out2;
|
|
26431
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26432
|
+
const raw = lines[i2];
|
|
26433
|
+
if (!/v-html\s*=/.test(raw)) continue;
|
|
26434
|
+
const vm = raw.match(/v-html\s*=\s*"([^"]+)"/);
|
|
26435
|
+
if (!vm) continue;
|
|
26436
|
+
const idMatch = vm[1].trim().match(/^([A-Za-z_$][\w$]*)/);
|
|
26437
|
+
if (!idMatch || !taintedBindings.has(idMatch[1])) continue;
|
|
26438
|
+
out2.push({
|
|
26439
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
26440
|
+
pass: "language-sources",
|
|
26441
|
+
category: "security",
|
|
26442
|
+
rule_id: "xss",
|
|
26443
|
+
cwe: "CWE-79",
|
|
26444
|
+
severity: "high",
|
|
26445
|
+
level: "error",
|
|
26446
|
+
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.',
|
|
26447
|
+
file,
|
|
26448
|
+
line: i2 + 1,
|
|
26449
|
+
snippet: raw.trim().substring(0, 100)
|
|
26450
|
+
});
|
|
26451
|
+
}
|
|
26452
|
+
return out2;
|
|
26453
|
+
}
|
|
26454
|
+
function findTsAngularBypassXssFindings(code, file) {
|
|
26455
|
+
const out2 = [];
|
|
26456
|
+
const lines = code.split("\n");
|
|
26457
|
+
const sanitizerNames = /* @__PURE__ */ new Set();
|
|
26458
|
+
const declRe = /\b(?:public|private|protected|readonly|\s)?\s*([A-Za-z_$][\w$]*)\s*:\s*DomSanitizer\b/g;
|
|
26459
|
+
let m;
|
|
26460
|
+
declRe.lastIndex = 0;
|
|
26461
|
+
while ((m = declRe.exec(code)) !== null) sanitizerNames.add(m[1]);
|
|
26462
|
+
if (sanitizerNames.size === 0) return out2;
|
|
26463
|
+
const assignRe = /\bthis\s*\.\s*([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\b/g;
|
|
26464
|
+
let am;
|
|
26465
|
+
assignRe.lastIndex = 0;
|
|
26466
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
26467
|
+
if (sanitizerNames.has(am[2])) sanitizerNames.add(am[1]);
|
|
26468
|
+
}
|
|
26469
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26470
|
+
const raw = lines[i2];
|
|
26471
|
+
const trimmed = raw.trim();
|
|
26472
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
26473
|
+
const callRe = /\b(?:this\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\.\s*bypassSecurityTrust(Html|Script|Url|ResourceUrl|Style)\s*\(([\s\S]*?)\)/;
|
|
26474
|
+
const cm = trimmed.match(callRe);
|
|
26475
|
+
if (!cm) continue;
|
|
26476
|
+
const recv = cm[1];
|
|
26477
|
+
if (!sanitizerNames.has(recv)) continue;
|
|
26478
|
+
const arg = cm[3].trim();
|
|
26479
|
+
if (/^(['"`])[^'"`]*\1$/.test(arg)) continue;
|
|
26480
|
+
out2.push({
|
|
26481
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
26482
|
+
pass: "language-sources",
|
|
26483
|
+
category: "security",
|
|
26484
|
+
rule_id: "xss",
|
|
26485
|
+
cwe: "CWE-79",
|
|
26486
|
+
severity: "high",
|
|
26487
|
+
level: "error",
|
|
26488
|
+
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.",
|
|
26489
|
+
file,
|
|
26490
|
+
line: i2 + 1,
|
|
26491
|
+
snippet: trimmed.substring(0, 100)
|
|
26492
|
+
});
|
|
26493
|
+
}
|
|
26494
|
+
return out2;
|
|
26495
|
+
}
|
|
26496
|
+
function findPythonFlaskStringConcatXssFindings(code, file) {
|
|
26497
|
+
const out2 = [];
|
|
26498
|
+
const lines = code.split("\n");
|
|
26499
|
+
const fns = [];
|
|
26500
|
+
const defRe = /^(\s*)def\s+([A-Za-z_]\w*)\s*\(/;
|
|
26501
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26502
|
+
const raw = lines[i2];
|
|
26503
|
+
const dm = raw.match(defRe);
|
|
26504
|
+
if (!dm) continue;
|
|
26505
|
+
const indent = dm[1].length;
|
|
26506
|
+
let hasRoute = false;
|
|
26507
|
+
for (let j = i2 - 1; j >= 0; j--) {
|
|
26508
|
+
const prev = lines[j].trim();
|
|
26509
|
+
if (prev === "" || prev.startsWith("#")) continue;
|
|
26510
|
+
if (!prev.startsWith("@")) break;
|
|
26511
|
+
if (/@\s*[A-Za-z_]\w*\s*\.\s*route\s*\(/.test(prev) || /@\s*route\s*\(/.test(prev)) {
|
|
26512
|
+
hasRoute = true;
|
|
26513
|
+
}
|
|
26514
|
+
}
|
|
26515
|
+
if (!hasRoute) continue;
|
|
26516
|
+
let end = lines.length;
|
|
26517
|
+
for (let k = i2 + 1; k < lines.length; k++) {
|
|
26518
|
+
const ln = lines[k];
|
|
26519
|
+
if (!ln.trim()) continue;
|
|
26520
|
+
const ind = ln.match(/^(\s*)/)?.[1].length ?? 0;
|
|
26521
|
+
if (ind <= indent) {
|
|
26522
|
+
end = k;
|
|
26523
|
+
break;
|
|
26524
|
+
}
|
|
26525
|
+
}
|
|
26526
|
+
fns.push({ name: dm[2], startLine: i2 + 1, endLine: end });
|
|
26527
|
+
}
|
|
26528
|
+
if (fns.length === 0) return out2;
|
|
26529
|
+
const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
|
|
26530
|
+
const escapeWrapRe = /\b(?:html\.escape|markupsafe\.escape|escape|markupsafe\.Markup\s*\.\s*escape|werkzeug\.utils\.escape)\s*\(/;
|
|
26531
|
+
for (const fn of fns) {
|
|
26532
|
+
const taintedVars = /* @__PURE__ */ new Set();
|
|
26533
|
+
for (let li = fn.startLine; li < fn.endLine; li++) {
|
|
26534
|
+
const raw = lines[li];
|
|
26535
|
+
const rm = raw.match(requestSourceRe);
|
|
26536
|
+
if (rm) taintedVars.add(rm[1]);
|
|
26537
|
+
}
|
|
26538
|
+
for (let li = fn.startLine; li < fn.endLine; li++) {
|
|
26539
|
+
const raw = lines[li];
|
|
26540
|
+
const trimmed = raw.trim();
|
|
26541
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
26542
|
+
const retMatch = trimmed.match(/^(?:return|yield)\s+(.+)$/);
|
|
26543
|
+
if (!retMatch) continue;
|
|
26544
|
+
const expr = retMatch[1];
|
|
26545
|
+
const inlineSource = /\brequest\s*\.\s*(?:args|form|values|files|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/.test(
|
|
26546
|
+
expr
|
|
26547
|
+
);
|
|
26548
|
+
const hasTaintedVar = [...taintedVars].some(
|
|
26549
|
+
(v) => new RegExp(`\\b${v}\\b`).test(expr)
|
|
26550
|
+
);
|
|
26551
|
+
if (!hasTaintedVar && !inlineSource) continue;
|
|
26552
|
+
if (escapeWrapRe.test(expr)) continue;
|
|
26553
|
+
const buildsHtml = /['"`]\s*\+/.test(expr) || /\+\s*['"`]/.test(expr) || // f-string with interpolation. Allow any non-`{` char between the
|
|
26554
|
+
// opening quote and the first `{` (Python f-strings may embed `"`
|
|
26555
|
+
// when single-quoted and vice-versa, e.g. f'<a href="{u}">').
|
|
26556
|
+
/\bf['"`][^{\n]*\{[^}]+\}/.test(expr) || /['"`]\s*%\s*[^=]/.test(expr) || /['"`]\.\s*format\s*\(/.test(expr);
|
|
26557
|
+
if (!buildsHtml) continue;
|
|
26558
|
+
out2.push({
|
|
26559
|
+
id: `xss-${file}-${li + 1}`,
|
|
26560
|
+
pass: "language-sources",
|
|
26561
|
+
category: "security",
|
|
26562
|
+
rule_id: "xss",
|
|
26563
|
+
cwe: "CWE-79",
|
|
26564
|
+
severity: "high",
|
|
26565
|
+
level: "error",
|
|
26566
|
+
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).",
|
|
26567
|
+
file,
|
|
26568
|
+
line: li + 1,
|
|
26569
|
+
snippet: trimmed.substring(0, 100)
|
|
26570
|
+
});
|
|
26571
|
+
break;
|
|
26572
|
+
}
|
|
26573
|
+
}
|
|
26574
|
+
return out2;
|
|
26575
|
+
}
|
|
26576
|
+
function findPythonJinjaMarkupXssFindings(code, file) {
|
|
26577
|
+
const out2 = [];
|
|
26578
|
+
const lines = code.split("\n");
|
|
26579
|
+
const importRe = /^\s*from\s+(?:markupsafe|flask)\s+import\s+(?:[^#\n]*\b)?Markup\b/m;
|
|
26580
|
+
const classDefRe = /^\s*class\s+Markup\b/m;
|
|
26581
|
+
if (!importRe.test(code)) return out2;
|
|
26582
|
+
if (classDefRe.test(code)) return out2;
|
|
26583
|
+
const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
|
|
26584
|
+
const taintedVars = /* @__PURE__ */ new Set();
|
|
26585
|
+
for (const line of lines) {
|
|
26586
|
+
const m = line.match(requestSourceRe);
|
|
26587
|
+
if (m) taintedVars.add(m[1]);
|
|
26588
|
+
}
|
|
26589
|
+
if (taintedVars.size === 0) return out2;
|
|
26590
|
+
if (!/\.\s*render\s*\(/.test(code)) return out2;
|
|
26591
|
+
const markupCallRe = /\bMarkup\s*\(\s*([A-Za-z_]\w*)\s*[,)]/g;
|
|
26592
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26593
|
+
const raw = lines[i2];
|
|
26594
|
+
const trimmed = raw.trim();
|
|
26595
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
26596
|
+
let mm;
|
|
26597
|
+
markupCallRe.lastIndex = 0;
|
|
26598
|
+
while ((mm = markupCallRe.exec(trimmed)) !== null) {
|
|
26599
|
+
const argName = mm[1];
|
|
26600
|
+
if (!taintedVars.has(argName)) continue;
|
|
26601
|
+
out2.push({
|
|
26602
|
+
id: `xss-${file}-${i2 + 1}`,
|
|
26603
|
+
pass: "language-sources",
|
|
26604
|
+
category: "security",
|
|
26605
|
+
rule_id: "xss",
|
|
26606
|
+
cwe: "CWE-79",
|
|
26607
|
+
severity: "high",
|
|
26608
|
+
level: "error",
|
|
26609
|
+
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.",
|
|
26610
|
+
file,
|
|
26611
|
+
line: i2 + 1,
|
|
26612
|
+
snippet: trimmed.substring(0, 100)
|
|
26613
|
+
});
|
|
26614
|
+
break;
|
|
26615
|
+
}
|
|
26616
|
+
}
|
|
26617
|
+
return out2;
|
|
26618
|
+
}
|
|
25947
26619
|
|
|
25948
26620
|
// src/analysis/passes/sink-filter-pass.ts
|
|
25949
26621
|
var JS_XSS_SANITIZERS = [
|