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.
@@ -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.128.0",
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",