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.
@@ -125,4 +125,76 @@ export declare function findPythonPatternFindings(code: string, file: string): S
125
125
  * benign and is ignored.
126
126
  */
127
127
  export declare function findRustPatternFindings(code: string, file: string): SastFinding[];
128
+ /**
129
+ * Rust `hardcoded-credential` (CWE-798). Pattern:
130
+ * `(pub|const|static) <NAME>: &str = "literal";` where NAME matches
131
+ * /api[_]?key|secret|token|password|passwd|pwd|auth/i and the literal is
132
+ * non-trivial (length > 8 and not a placeholder).
133
+ */
134
+ export declare function findRustHardcodedCredentialFindings(code: string, file: string): SastFinding[];
135
+ /**
136
+ * Rust `insecure-cookie` (CWE-1004 / CWE-614). Pattern:
137
+ * `Cookie::build(...)` chain that explicitly calls `.secure(false)` or
138
+ * `.http_only(false)` (or both). The dedicated `insecure-cookie-pass.ts`
139
+ * handles the `format!("Set-Cookie: ...")` shape but not the actix-web
140
+ * builder chain.
141
+ */
142
+ export declare function findRustInsecureCookieFindings(code: string, file: string): SastFinding[];
143
+ /**
144
+ * Rust `jwt-verify-disabled` (CWE-347). Pattern:
145
+ * `.insecure_disable_signature_validation()` method call on a
146
+ * jsonwebtoken `Validation` value. Any presence of this method
147
+ * disables the signature check.
148
+ */
149
+ export declare function findRustJwtVerifyDisabledFindings(code: string, file: string): SastFinding[];
150
+ /**
151
+ * Rust `weak-crypto` (CWE-327). Pattern (raw ECB via `aes` crate):
152
+ * any line that calls `.encrypt_block(` or `.decrypt_block(` on a
153
+ * block-cipher receiver constructed via `Aes128::new` / `Aes192::new` /
154
+ * `Aes256::new` / `Aes128Ecb` / `Aes256Ecb`. We collect the cipher
155
+ * constructor lines in a first pass (variable name → seen) and emit
156
+ * on every `.encrypt_block(` / `.decrypt_block(` line in the same file.
157
+ *
158
+ * The wrapped CBC/GCM/CTR forms go through `Cbc::<Aes128, ...>::new`
159
+ * or `Aes128Gcm::new` — those do NOT call `.encrypt_block` directly
160
+ * and so are not matched.
161
+ */
162
+ export declare function findRustWeakCryptoEcbFindings(code: string, file: string): SastFinding[];
163
+ /**
164
+ * Java pattern findings — Sprint 78 (#190):
165
+ * - `jwt-verify-disabled` (CWE-347): bare `JWT.decode(<token>)` on the
166
+ * auth0 `com.auth0.jwt.JWT` class. `decode` is documented as
167
+ * "decode the token without performing any verification"; only
168
+ * `JWT.require(...).build().verify(token)` enforces the signature.
169
+ * - `tls-verify-disabled` (CWE-295): anonymous `X509TrustManager`
170
+ * implementation whose `checkServerTrusted` body is empty
171
+ * (returns void without raising). This trust-nothing implementation
172
+ * accepts every certificate.
173
+ */
174
+ export declare function findJavaPatternFindings(code: string, file: string): SastFinding[];
175
+ /**
176
+ * Go pattern findings — Sprint 78 (#190):
177
+ * - `weak-crypto` (CWE-327): raw ECB usage via `aes.NewCipher(...)`
178
+ * followed by a direct `<cipher>.Encrypt(` / `.Decrypt(` call on the
179
+ * constructed value (no `cipher.NewCBCEncrypter` / `NewGCM` / `NewCTR`
180
+ * wrapper). The Go stdlib `aes.Cipher` exposes `Encrypt` / `Decrypt`
181
+ * that operate on a single 16-byte block — calling these directly is
182
+ * ECB mode.
183
+ *
184
+ * Algorithm:
185
+ * 1. Collect every `<v>, _ := aes.NewCipher(...)` cipher variable.
186
+ * 2. If the file contains a `cipher.NewGCM(<v>)` / `cipher.NewCBC*(<v>)`
187
+ * / `cipher.NewCTR(<v>)` wrapping line for that variable, skip it
188
+ * (wrapped mode is not ECB).
189
+ * 3. Otherwise emit on every `<v>.Encrypt(` / `<v>.Decrypt(` line.
190
+ */
191
+ export declare function findGoPatternFindings(code: string, file: string): SastFinding[];
192
+ /**
193
+ * JS pattern findings — Sprint 78 (#190):
194
+ * - `xml-entity-expansion` (CWE-611 / CWE-776): libxmljs `parseXml`
195
+ * (and `parseXmlString`) called with `{ noent: true }` resolves
196
+ * external entities, enabling XXE / billion-laughs. The default is
197
+ * `noent: false`; only the explicit-true form is unsafe.
198
+ */
199
+ export declare function findJsPatternFindings(code: string, file: string): SastFinding[];
128
200
  //# sourceMappingURL=language-sources-pass.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAC3H,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqB9E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0C/B,CAAC;AAiCF,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAMD,qBAAa,mBAAoB,YAAW,YAAY,CAAC,qBAAqB,CAAC;IAC7E,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,qBAAqB;CAqN7C;AAgkBD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkG9E;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAwC5G;AAED,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBjD;AAyKD,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBpG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmCrB;AA+MD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAsKvF;AAiyDD,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,WAAW,EAAE,CAcf;AAoLD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA+HnF;AAMD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA2BjF"}
1
+ {"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"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"}
@@ -206,6 +206,11 @@ export class LanguageSourcesPass {
206
206
  if (language === 'go') {
207
207
  additionalSanitizers.push(...findGoMapAllowlistGuardSanitizers(code));
208
208
  additionalSanitizers.push(...findGoHtmlTemplateImportSanitizers(code));
209
+ // Sprint 78 (#190): Go ECB-mode weak-crypto detection.
210
+ const goMisconfigFindings = findGoPatternFindings(code, graph.ir.meta.file);
211
+ for (const finding of goMisconfigFindings) {
212
+ ctx.addFinding(finding);
213
+ }
209
214
  }
210
215
  // -- Python: safe-handler sanitizer detectors (cognium-dev #114 Sprint 31) --
211
216
  if (language === 'python') {
@@ -241,6 +246,21 @@ export class LanguageSourcesPass {
241
246
  for (const finding of rustMisconfigFindings) {
242
247
  ctx.addFinding(finding);
243
248
  }
249
+ // Sprint 78 (#190): additional Rust misconfig pattern detectors —
250
+ // hardcoded-credential, insecure-cookie (builder chain),
251
+ // jwt-verify-disabled, weak-crypto (raw ECB block ops).
252
+ for (const finding of findRustHardcodedCredentialFindings(code, graph.ir.meta.file)) {
253
+ ctx.addFinding(finding);
254
+ }
255
+ for (const finding of findRustInsecureCookieFindings(code, graph.ir.meta.file)) {
256
+ ctx.addFinding(finding);
257
+ }
258
+ for (const finding of findRustJwtVerifyDisabledFindings(code, graph.ir.meta.file)) {
259
+ ctx.addFinding(finding);
260
+ }
261
+ for (const finding of findRustWeakCryptoEcbFindings(code, graph.ir.meta.file)) {
262
+ ctx.addFinding(finding);
263
+ }
244
264
  }
245
265
  // -- JavaScript/TypeScript: Sprint 73 (#216 Pattern A + B) — ETE
246
266
  // sanitizer-chain recognition (JSON.parse / bcrypt.hash / csv
@@ -252,6 +272,10 @@ export class LanguageSourcesPass {
252
272
  additionalSanitizers.push(...findJsWrapperFunctionSanitizers(code));
253
273
  // Sprint 75 (#216 Pattern D): JS SSRF allow-list guard (var-aware).
254
274
  additionalSanitizers.push(...findJsSsrfAllowlistGuardSanitizers(code));
275
+ // Sprint 78 (#190): JS misconfig pattern findings — libxmljs noent:true.
276
+ for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
277
+ ctx.addFinding(finding);
278
+ }
255
279
  }
256
280
  // -- Java: Sprint 73 (#216 Pattern A) — Jackson readValue / Gson
257
281
  // fromJson recognized as ETE terminator (does not affect
@@ -263,6 +287,12 @@ export class LanguageSourcesPass {
263
287
  additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
264
288
  // Sprint 77a (#216 Pattern X): argv-form exec sanitizer.
265
289
  additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
290
+ // Sprint 78 (#190): Java misconfig pattern findings —
291
+ // jwt-verify-disabled (auth0 JWT.decode bare) +
292
+ // tls-verify-disabled (empty-body X509TrustManager).
293
+ for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
294
+ ctx.addFinding(finding);
295
+ }
266
296
  }
267
297
  // Sprint 70 (#151): cross-language env-secret → external-network exfiltration.
268
298
  // Pattern findings only — no taint flow required (composed-flow shape that
@@ -3710,4 +3740,368 @@ export function findRustPatternFindings(code, file) {
3710
3740
  }
3711
3741
  return out;
3712
3742
  }
3743
+ // ---------------------------------------------------------------------------
3744
+ // Sprint 78 (#190) — Tier-2 misconfig pattern extensions for Rust, Java, Go,
3745
+ // and JS that the dedicated misconfig passes don't yet recognize.
3746
+ // ---------------------------------------------------------------------------
3747
+ /**
3748
+ * Rust `hardcoded-credential` (CWE-798). Pattern:
3749
+ * `(pub|const|static) <NAME>: &str = "literal";` where NAME matches
3750
+ * /api[_]?key|secret|token|password|passwd|pwd|auth/i and the literal is
3751
+ * non-trivial (length > 8 and not a placeholder).
3752
+ */
3753
+ export function findRustHardcodedCredentialFindings(code, file) {
3754
+ const out = [];
3755
+ const lines = code.split('\n');
3756
+ const re = /\b(?:pub\s+)?(?:const|static)\s+([A-Z][A-Z0-9_]*)\s*:\s*&\s*'?[a-z_]*\s*str\s*=\s*"([^"]+)"/;
3757
+ const nameRe = /(?:^|_)(?:API[_]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|AUTH)(?:_|$)/i;
3758
+ for (let i = 0; i < lines.length; i++) {
3759
+ const raw = lines[i];
3760
+ const trimmed = raw.trim();
3761
+ if (!trimmed || trimmed.startsWith('//'))
3762
+ continue;
3763
+ const m = trimmed.match(re);
3764
+ if (!m)
3765
+ continue;
3766
+ const name = m[1];
3767
+ const value = m[2];
3768
+ if (!nameRe.test(name))
3769
+ continue;
3770
+ if (value.length < 8)
3771
+ continue;
3772
+ if (/^(?:xxx|todo|fixme|placeholder|changeme)/i.test(value))
3773
+ continue;
3774
+ out.push({
3775
+ id: `hardcoded-credential-${file}-${i + 1}`,
3776
+ pass: 'language-sources',
3777
+ category: 'security',
3778
+ rule_id: 'hardcoded-credential',
3779
+ cwe: 'CWE-798',
3780
+ severity: 'high',
3781
+ level: 'error',
3782
+ message: `Hardcoded credential: const ${name} contains a literal secret value`,
3783
+ file,
3784
+ line: i + 1,
3785
+ snippet: trimmed.substring(0, 100),
3786
+ });
3787
+ }
3788
+ return out;
3789
+ }
3790
+ /**
3791
+ * Rust `insecure-cookie` (CWE-1004 / CWE-614). Pattern:
3792
+ * `Cookie::build(...)` chain that explicitly calls `.secure(false)` or
3793
+ * `.http_only(false)` (or both). The dedicated `insecure-cookie-pass.ts`
3794
+ * handles the `format!("Set-Cookie: ...")` shape but not the actix-web
3795
+ * builder chain.
3796
+ */
3797
+ export function findRustInsecureCookieFindings(code, file) {
3798
+ const out = [];
3799
+ const lines = code.split('\n');
3800
+ const builderRe = /\bCookie\s*::\s*build\s*\(/;
3801
+ const insecureFlagRe = /\.\s*(?:secure|http_only)\s*\(\s*false\s*\)/;
3802
+ for (let i = 0; i < lines.length; i++) {
3803
+ const raw = lines[i];
3804
+ const trimmed = raw.trim();
3805
+ if (!trimmed || trimmed.startsWith('//'))
3806
+ continue;
3807
+ if (!builderRe.test(trimmed))
3808
+ continue;
3809
+ if (!insecureFlagRe.test(trimmed))
3810
+ continue;
3811
+ out.push({
3812
+ id: `insecure-cookie-${file}-${i + 1}`,
3813
+ pass: 'language-sources',
3814
+ category: 'security',
3815
+ rule_id: 'insecure-cookie',
3816
+ cwe: 'CWE-1004',
3817
+ severity: 'medium',
3818
+ level: 'warning',
3819
+ message: 'Insecure cookie: Cookie::build chain disables Secure / HttpOnly flag(s)',
3820
+ file,
3821
+ line: i + 1,
3822
+ snippet: trimmed.substring(0, 100),
3823
+ });
3824
+ }
3825
+ return out;
3826
+ }
3827
+ /**
3828
+ * Rust `jwt-verify-disabled` (CWE-347). Pattern:
3829
+ * `.insecure_disable_signature_validation()` method call on a
3830
+ * jsonwebtoken `Validation` value. Any presence of this method
3831
+ * disables the signature check.
3832
+ */
3833
+ export function findRustJwtVerifyDisabledFindings(code, file) {
3834
+ const out = [];
3835
+ const lines = code.split('\n');
3836
+ const re = /\.\s*insecure_disable_signature_validation\s*\(/;
3837
+ for (let i = 0; i < lines.length; i++) {
3838
+ const raw = lines[i];
3839
+ const trimmed = raw.trim();
3840
+ if (!trimmed || trimmed.startsWith('//'))
3841
+ continue;
3842
+ if (!re.test(trimmed))
3843
+ continue;
3844
+ out.push({
3845
+ id: `jwt-verify-disabled-${file}-${i + 1}`,
3846
+ pass: 'language-sources',
3847
+ category: 'security',
3848
+ rule_id: 'jwt-verify-disabled',
3849
+ cwe: 'CWE-347',
3850
+ severity: 'critical',
3851
+ level: 'error',
3852
+ message: 'JWT signature verification disabled: ' +
3853
+ 'Validation::insecure_disable_signature_validation() forfeits signature enforcement',
3854
+ file,
3855
+ line: i + 1,
3856
+ snippet: trimmed.substring(0, 100),
3857
+ });
3858
+ }
3859
+ return out;
3860
+ }
3861
+ /**
3862
+ * Rust `weak-crypto` (CWE-327). Pattern (raw ECB via `aes` crate):
3863
+ * any line that calls `.encrypt_block(` or `.decrypt_block(` on a
3864
+ * block-cipher receiver constructed via `Aes128::new` / `Aes192::new` /
3865
+ * `Aes256::new` / `Aes128Ecb` / `Aes256Ecb`. We collect the cipher
3866
+ * constructor lines in a first pass (variable name → seen) and emit
3867
+ * on every `.encrypt_block(` / `.decrypt_block(` line in the same file.
3868
+ *
3869
+ * The wrapped CBC/GCM/CTR forms go through `Cbc::<Aes128, ...>::new`
3870
+ * or `Aes128Gcm::new` — those do NOT call `.encrypt_block` directly
3871
+ * and so are not matched.
3872
+ */
3873
+ export function findRustWeakCryptoEcbFindings(code, file) {
3874
+ const out = [];
3875
+ const lines = code.split('\n');
3876
+ const ctorRe = /\bAes(?:128|192|256)(?:Ecb)?\s*::\s*new\s*\(/;
3877
+ const blockOpRe = /\.\s*(encrypt_block|decrypt_block)\s*\(/;
3878
+ // First: confirm the file uses raw block-cipher construction (Aes*::new)
3879
+ // — without that, a stray `.encrypt_block(` on some other type isn't
3880
+ // necessarily ECB.
3881
+ let sawCtor = false;
3882
+ for (const line of lines) {
3883
+ if (ctorRe.test(line)) {
3884
+ sawCtor = true;
3885
+ break;
3886
+ }
3887
+ }
3888
+ if (!sawCtor)
3889
+ return out;
3890
+ for (let i = 0; i < lines.length; i++) {
3891
+ const raw = lines[i];
3892
+ const trimmed = raw.trim();
3893
+ if (!trimmed || trimmed.startsWith('//'))
3894
+ continue;
3895
+ if (!blockOpRe.test(trimmed))
3896
+ continue;
3897
+ out.push({
3898
+ id: `weak-crypto-${file}-${i + 1}`,
3899
+ pass: 'language-sources',
3900
+ category: 'security',
3901
+ rule_id: 'weak-crypto',
3902
+ cwe: 'CWE-327',
3903
+ severity: 'high',
3904
+ level: 'error',
3905
+ message: 'Weak crypto (ECB mode): raw Aes::encrypt_block/decrypt_block ' +
3906
+ 'leaks repeating-block patterns. Use AES-GCM, AES-CTR, or ' +
3907
+ 'AES-CBC with an HMAC.',
3908
+ file,
3909
+ line: i + 1,
3910
+ snippet: trimmed.substring(0, 100),
3911
+ });
3912
+ }
3913
+ return out;
3914
+ }
3915
+ /**
3916
+ * Java pattern findings — Sprint 78 (#190):
3917
+ * - `jwt-verify-disabled` (CWE-347): bare `JWT.decode(<token>)` on the
3918
+ * auth0 `com.auth0.jwt.JWT` class. `decode` is documented as
3919
+ * "decode the token without performing any verification"; only
3920
+ * `JWT.require(...).build().verify(token)` enforces the signature.
3921
+ * - `tls-verify-disabled` (CWE-295): anonymous `X509TrustManager`
3922
+ * implementation whose `checkServerTrusted` body is empty
3923
+ * (returns void without raising). This trust-nothing implementation
3924
+ * accepts every certificate.
3925
+ */
3926
+ export function findJavaPatternFindings(code, file) {
3927
+ const out = [];
3928
+ const lines = code.split('\n');
3929
+ // jwt-verify-disabled: `JWT.decode(<expr>)`. Anchored to the `JWT.`
3930
+ // receiver to avoid matching unrelated `decode(` calls on Base64,
3931
+ // URLDecoder, etc. Guarded against the safer `JWT.require(...).build()
3932
+ // .verify(...)` chain by requiring `decode` to appear without
3933
+ // `.verify(` later on the same line.
3934
+ const jwtDecodeRe = /\bJWT\s*\.\s*decode\s*\(/;
3935
+ for (let i = 0; i < lines.length; i++) {
3936
+ const raw = lines[i];
3937
+ const trimmed = raw.trim();
3938
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('*'))
3939
+ continue;
3940
+ if (!jwtDecodeRe.test(trimmed))
3941
+ continue;
3942
+ if (/\.\s*verify\s*\(/.test(trimmed))
3943
+ continue;
3944
+ out.push({
3945
+ id: `jwt-verify-disabled-${file}-${i + 1}-decode`,
3946
+ pass: 'language-sources',
3947
+ category: 'security',
3948
+ rule_id: 'jwt-verify-disabled',
3949
+ cwe: 'CWE-347',
3950
+ severity: 'critical',
3951
+ level: 'error',
3952
+ message: 'JWT signature not verified: auth0 `JWT.decode(token)` parses ' +
3953
+ 'without checking the signature. Use `JWT.require(<algorithm>)' +
3954
+ '.build().verify(token)` to enforce verification.',
3955
+ file,
3956
+ line: i + 1,
3957
+ snippet: trimmed.substring(0, 100),
3958
+ });
3959
+ }
3960
+ // tls-verify-disabled: anonymous X509TrustManager with empty
3961
+ // checkServerTrusted body. Two-pass: locate the anonymous-class start
3962
+ // line (`new X509TrustManager() {`), then scan ahead for the
3963
+ // `checkServerTrusted(...)` method signature whose `{...}` body is
3964
+ // empty (no `throw`, no `if`).
3965
+ const anonStartRe = /\bnew\s+X509TrustManager\s*\(\s*\)\s*\{/;
3966
+ const checkServerSig = /\bcheckServerTrusted\s*\([^)]*\)\s*(?:throws\s+[^\{]*)?\{\s*\}/;
3967
+ for (let i = 0; i < lines.length; i++) {
3968
+ const raw = lines[i];
3969
+ if (!anonStartRe.test(raw))
3970
+ continue;
3971
+ // Scan up to 15 lines ahead for the empty-body checkServerTrusted.
3972
+ const end = Math.min(lines.length, i + 16);
3973
+ let foundAt = -1;
3974
+ for (let j = i; j < end; j++) {
3975
+ if (checkServerSig.test(lines[j])) {
3976
+ foundAt = j;
3977
+ break;
3978
+ }
3979
+ }
3980
+ if (foundAt < 0)
3981
+ continue;
3982
+ out.push({
3983
+ id: `tls-verify-disabled-${file}-${foundAt + 1}`,
3984
+ pass: 'language-sources',
3985
+ category: 'security',
3986
+ rule_id: 'tls-verify-disabled',
3987
+ cwe: 'CWE-295',
3988
+ severity: 'high',
3989
+ level: 'error',
3990
+ message: 'TLS certificate verification disabled: anonymous X509TrustManager ' +
3991
+ 'with empty checkServerTrusted body accepts every certificate.',
3992
+ file,
3993
+ line: foundAt + 1,
3994
+ snippet: lines[foundAt].trim().substring(0, 100),
3995
+ });
3996
+ }
3997
+ return out;
3998
+ }
3999
+ /**
4000
+ * Go pattern findings — Sprint 78 (#190):
4001
+ * - `weak-crypto` (CWE-327): raw ECB usage via `aes.NewCipher(...)`
4002
+ * followed by a direct `<cipher>.Encrypt(` / `.Decrypt(` call on the
4003
+ * constructed value (no `cipher.NewCBCEncrypter` / `NewGCM` / `NewCTR`
4004
+ * wrapper). The Go stdlib `aes.Cipher` exposes `Encrypt` / `Decrypt`
4005
+ * that operate on a single 16-byte block — calling these directly is
4006
+ * ECB mode.
4007
+ *
4008
+ * Algorithm:
4009
+ * 1. Collect every `<v>, _ := aes.NewCipher(...)` cipher variable.
4010
+ * 2. If the file contains a `cipher.NewGCM(<v>)` / `cipher.NewCBC*(<v>)`
4011
+ * / `cipher.NewCTR(<v>)` wrapping line for that variable, skip it
4012
+ * (wrapped mode is not ECB).
4013
+ * 3. Otherwise emit on every `<v>.Encrypt(` / `<v>.Decrypt(` line.
4014
+ */
4015
+ export function findGoPatternFindings(code, file) {
4016
+ const out = [];
4017
+ const lines = code.split('\n');
4018
+ const cipherVars = new Set();
4019
+ const ctorRe = /\b([a-zA-Z_]\w*)\s*(?:,\s*[a-zA-Z_]\w*)?\s*:?=\s*aes\.NewCipher\s*\(/;
4020
+ for (const line of lines) {
4021
+ const m = line.match(ctorRe);
4022
+ if (m)
4023
+ cipherVars.add(m[1]);
4024
+ }
4025
+ if (cipherVars.size === 0)
4026
+ return out;
4027
+ // Drop wrapped ciphers (CBC/GCM/CTR/OFB/CFB) — those are not ECB.
4028
+ for (const v of Array.from(cipherVars)) {
4029
+ const wrapRe = new RegExp(`\\bcipher\\.New(?:GCM|CBCEncrypter|CBCDecrypter|CTR|OFB|CFBEncrypter|CFBDecrypter)\\s*\\(\\s*${v}\\b`);
4030
+ for (const line of lines) {
4031
+ if (wrapRe.test(line)) {
4032
+ cipherVars.delete(v);
4033
+ break;
4034
+ }
4035
+ }
4036
+ }
4037
+ if (cipherVars.size === 0)
4038
+ return out;
4039
+ for (let i = 0; i < lines.length; i++) {
4040
+ const raw = lines[i];
4041
+ const trimmed = raw.trim();
4042
+ if (!trimmed || trimmed.startsWith('//'))
4043
+ continue;
4044
+ for (const v of cipherVars) {
4045
+ const opRe = new RegExp(`\\b${v}\\s*\\.\\s*(?:Encrypt|Decrypt)\\s*\\(`);
4046
+ if (!opRe.test(trimmed))
4047
+ continue;
4048
+ out.push({
4049
+ id: `weak-crypto-${file}-${i + 1}`,
4050
+ pass: 'language-sources',
4051
+ category: 'security',
4052
+ rule_id: 'weak-crypto',
4053
+ cwe: 'CWE-327',
4054
+ severity: 'high',
4055
+ level: 'error',
4056
+ message: 'Weak crypto (ECB mode): raw aes.Cipher.Encrypt/Decrypt on a ' +
4057
+ 'block leaks repeating-block patterns. Wrap with cipher.NewGCM, ' +
4058
+ 'cipher.NewCTR, or cipher.NewCBCEncrypter + HMAC.',
4059
+ file,
4060
+ line: i + 1,
4061
+ snippet: trimmed.substring(0, 100),
4062
+ });
4063
+ break;
4064
+ }
4065
+ }
4066
+ return out;
4067
+ }
4068
+ /**
4069
+ * JS pattern findings — Sprint 78 (#190):
4070
+ * - `xml-entity-expansion` (CWE-611 / CWE-776): libxmljs `parseXml`
4071
+ * (and `parseXmlString`) called with `{ noent: true }` resolves
4072
+ * external entities, enabling XXE / billion-laughs. The default is
4073
+ * `noent: false`; only the explicit-true form is unsafe.
4074
+ */
4075
+ export function findJsPatternFindings(code, file) {
4076
+ const out = [];
4077
+ const lines = code.split('\n');
4078
+ const parseRe = /\blibxml(?:js)?\s*\.\s*parseXml(?:String)?\s*\(/;
4079
+ const noentTrueRe = /\bnoent\s*:\s*true\b/;
4080
+ for (let i = 0; i < lines.length; i++) {
4081
+ const raw = lines[i];
4082
+ const trimmed = raw.trim();
4083
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('*'))
4084
+ continue;
4085
+ if (!parseRe.test(trimmed))
4086
+ continue;
4087
+ if (!noentTrueRe.test(trimmed))
4088
+ continue;
4089
+ out.push({
4090
+ id: `xml-entity-expansion-${file}-${i + 1}`,
4091
+ pass: 'language-sources',
4092
+ category: 'security',
4093
+ rule_id: 'xml-entity-expansion',
4094
+ cwe: 'CWE-611',
4095
+ severity: 'high',
4096
+ level: 'error',
4097
+ message: 'XML external entity resolution enabled: libxmljs parseXml ' +
4098
+ 'called with `noent: true` resolves external entities (XXE / ' +
4099
+ 'billion-laughs). Omit the flag or set `noent: false`.',
4100
+ file,
4101
+ line: i + 1,
4102
+ snippet: trimmed.substring(0, 100),
4103
+ });
4104
+ }
4105
+ return out;
4106
+ }
3713
4107
  //# sourceMappingURL=language-sources-pass.js.map