circle-ir 3.128.0 → 3.129.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 +72 -0
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +394 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +268 -0
- package/package.json +1 -1
|
@@ -23650,6 +23650,10 @@ 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
|
+
}
|
|
23653
23657
|
}
|
|
23654
23658
|
if (language === "python") {
|
|
23655
23659
|
additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
|
|
@@ -23671,6 +23675,18 @@ var LanguageSourcesPass = class {
|
|
|
23671
23675
|
for (const finding of rustMisconfigFindings) {
|
|
23672
23676
|
ctx.addFinding(finding);
|
|
23673
23677
|
}
|
|
23678
|
+
for (const finding of findRustHardcodedCredentialFindings(code, graph.ir.meta.file)) {
|
|
23679
|
+
ctx.addFinding(finding);
|
|
23680
|
+
}
|
|
23681
|
+
for (const finding of findRustInsecureCookieFindings(code, graph.ir.meta.file)) {
|
|
23682
|
+
ctx.addFinding(finding);
|
|
23683
|
+
}
|
|
23684
|
+
for (const finding of findRustJwtVerifyDisabledFindings(code, graph.ir.meta.file)) {
|
|
23685
|
+
ctx.addFinding(finding);
|
|
23686
|
+
}
|
|
23687
|
+
for (const finding of findRustWeakCryptoEcbFindings(code, graph.ir.meta.file)) {
|
|
23688
|
+
ctx.addFinding(finding);
|
|
23689
|
+
}
|
|
23674
23690
|
}
|
|
23675
23691
|
if (language === "javascript" || language === "typescript" || language === "htmljs") {
|
|
23676
23692
|
additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
|
|
@@ -23678,6 +23694,9 @@ var LanguageSourcesPass = class {
|
|
|
23678
23694
|
additionalSanitizers.push(...findJsCsvFormulaPrefixSanitizers(code));
|
|
23679
23695
|
additionalSanitizers.push(...findJsWrapperFunctionSanitizers(code));
|
|
23680
23696
|
additionalSanitizers.push(...findJsSsrfAllowlistGuardSanitizers(code));
|
|
23697
|
+
for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
|
|
23698
|
+
ctx.addFinding(finding);
|
|
23699
|
+
}
|
|
23681
23700
|
}
|
|
23682
23701
|
if (language === "java") {
|
|
23683
23702
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
@@ -23686,6 +23705,9 @@ var LanguageSourcesPass = class {
|
|
|
23686
23705
|
);
|
|
23687
23706
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
23688
23707
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
23708
|
+
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
23709
|
+
ctx.addFinding(finding);
|
|
23710
|
+
}
|
|
23689
23711
|
}
|
|
23690
23712
|
if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
|
|
23691
23713
|
const exfilFindings = findExternalSecretExfiltrationFindings(
|
|
@@ -25944,6 +25966,252 @@ function findRustPatternFindings(code, file) {
|
|
|
25944
25966
|
}
|
|
25945
25967
|
return out2;
|
|
25946
25968
|
}
|
|
25969
|
+
function findRustHardcodedCredentialFindings(code, file) {
|
|
25970
|
+
const out2 = [];
|
|
25971
|
+
const lines = code.split("\n");
|
|
25972
|
+
const re = /\b(?:pub\s+)?(?:const|static)\s+([A-Z][A-Z0-9_]*)\s*:\s*&\s*'?[a-z_]*\s*str\s*=\s*"([^"]+)"/;
|
|
25973
|
+
const nameRe = /(?:^|_)(?:API[_]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|AUTH)(?:_|$)/i;
|
|
25974
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25975
|
+
const raw = lines[i2];
|
|
25976
|
+
const trimmed = raw.trim();
|
|
25977
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
25978
|
+
const m = trimmed.match(re);
|
|
25979
|
+
if (!m) continue;
|
|
25980
|
+
const name2 = m[1];
|
|
25981
|
+
const value = m[2];
|
|
25982
|
+
if (!nameRe.test(name2)) continue;
|
|
25983
|
+
if (value.length < 8) continue;
|
|
25984
|
+
if (/^(?:xxx|todo|fixme|placeholder|changeme)/i.test(value)) continue;
|
|
25985
|
+
out2.push({
|
|
25986
|
+
id: `hardcoded-credential-${file}-${i2 + 1}`,
|
|
25987
|
+
pass: "language-sources",
|
|
25988
|
+
category: "security",
|
|
25989
|
+
rule_id: "hardcoded-credential",
|
|
25990
|
+
cwe: "CWE-798",
|
|
25991
|
+
severity: "high",
|
|
25992
|
+
level: "error",
|
|
25993
|
+
message: `Hardcoded credential: const ${name2} contains a literal secret value`,
|
|
25994
|
+
file,
|
|
25995
|
+
line: i2 + 1,
|
|
25996
|
+
snippet: trimmed.substring(0, 100)
|
|
25997
|
+
});
|
|
25998
|
+
}
|
|
25999
|
+
return out2;
|
|
26000
|
+
}
|
|
26001
|
+
function findRustInsecureCookieFindings(code, file) {
|
|
26002
|
+
const out2 = [];
|
|
26003
|
+
const lines = code.split("\n");
|
|
26004
|
+
const builderRe = /\bCookie\s*::\s*build\s*\(/;
|
|
26005
|
+
const insecureFlagRe = /\.\s*(?:secure|http_only)\s*\(\s*false\s*\)/;
|
|
26006
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26007
|
+
const raw = lines[i2];
|
|
26008
|
+
const trimmed = raw.trim();
|
|
26009
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26010
|
+
if (!builderRe.test(trimmed)) continue;
|
|
26011
|
+
if (!insecureFlagRe.test(trimmed)) continue;
|
|
26012
|
+
out2.push({
|
|
26013
|
+
id: `insecure-cookie-${file}-${i2 + 1}`,
|
|
26014
|
+
pass: "language-sources",
|
|
26015
|
+
category: "security",
|
|
26016
|
+
rule_id: "insecure-cookie",
|
|
26017
|
+
cwe: "CWE-1004",
|
|
26018
|
+
severity: "medium",
|
|
26019
|
+
level: "warning",
|
|
26020
|
+
message: "Insecure cookie: Cookie::build chain disables Secure / HttpOnly flag(s)",
|
|
26021
|
+
file,
|
|
26022
|
+
line: i2 + 1,
|
|
26023
|
+
snippet: trimmed.substring(0, 100)
|
|
26024
|
+
});
|
|
26025
|
+
}
|
|
26026
|
+
return out2;
|
|
26027
|
+
}
|
|
26028
|
+
function findRustJwtVerifyDisabledFindings(code, file) {
|
|
26029
|
+
const out2 = [];
|
|
26030
|
+
const lines = code.split("\n");
|
|
26031
|
+
const re = /\.\s*insecure_disable_signature_validation\s*\(/;
|
|
26032
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26033
|
+
const raw = lines[i2];
|
|
26034
|
+
const trimmed = raw.trim();
|
|
26035
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26036
|
+
if (!re.test(trimmed)) continue;
|
|
26037
|
+
out2.push({
|
|
26038
|
+
id: `jwt-verify-disabled-${file}-${i2 + 1}`,
|
|
26039
|
+
pass: "language-sources",
|
|
26040
|
+
category: "security",
|
|
26041
|
+
rule_id: "jwt-verify-disabled",
|
|
26042
|
+
cwe: "CWE-347",
|
|
26043
|
+
severity: "critical",
|
|
26044
|
+
level: "error",
|
|
26045
|
+
message: "JWT signature verification disabled: Validation::insecure_disable_signature_validation() forfeits signature enforcement",
|
|
26046
|
+
file,
|
|
26047
|
+
line: i2 + 1,
|
|
26048
|
+
snippet: trimmed.substring(0, 100)
|
|
26049
|
+
});
|
|
26050
|
+
}
|
|
26051
|
+
return out2;
|
|
26052
|
+
}
|
|
26053
|
+
function findRustWeakCryptoEcbFindings(code, file) {
|
|
26054
|
+
const out2 = [];
|
|
26055
|
+
const lines = code.split("\n");
|
|
26056
|
+
const ctorRe = /\bAes(?:128|192|256)(?:Ecb)?\s*::\s*new\s*\(/;
|
|
26057
|
+
const blockOpRe = /\.\s*(encrypt_block|decrypt_block)\s*\(/;
|
|
26058
|
+
let sawCtor = false;
|
|
26059
|
+
for (const line of lines) {
|
|
26060
|
+
if (ctorRe.test(line)) {
|
|
26061
|
+
sawCtor = true;
|
|
26062
|
+
break;
|
|
26063
|
+
}
|
|
26064
|
+
}
|
|
26065
|
+
if (!sawCtor) return out2;
|
|
26066
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26067
|
+
const raw = lines[i2];
|
|
26068
|
+
const trimmed = raw.trim();
|
|
26069
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26070
|
+
if (!blockOpRe.test(trimmed)) continue;
|
|
26071
|
+
out2.push({
|
|
26072
|
+
id: `weak-crypto-${file}-${i2 + 1}`,
|
|
26073
|
+
pass: "language-sources",
|
|
26074
|
+
category: "security",
|
|
26075
|
+
rule_id: "weak-crypto",
|
|
26076
|
+
cwe: "CWE-327",
|
|
26077
|
+
severity: "high",
|
|
26078
|
+
level: "error",
|
|
26079
|
+
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.",
|
|
26080
|
+
file,
|
|
26081
|
+
line: i2 + 1,
|
|
26082
|
+
snippet: trimmed.substring(0, 100)
|
|
26083
|
+
});
|
|
26084
|
+
}
|
|
26085
|
+
return out2;
|
|
26086
|
+
}
|
|
26087
|
+
function findJavaPatternFindings(code, file) {
|
|
26088
|
+
const out2 = [];
|
|
26089
|
+
const lines = code.split("\n");
|
|
26090
|
+
const jwtDecodeRe = /\bJWT\s*\.\s*decode\s*\(/;
|
|
26091
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26092
|
+
const raw = lines[i2];
|
|
26093
|
+
const trimmed = raw.trim();
|
|
26094
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
26095
|
+
if (!jwtDecodeRe.test(trimmed)) continue;
|
|
26096
|
+
if (/\.\s*verify\s*\(/.test(trimmed)) continue;
|
|
26097
|
+
out2.push({
|
|
26098
|
+
id: `jwt-verify-disabled-${file}-${i2 + 1}-decode`,
|
|
26099
|
+
pass: "language-sources",
|
|
26100
|
+
category: "security",
|
|
26101
|
+
rule_id: "jwt-verify-disabled",
|
|
26102
|
+
cwe: "CWE-347",
|
|
26103
|
+
severity: "critical",
|
|
26104
|
+
level: "error",
|
|
26105
|
+
message: "JWT signature not verified: auth0 `JWT.decode(token)` parses without checking the signature. Use `JWT.require(<algorithm>).build().verify(token)` to enforce verification.",
|
|
26106
|
+
file,
|
|
26107
|
+
line: i2 + 1,
|
|
26108
|
+
snippet: trimmed.substring(0, 100)
|
|
26109
|
+
});
|
|
26110
|
+
}
|
|
26111
|
+
const anonStartRe = /\bnew\s+X509TrustManager\s*\(\s*\)\s*\{/;
|
|
26112
|
+
const checkServerSig = /\bcheckServerTrusted\s*\([^)]*\)\s*(?:throws\s+[^\{]*)?\{\s*\}/;
|
|
26113
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26114
|
+
const raw = lines[i2];
|
|
26115
|
+
if (!anonStartRe.test(raw)) continue;
|
|
26116
|
+
const end = Math.min(lines.length, i2 + 16);
|
|
26117
|
+
let foundAt = -1;
|
|
26118
|
+
for (let j = i2; j < end; j++) {
|
|
26119
|
+
if (checkServerSig.test(lines[j])) {
|
|
26120
|
+
foundAt = j;
|
|
26121
|
+
break;
|
|
26122
|
+
}
|
|
26123
|
+
}
|
|
26124
|
+
if (foundAt < 0) continue;
|
|
26125
|
+
out2.push({
|
|
26126
|
+
id: `tls-verify-disabled-${file}-${foundAt + 1}`,
|
|
26127
|
+
pass: "language-sources",
|
|
26128
|
+
category: "security",
|
|
26129
|
+
rule_id: "tls-verify-disabled",
|
|
26130
|
+
cwe: "CWE-295",
|
|
26131
|
+
severity: "high",
|
|
26132
|
+
level: "error",
|
|
26133
|
+
message: "TLS certificate verification disabled: anonymous X509TrustManager with empty checkServerTrusted body accepts every certificate.",
|
|
26134
|
+
file,
|
|
26135
|
+
line: foundAt + 1,
|
|
26136
|
+
snippet: lines[foundAt].trim().substring(0, 100)
|
|
26137
|
+
});
|
|
26138
|
+
}
|
|
26139
|
+
return out2;
|
|
26140
|
+
}
|
|
26141
|
+
function findGoPatternFindings(code, file) {
|
|
26142
|
+
const out2 = [];
|
|
26143
|
+
const lines = code.split("\n");
|
|
26144
|
+
const cipherVars = /* @__PURE__ */ new Set();
|
|
26145
|
+
const ctorRe = /\b([a-zA-Z_]\w*)\s*(?:,\s*[a-zA-Z_]\w*)?\s*:?=\s*aes\.NewCipher\s*\(/;
|
|
26146
|
+
for (const line of lines) {
|
|
26147
|
+
const m = line.match(ctorRe);
|
|
26148
|
+
if (m) cipherVars.add(m[1]);
|
|
26149
|
+
}
|
|
26150
|
+
if (cipherVars.size === 0) return out2;
|
|
26151
|
+
for (const v of Array.from(cipherVars)) {
|
|
26152
|
+
const wrapRe = new RegExp(
|
|
26153
|
+
`\\bcipher\\.New(?:GCM|CBCEncrypter|CBCDecrypter|CTR|OFB|CFBEncrypter|CFBDecrypter)\\s*\\(\\s*${v}\\b`
|
|
26154
|
+
);
|
|
26155
|
+
for (const line of lines) {
|
|
26156
|
+
if (wrapRe.test(line)) {
|
|
26157
|
+
cipherVars.delete(v);
|
|
26158
|
+
break;
|
|
26159
|
+
}
|
|
26160
|
+
}
|
|
26161
|
+
}
|
|
26162
|
+
if (cipherVars.size === 0) return out2;
|
|
26163
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26164
|
+
const raw = lines[i2];
|
|
26165
|
+
const trimmed = raw.trim();
|
|
26166
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
26167
|
+
for (const v of cipherVars) {
|
|
26168
|
+
const opRe = new RegExp(`\\b${v}\\s*\\.\\s*(?:Encrypt|Decrypt)\\s*\\(`);
|
|
26169
|
+
if (!opRe.test(trimmed)) continue;
|
|
26170
|
+
out2.push({
|
|
26171
|
+
id: `weak-crypto-${file}-${i2 + 1}`,
|
|
26172
|
+
pass: "language-sources",
|
|
26173
|
+
category: "security",
|
|
26174
|
+
rule_id: "weak-crypto",
|
|
26175
|
+
cwe: "CWE-327",
|
|
26176
|
+
severity: "high",
|
|
26177
|
+
level: "error",
|
|
26178
|
+
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.",
|
|
26179
|
+
file,
|
|
26180
|
+
line: i2 + 1,
|
|
26181
|
+
snippet: trimmed.substring(0, 100)
|
|
26182
|
+
});
|
|
26183
|
+
break;
|
|
26184
|
+
}
|
|
26185
|
+
}
|
|
26186
|
+
return out2;
|
|
26187
|
+
}
|
|
26188
|
+
function findJsPatternFindings(code, file) {
|
|
26189
|
+
const out2 = [];
|
|
26190
|
+
const lines = code.split("\n");
|
|
26191
|
+
const parseRe = /\blibxml(?:js)?\s*\.\s*parseXml(?:String)?\s*\(/;
|
|
26192
|
+
const noentTrueRe = /\bnoent\s*:\s*true\b/;
|
|
26193
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
26194
|
+
const raw = lines[i2];
|
|
26195
|
+
const trimmed = raw.trim();
|
|
26196
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
26197
|
+
if (!parseRe.test(trimmed)) continue;
|
|
26198
|
+
if (!noentTrueRe.test(trimmed)) continue;
|
|
26199
|
+
out2.push({
|
|
26200
|
+
id: `xml-entity-expansion-${file}-${i2 + 1}`,
|
|
26201
|
+
pass: "language-sources",
|
|
26202
|
+
category: "security",
|
|
26203
|
+
rule_id: "xml-entity-expansion",
|
|
26204
|
+
cwe: "CWE-611",
|
|
26205
|
+
severity: "high",
|
|
26206
|
+
level: "error",
|
|
26207
|
+
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`.",
|
|
26208
|
+
file,
|
|
26209
|
+
line: i2 + 1,
|
|
26210
|
+
snippet: trimmed.substring(0, 100)
|
|
26211
|
+
});
|
|
26212
|
+
}
|
|
26213
|
+
return out2;
|
|
26214
|
+
}
|
|
25947
26215
|
|
|
25948
26216
|
// src/analysis/passes/sink-filter-pass.ts
|
|
25949
26217
|
var JS_XSS_SANITIZERS = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.129.0",
|
|
4
4
|
"description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|