circle-ir 4.16.0 → 4.17.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.
Files changed (59) hide show
  1. package/dist/analysis/constant-propagation/index.d.ts +2 -0
  2. package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
  3. package/dist/analysis/constant-propagation/index.js +10 -0
  4. package/dist/analysis/constant-propagation/index.js.map +1 -1
  5. package/dist/analysis/constant-propagation/propagator.d.ts +11 -0
  6. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  7. package/dist/analysis/constant-propagation/propagator.js +125 -20
  8. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  9. package/dist/analysis/constant-propagation/types.d.ts +2 -0
  10. package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
  11. package/dist/analysis/constant-propagation.d.ts +1 -1
  12. package/dist/analysis/constant-propagation.d.ts.map +1 -1
  13. package/dist/analysis/constant-propagation.js +1 -1
  14. package/dist/analysis/constant-propagation.js.map +1 -1
  15. package/dist/analysis/interprocedural.d.ts.map +1 -1
  16. package/dist/analysis/interprocedural.js +37 -8
  17. package/dist/analysis/interprocedural.js.map +1 -1
  18. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  19. package/dist/analysis/passes/cross-file-pass.js +3 -1
  20. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  21. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +13 -0
  22. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  23. package/dist/analysis/passes/csharp-sanitizer-credit.js +48 -0
  24. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  25. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  26. package/dist/analysis/passes/language-sources-pass.js +80 -8
  27. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  28. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  29. package/dist/analysis/passes/param-source-flow-gate-pass.js +17 -1
  30. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  31. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  32. package/dist/analysis/passes/sink-filter-pass.js +19 -3
  33. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  34. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  35. package/dist/analysis/passes/taint-propagation-pass.js +205 -22
  36. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  37. package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
  38. package/dist/analysis/rust-literal-guard.js +100 -1
  39. package/dist/analysis/rust-literal-guard.js.map +1 -1
  40. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  41. package/dist/analysis/taint-matcher.js +1 -0
  42. package/dist/analysis/taint-matcher.js.map +1 -1
  43. package/dist/browser/circle-ir.js +561 -41
  44. package/dist/core/circle-ir-core.cjs +149 -17
  45. package/dist/core/circle-ir-core.js +149 -17
  46. package/dist/core/extractors/types.js +53 -9
  47. package/dist/core/extractors/types.js.map +1 -1
  48. package/dist/navigation/navigation-index.d.ts +40 -1
  49. package/dist/navigation/navigation-index.d.ts.map +1 -1
  50. package/dist/navigation/navigation-index.js +300 -45
  51. package/dist/navigation/navigation-index.js.map +1 -1
  52. package/dist/resolution/cross-file.d.ts +9 -0
  53. package/dist/resolution/cross-file.d.ts.map +1 -1
  54. package/dist/resolution/cross-file.js +134 -0
  55. package/dist/resolution/cross-file.js.map +1 -1
  56. package/dist/types/index.d.ts +18 -0
  57. package/dist/types/index.d.ts.map +1 -1
  58. package/dist/types/index.js.map +1 -1
  59. package/package.json +1 -1
@@ -1701,6 +1701,9 @@ function findCSharpRequestSources(sourceCode, language) {
1701
1701
  // cognium-dev#638 step 3 — `box.value = req.QueryString["k"]` seeds the
1702
1702
  // container local so cross-file calls that pass `box` carry taint.
1703
1703
  const memberFieldAssignRe = /^\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
1704
+ // cognium-dev#638 step 5 — `list.Add(req.QueryString["k"])` seeds the list
1705
+ // local so cross-file calls that pass `list` carry taint into callee reads.
1706
+ const collectionAddRe = /^\s*([A-Za-z_]\w*)\s*\.\s*Add\s*\(\s*(.+?)\s*\)\s*;?\s*$/;
1704
1707
  // `Request.*` is the inherited controller member. A parameter typed
1705
1708
  // `HttpRequest` / `HttpRequestBase` is the same surface under whatever
1706
1709
  // name the method uses (`req` in Juliet) — cognium-dev#501.
@@ -1869,6 +1872,25 @@ function findCSharpRequestSources(sourceCode, language) {
1869
1872
  continue;
1870
1873
  }
1871
1874
  }
1875
+ const addEl = collectionAddRe.exec(stmt);
1876
+ if (addEl) {
1877
+ const [, baseName, rhs] = addEl;
1878
+ const type = classify(rhs);
1879
+ if (type && baseName) {
1880
+ const lineNumber = i + 1;
1881
+ if (!sources.some(s => s.line === lineNumber && s.variable === baseName)) {
1882
+ sources.push({
1883
+ type,
1884
+ location: `${baseName}.Add(${rhs.trim().substring(0, 50)}${rhs.length > 50 ? '...' : ''})`,
1885
+ severity: type === 'db_input' ? 'medium' : 'high',
1886
+ line: lineNumber,
1887
+ confidence: 1.0,
1888
+ variable: baseName,
1889
+ });
1890
+ }
1891
+ continue;
1892
+ }
1893
+ }
1872
1894
  const m = assignRe.exec(stmt);
1873
1895
  // cognium-dev#502 — `assignRe` is anchored and greedy, so on a one-line
1874
1896
  // block header that also contains the real assignment
@@ -7446,10 +7468,14 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
7446
7468
  const lines = code.split('\n');
7447
7469
  const terminatorRe = /\b(throw|return)\b/;
7448
7470
  // Allow-list membership, rejecting form:
7449
- // if (Array.IndexOf(Allowed, x) < 0) return;
7471
+ // if (Array.IndexOf(Allowed, x) < 0) return; (also System.Array, == -1)
7450
7472
  // if (!Allowed.Contains(x)) return;
7473
+ // cognium-dev#809: the IndexOf form credits only a `static readonly`
7474
+ // string-literal array field the file never writes (see
7475
+ // `collectCSharpLiteralArrayFields`); any other array may hold the value.
7476
+ const literalArrays = collectCSharpLiteralArrayFields(code);
7477
+ const indexOfRe = /\bif\s*\(\s*(?:System\s*\.\s*)?Array\s*\.\s*IndexOf\s*\(\s*([A-Za-z_]\w*)\s*,\s*([A-Za-z_][\w.]*)\s*\)\s*(?:<\s*0|==\s*-\s*1)\s*\)/;
7451
7478
  const allowlistRes = [
7452
- /\bif\s*\(\s*Array\s*\.\s*IndexOf\s*\([^,]+,\s*([A-Za-z_][\w.]*)\s*\)\s*<\s*0\s*\)/,
7453
7479
  /\bif\s*\(\s*!\s*[A-Za-z_][\w.]*\s*\.\s*Contains\s*\(\s*([A-Za-z_][\w.]*)\s*\)\s*\)/,
7454
7480
  ];
7455
7481
  // Character-class validation, rejecting form:
@@ -7457,13 +7483,17 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
7457
7483
  const charClassRe = /\bif\s*\(\s*!\s*([A-Za-z_][\w.]*)\s*\.\s*All\s*\(\s*char\s*\.\s*Is[A-Za-z]+\s*\)\s*\)/;
7458
7484
  for (let i = 0; i < lines.length; i++) {
7459
7485
  let guardedExpr = null;
7460
- for (const re of allowlistRes) {
7461
- const m = re.exec(lines[i]);
7462
- if (m) {
7463
- guardedExpr = m[1];
7464
- break;
7486
+ const im = indexOfRe.exec(lines[i]);
7487
+ if (im && literalArrays.has(im[1]))
7488
+ guardedExpr = im[2];
7489
+ if (!guardedExpr)
7490
+ for (const re of allowlistRes) {
7491
+ const m = re.exec(lines[i]);
7492
+ if (m) {
7493
+ guardedExpr = m[1];
7494
+ break;
7495
+ }
7465
7496
  }
7466
- }
7467
7497
  if (!guardedExpr) {
7468
7498
  const m = charClassRe.exec(lines[i]);
7469
7499
  if (m)
@@ -7507,6 +7537,10 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
7507
7537
  const a = assignRe.exec(lines[l]);
7508
7538
  if (a && mentions(a[2]))
7509
7539
  guarded.add(a[1]);
7540
+ // cognium-dev#809: a reassignment from anything unguarded ends the
7541
+ // credit (`target = Console.ReadLine();` after the guard).
7542
+ else if (a)
7543
+ guarded.delete(a[1]);
7510
7544
  if (mentions(lines[l])) {
7511
7545
  sanitizers.push({
7512
7546
  type: 'csharp_early_return_guard',
@@ -7520,6 +7554,44 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
7520
7554
  }
7521
7555
  return sanitizers;
7522
7556
  }
7557
+ /**
7558
+ * cognium-dev#809 — names of `static readonly string[]` fields initialised
7559
+ * with string literals only, that the file never writes:
7560
+ *
7561
+ * private static readonly string[] KnownHosts = { "a.example", "b.example" };
7562
+ *
7563
+ * Past `if (Array.IndexOf(KnownHosts, x) < 0) throw …;`, `x` is one of those
7564
+ * literals. The C# counterpart of the Python literal-collection membership
7565
+ * guard (#563) and the Rust literal-equality guard (#293).
7566
+ *
7567
+ * `readonly` stops the field being rebound outside an initialiser, but array
7568
+ * elements stay writable, so any element store, `ref`/`out` pass,
7569
+ * `SetValue`, `Array.<mutator>(…)` naming the field, or plain assignment
7570
+ * after the declaration disqualifies it.
7571
+ */
7572
+ function collectCSharpLiteralArrayFields(code) {
7573
+ const names = new Set();
7574
+ const lit = String.raw `(?:@"(?:[^"]|"")*"|"(?:[^"\\\n]|\\.)*")`;
7575
+ const elems = new RegExp(String.raw `^\s*${lit}(?:\s*,\s*${lit})*\s*,?\s*$`);
7576
+ const declRe = /((?:\b(?:public|private|protected|internal|static|readonly)\s+)+)string\s*\[\s*\]\s+([A-Za-z_]\w*)\s*=\s*(?:new\s+(?:string\s*)?\[\s*\]\s*)?\{([^{}]*)\}\s*;/g;
7577
+ for (const m of code.matchAll(declRe)) {
7578
+ const mods = m[1];
7579
+ const name = m[2];
7580
+ if (!/\bstatic\b/.test(mods) || !/\breadonly\b/.test(mods))
7581
+ continue;
7582
+ if (!elems.test(m[3]))
7583
+ continue;
7584
+ const rest = code.slice(0, m.index) + code.slice(m.index + m[0].length);
7585
+ const written = new RegExp(String.raw `\b${name}\s*(?:\[[^\]]*\]\s*)?(?:[-+*/%&|^]|\?\?)?=(?!=)` +
7586
+ String.raw `|\b(?:ref|out)\s+${name}\b` +
7587
+ String.raw `|\b${name}\s*\.\s*SetValue\s*\(` +
7588
+ String.raw `|\bArray\s*\.\s*(?:Clear|Copy|ConstrainedCopy|Fill|Resize|Reverse|Sort)\s*\([^;]*\b${name}\b`);
7589
+ if (written.test(rest))
7590
+ continue;
7591
+ names.add(name);
7592
+ }
7593
+ return names;
7594
+ }
7523
7595
  function findCSharpFullPathStartsWithGuardSanitizers(code) {
7524
7596
  const sanitizers = [];
7525
7597
  const lines = code.split('\n');