circle-ir 3.52.0 → 3.53.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.
@@ -10621,6 +10621,13 @@ var DEFAULT_SOURCES = [
10621
10621
  { method: "getFileName", class: "BodyPart", type: "file_input", severity: "high", return_tainted: true },
10622
10622
  { method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
10623
10623
  { method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
10624
+ // Archive entry names (Zip-Slip / Tar-Slip CWE-22, issue #52)
10625
+ // entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
10626
+ // is a classic Zip-Slip vulnerability.
10627
+ { method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
10628
+ { method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
10629
+ { method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
10630
+ { method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
10624
10631
  // Command line arguments
10625
10632
  { method: "getArgs", type: "io_input", severity: "high", return_tainted: true },
10626
10633
  { method: "getOptionValue", class: "CommandLine", type: "io_input", severity: "high", return_tainted: true },
@@ -11055,7 +11062,7 @@ var DEFAULT_SINKS = [
11055
11062
  { method: "staticFileLocation", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11056
11063
  // Zip/archive handling
11057
11064
  { method: "getEntry", class: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11058
- { method: "getName", class: "ZipEntry", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [] },
11065
+ // ZipEntry.getName moved to file_sources.yaml as a taint SOURCE (type=archive_entry, issue #52)
11059
11066
  // Resource loading classes (various frameworks)
11060
11067
  { method: "ClassPathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11061
11068
  { method: "FileSystemResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -12738,10 +12745,11 @@ function matchesSourcePattern(call, pattern) {
12738
12745
  return false;
12739
12746
  }
12740
12747
  if (pattern.class && pattern.class !== "constructor") {
12741
- if (!call.receiver) {
12748
+ if (call.receiver_type && call.receiver_type === pattern.class) {
12749
+ } else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
12750
+ } else if (!call.receiver) {
12742
12751
  return false;
12743
- }
12744
- if (!receiverMightBeClass(call.receiver, pattern.class)) {
12752
+ } else if (!receiverMightBeClass(call.receiver, pattern.class)) {
12745
12753
  return false;
12746
12754
  }
12747
12755
  }
@@ -12999,13 +13007,14 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12999
13007
  if (pattern.class === "constructor") {
13000
13008
  return true;
13001
13009
  }
13002
- if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
13010
+ if (call.receiver_type && call.receiver_type === pattern.class) {
13011
+ } else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
13012
+ } else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
13003
13013
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
13004
13014
  return true;
13005
13015
  }
13006
13016
  return false;
13007
- }
13008
- if (!call.receiver) {
13017
+ } else if (!call.receiver && !call.receiver_type) {
13009
13018
  return false;
13010
13019
  }
13011
13020
  }
@@ -27347,6 +27356,50 @@ function literalAlgo2(call, position) {
27347
27356
  const cleaned = stripQuotes5(raw);
27348
27357
  return cleaned || null;
27349
27358
  }
27359
+ function detectStaticIvJava(call) {
27360
+ const arg = call.arguments.find((a) => a.position === 0);
27361
+ if (!arg) return null;
27362
+ const expr = (arg.literal ?? arg.expression ?? "").trim();
27363
+ if (!expr) return null;
27364
+ if (/^new\s+byte\s*\[[^\]]*\]\s*$/.test(expr)) {
27365
+ return `zero-filled ${expr}`;
27366
+ }
27367
+ if (/^new\s+byte\s*\[\s*\]\s*\{[^}]*\}\s*$/.test(expr)) {
27368
+ return `literal byte[] initializer`;
27369
+ }
27370
+ if (/^"[^"]*"\.getBytes\s*\(/.test(expr)) {
27371
+ return `literal string .getBytes()`;
27372
+ }
27373
+ if (/^"[^"]*"$/.test(expr)) {
27374
+ return `literal string`;
27375
+ }
27376
+ return null;
27377
+ }
27378
+ function isJavaCtor(call, className) {
27379
+ if (call.is_constructor === true) return true;
27380
+ if (call.receiver) return false;
27381
+ if (call.receiver_type === className) return true;
27382
+ if ((call.receiver_type_fqn ?? "").endsWith("." + className)) return true;
27383
+ return false;
27384
+ }
27385
+ function detectHardcodedKeyJava(call) {
27386
+ const arg = call.arguments.find((a) => a.position === 0);
27387
+ if (!arg) return null;
27388
+ const expr = (arg.literal ?? arg.expression ?? "").trim();
27389
+ if (!expr) return null;
27390
+ if (/^"[^"]*"\.getBytes\s*\(/.test(expr)) return `literal string .getBytes()`;
27391
+ if (/^new\s+byte\s*\[\s*\]\s*\{[^}]*\}\s*$/.test(expr)) return `literal byte[] initializer`;
27392
+ if (/^"[^"]*"$/.test(expr)) return `literal string`;
27393
+ return null;
27394
+ }
27395
+ var ISSUE_CWE = {
27396
+ "weak-cipher": "CWE-327",
27397
+ "ecb-mode": "CWE-327",
27398
+ "deprecated-api": "CWE-327",
27399
+ "static-iv": "CWE-329",
27400
+ "hardcoded-key": "CWE-321",
27401
+ "weak-rsa-key": "CWE-326"
27402
+ };
27350
27403
  var WeakCryptoPass = class {
27351
27404
  name = "weak-crypto";
27352
27405
  category = "security";
@@ -27365,13 +27418,13 @@ var WeakCryptoPass = class {
27365
27418
  pass: this.name,
27366
27419
  category: this.category,
27367
27420
  rule_id: this.name,
27368
- cwe: "CWE-327",
27421
+ cwe: ISSUE_CWE[det.issue],
27369
27422
  severity: "high",
27370
27423
  level: "error",
27371
27424
  message,
27372
27425
  file,
27373
27426
  line,
27374
- fix: "Use AES-GCM (authenticated) or ChaCha20-Poly1305. Avoid DES, 3DES, RC2, RC4, Blowfish, and ECB mode. For asymmetric encryption use RSA-OAEP with \u22652048-bit keys or modern curve-based schemes.",
27427
+ fix: this.buildFix(det.issue),
27375
27428
  evidence: { ...det, language }
27376
27429
  });
27377
27430
  }
@@ -27386,10 +27439,28 @@ var WeakCryptoPass = class {
27386
27439
  return `ECB block-cipher mode used via \`${det.api}\` (\`${det.detail}\`). ECB leaks plaintext structure (identical blocks \u2192 identical ciphertext) and is not semantically secure.`;
27387
27440
  case "deprecated-api":
27388
27441
  return `Deprecated crypto API \`${det.api}\` used (no IV: \`${det.detail}\`). This API derives the key/IV from a password in an insecure way.`;
27442
+ case "static-iv":
27443
+ return `Static or zero-valued IV passed to \`${det.api}\` (\`${det.detail}\`). Reusing a fixed IV with CBC/CTR/GCM breaks confidentiality and, for GCM, can leak the authentication key.`;
27444
+ case "hardcoded-key":
27445
+ return `Hardcoded symmetric key material passed to \`${det.api}\` (\`${det.detail}\`). Keys embedded in source code are trivially recoverable from binaries and shared across deployments \u2014 they provide no confidentiality.`;
27446
+ case "weak-rsa-key":
27447
+ return `Weak RSA key size \`${det.detail}\` requested via \`${det.api}\`. RSA keys below 2048 bits are factorable and not compliant with NIST SP 800-57 / FIPS 186-5.`;
27389
27448
  default:
27390
27449
  return `Weak cryptography: ${det.detail} (${det.api})`;
27391
27450
  }
27392
27451
  }
27452
+ buildFix(issue) {
27453
+ switch (issue) {
27454
+ case "static-iv":
27455
+ return "Generate a fresh random IV per message using SecureRandom: `byte[] iv = new byte[12]; SecureRandom.getInstanceStrong().nextBytes(iv); new IvParameterSpec(iv);` and prepend it to the ciphertext.";
27456
+ case "hardcoded-key":
27457
+ return "Load the key from a secure key management system (HSM, KMS, Vault) or platform keystore. Never embed key material in source code.";
27458
+ case "weak-rsa-key":
27459
+ return "Initialize KeyPairGenerator with at least 2048 bits (preferably 3072 or 4096) for RSA, or switch to EC keys (P-256+).";
27460
+ default:
27461
+ return "Use AES-GCM (authenticated) or ChaCha20-Poly1305. Avoid DES, 3DES, RC2, RC4, Blowfish, and ECB mode. For asymmetric encryption use RSA-OAEP with \u22652048-bit keys or modern curve-based schemes.";
27462
+ }
27463
+ }
27393
27464
  detect(call, language) {
27394
27465
  const method = call.method_name;
27395
27466
  const receiver = call.receiver ?? "";
@@ -27405,6 +27476,33 @@ var WeakCryptoPass = class {
27405
27476
  if (ecb) out2.push({ issue: "ecb-mode", detail: spec, api });
27406
27477
  }
27407
27478
  }
27479
+ if (method === "IvParameterSpec" && isJavaCtor(call, "IvParameterSpec")) {
27480
+ const ivDetail = detectStaticIvJava(call);
27481
+ if (ivDetail) {
27482
+ out2.push({ issue: "static-iv", detail: ivDetail, api: "new IvParameterSpec" });
27483
+ }
27484
+ }
27485
+ if (method === "SecretKeySpec" && isJavaCtor(call, "SecretKeySpec")) {
27486
+ const keyDetail = detectHardcodedKeyJava(call);
27487
+ if (keyDetail) {
27488
+ out2.push({ issue: "hardcoded-key", detail: keyDetail, api: "new SecretKeySpec" });
27489
+ }
27490
+ }
27491
+ if (method === "initialize") {
27492
+ const isKpg = call.receiver_type === "KeyPairGenerator" || (call.receiver_type_fqn ?? "").endsWith(".KeyPairGenerator");
27493
+ if (isKpg) {
27494
+ const sizeArg = call.arguments.find((a) => a.position === 0);
27495
+ const expr = (sizeArg?.literal ?? sizeArg?.expression ?? "").trim();
27496
+ const n = parseInt(expr, 10);
27497
+ if (Number.isFinite(n) && n > 0 && n < 2048) {
27498
+ out2.push({
27499
+ issue: "weak-rsa-key",
27500
+ detail: String(n),
27501
+ api: "KeyPairGenerator.initialize"
27502
+ });
27503
+ }
27504
+ }
27505
+ }
27408
27506
  return out2;
27409
27507
  }
27410
27508
  if (language === "python") {
@@ -10003,6 +10003,13 @@ var DEFAULT_SOURCES = [
10003
10003
  { method: "getFileName", class: "BodyPart", type: "file_input", severity: "high", return_tainted: true },
10004
10004
  { method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
10005
10005
  { method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
10006
+ // Archive entry names (Zip-Slip / Tar-Slip CWE-22, issue #52)
10007
+ // entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
10008
+ // is a classic Zip-Slip vulnerability.
10009
+ { method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
10010
+ { method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
10011
+ { method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
10012
+ { method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
10006
10013
  // Command line arguments
10007
10014
  { method: "getArgs", type: "io_input", severity: "high", return_tainted: true },
10008
10015
  { method: "getOptionValue", class: "CommandLine", type: "io_input", severity: "high", return_tainted: true },
@@ -10437,7 +10444,7 @@ var DEFAULT_SINKS = [
10437
10444
  { method: "staticFileLocation", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10438
10445
  // Zip/archive handling
10439
10446
  { method: "getEntry", class: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10440
- { method: "getName", class: "ZipEntry", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [] },
10447
+ // ZipEntry.getName moved to file_sources.yaml as a taint SOURCE (type=archive_entry, issue #52)
10441
10448
  // Resource loading classes (various frameworks)
10442
10449
  { method: "ClassPathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10443
10450
  { method: "FileSystemResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -12033,10 +12040,11 @@ function matchesSourcePattern(call, pattern) {
12033
12040
  return false;
12034
12041
  }
12035
12042
  if (pattern.class && pattern.class !== "constructor") {
12036
- if (!call.receiver) {
12043
+ if (call.receiver_type && call.receiver_type === pattern.class) {
12044
+ } else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
12045
+ } else if (!call.receiver) {
12037
12046
  return false;
12038
- }
12039
- if (!receiverMightBeClass(call.receiver, pattern.class)) {
12047
+ } else if (!receiverMightBeClass(call.receiver, pattern.class)) {
12040
12048
  return false;
12041
12049
  }
12042
12050
  }
@@ -12294,13 +12302,14 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12294
12302
  if (pattern.class === "constructor") {
12295
12303
  return true;
12296
12304
  }
12297
- if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12305
+ if (call.receiver_type && call.receiver_type === pattern.class) {
12306
+ } else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
12307
+ } else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12298
12308
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
12299
12309
  return true;
12300
12310
  }
12301
12311
  return false;
12302
- }
12303
- if (!call.receiver) {
12312
+ } else if (!call.receiver && !call.receiver_type) {
12304
12313
  return false;
12305
12314
  }
12306
12315
  }
@@ -9937,6 +9937,13 @@ var DEFAULT_SOURCES = [
9937
9937
  { method: "getFileName", class: "BodyPart", type: "file_input", severity: "high", return_tainted: true },
9938
9938
  { method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
9939
9939
  { method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
9940
+ // Archive entry names (Zip-Slip / Tar-Slip CWE-22, issue #52)
9941
+ // entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
9942
+ // is a classic Zip-Slip vulnerability.
9943
+ { method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
9944
+ { method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
9945
+ { method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
9946
+ { method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
9940
9947
  // Command line arguments
9941
9948
  { method: "getArgs", type: "io_input", severity: "high", return_tainted: true },
9942
9949
  { method: "getOptionValue", class: "CommandLine", type: "io_input", severity: "high", return_tainted: true },
@@ -10371,7 +10378,7 @@ var DEFAULT_SINKS = [
10371
10378
  { method: "staticFileLocation", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10372
10379
  // Zip/archive handling
10373
10380
  { method: "getEntry", class: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10374
- { method: "getName", class: "ZipEntry", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [] },
10381
+ // ZipEntry.getName moved to file_sources.yaml as a taint SOURCE (type=archive_entry, issue #52)
10375
10382
  // Resource loading classes (various frameworks)
10376
10383
  { method: "ClassPathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10377
10384
  { method: "FileSystemResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11967,10 +11974,11 @@ function matchesSourcePattern(call, pattern) {
11967
11974
  return false;
11968
11975
  }
11969
11976
  if (pattern.class && pattern.class !== "constructor") {
11970
- if (!call.receiver) {
11977
+ if (call.receiver_type && call.receiver_type === pattern.class) {
11978
+ } else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
11979
+ } else if (!call.receiver) {
11971
11980
  return false;
11972
- }
11973
- if (!receiverMightBeClass(call.receiver, pattern.class)) {
11981
+ } else if (!receiverMightBeClass(call.receiver, pattern.class)) {
11974
11982
  return false;
11975
11983
  }
11976
11984
  }
@@ -12228,13 +12236,14 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12228
12236
  if (pattern.class === "constructor") {
12229
12237
  return true;
12230
12238
  }
12231
- if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12239
+ if (call.receiver_type && call.receiver_type === pattern.class) {
12240
+ } else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
12241
+ } else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12232
12242
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
12233
12243
  return true;
12234
12244
  }
12235
12245
  return false;
12236
- }
12237
- if (!call.receiver) {
12246
+ } else if (!call.receiver && !call.receiver_type) {
12238
12247
  return false;
12239
12248
  }
12240
12249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.52.0",
3
+ "version": "3.53.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",