cognium-dev 4.16.0 → 4.16.1

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 (2) hide show
  1. package/dist/cli.js +153 -25
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -20551,20 +20551,25 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
20551
20551
  const lines = code.split(`
20552
20552
  `);
20553
20553
  const terminatorRe = /\b(throw|return)\b/;
20554
+ const literalArrays = collectCSharpLiteralArrayFields(code);
20555
+ 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*\)/;
20554
20556
  const allowlistRes = [
20555
- /\bif\s*\(\s*Array\s*\.\s*IndexOf\s*\([^,]+,\s*([A-Za-z_][\w.]*)\s*\)\s*<\s*0\s*\)/,
20556
20557
  /\bif\s*\(\s*!\s*[A-Za-z_][\w.]*\s*\.\s*Contains\s*\(\s*([A-Za-z_][\w.]*)\s*\)\s*\)/
20557
20558
  ];
20558
20559
  const charClassRe = /\bif\s*\(\s*!\s*([A-Za-z_][\w.]*)\s*\.\s*All\s*\(\s*char\s*\.\s*Is[A-Za-z]+\s*\)\s*\)/;
20559
20560
  for (let i2 = 0;i2 < lines.length; i2++) {
20560
20561
  let guardedExpr = null;
20561
- for (const re of allowlistRes) {
20562
- const m = re.exec(lines[i2]);
20563
- if (m) {
20564
- guardedExpr = m[1];
20565
- break;
20562
+ const im = indexOfRe.exec(lines[i2]);
20563
+ if (im && literalArrays.has(im[1]))
20564
+ guardedExpr = im[2];
20565
+ if (!guardedExpr)
20566
+ for (const re of allowlistRes) {
20567
+ const m = re.exec(lines[i2]);
20568
+ if (m) {
20569
+ guardedExpr = m[1];
20570
+ break;
20571
+ }
20566
20572
  }
20567
- }
20568
20573
  if (!guardedExpr) {
20569
20574
  const m = charClassRe.exec(lines[i2]);
20570
20575
  if (m)
@@ -20594,6 +20599,8 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
20594
20599
  const a = assignRe.exec(lines[l]);
20595
20600
  if (a && mentions(a[2]))
20596
20601
  guarded.add(a[1]);
20602
+ else if (a)
20603
+ guarded.delete(a[1]);
20597
20604
  if (mentions(lines[l])) {
20598
20605
  sanitizers.push({
20599
20606
  type: "csharp_early_return_guard",
@@ -20614,6 +20621,26 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
20614
20621
  }
20615
20622
  return sanitizers;
20616
20623
  }
20624
+ function collectCSharpLiteralArrayFields(code) {
20625
+ const names = new Set;
20626
+ const lit = String.raw`(?:@"(?:[^"]|"")*"|"(?:[^"\\\n]|\\.)*")`;
20627
+ const elems = new RegExp(String.raw`^\s*${lit}(?:\s*,\s*${lit})*\s*,?\s*$`);
20628
+ 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;
20629
+ for (const m of code.matchAll(declRe)) {
20630
+ const mods = m[1];
20631
+ const name2 = m[2];
20632
+ if (!/\bstatic\b/.test(mods) || !/\breadonly\b/.test(mods))
20633
+ continue;
20634
+ if (!elems.test(m[3]))
20635
+ continue;
20636
+ const rest = code.slice(0, m.index) + code.slice(m.index + m[0].length);
20637
+ const written = new RegExp(String.raw`\b${name2}\s*(?:\[[^\]]*\]\s*)?(?:[-+*/%&|^]|\?\?)?=(?!=)` + String.raw`|\b(?:ref|out)\s+${name2}\b` + String.raw`|\b${name2}\s*\.\s*SetValue\s*\(` + String.raw`|\bArray\s*\.\s*(?:Clear|Copy|ConstrainedCopy|Fill|Resize|Reverse|Sort)\s*\([^;]*\b${name2}\b`);
20638
+ if (written.test(rest))
20639
+ continue;
20640
+ names.add(name2);
20641
+ }
20642
+ return names;
20643
+ }
20617
20644
  function findCSharpFullPathStartsWithGuardSanitizers(code) {
20618
20645
  const sanitizers = [];
20619
20646
  const lines = code.split(`
@@ -38184,6 +38211,12 @@ function bareIdentifiers(text) {
38184
38211
  out2.push(m[1]);
38185
38212
  return out2;
38186
38213
  }
38214
+ function isLiteralValue(rhs) {
38215
+ if (/\$"/.test(rhs))
38216
+ return false;
38217
+ const ids = bareIdentifiers(rhs);
38218
+ return ids.every((id) => id === "null" || id === "true" || id === "false" || id === "default") || /^\s*(?:string|String)\s*\.\s*Empty\s*$/.test(rhs);
38219
+ }
38187
38220
  function insideParens(text, open) {
38188
38221
  let depth = 0;
38189
38222
  for (let i2 = open;i2 < text.length; i2++) {
@@ -38372,6 +38405,34 @@ class CSharpCredit {
38372
38405
  sanitizedVarsAt(type, sinkLine) {
38373
38406
  return this.sanitizedVarsIn(type, this.methodAt(sinkLine)?.start_line ?? 1, sinkLine, true);
38374
38407
  }
38408
+ fieldWrites(type, fields) {
38409
+ const credited = new Set;
38410
+ const unsanitized = new Set;
38411
+ for (const m of this.methods) {
38412
+ const sanitized = new Set;
38413
+ for (const st of this.statements(m.start_line, m.end_line + 1)) {
38414
+ if (this.methodAt(st.line) !== m)
38415
+ continue;
38416
+ for (const op of this.creditOpsInStatement(st.text)) {
38417
+ const lhs = op.lhs;
38418
+ const keeps = op.kind === "append" ? sanitized.has(lhs) && this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, true, true) : this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, true, false);
38419
+ if (keeps)
38420
+ sanitized.add(lhs);
38421
+ else
38422
+ sanitized.delete(lhs);
38423
+ if (!fields.has(lhs) || isLiteralValue(op.rhs))
38424
+ continue;
38425
+ if (keeps)
38426
+ credited.add(lhs);
38427
+ else
38428
+ unsanitized.add(lhs);
38429
+ }
38430
+ }
38431
+ }
38432
+ for (const f of unsanitized)
38433
+ credited.delete(f);
38434
+ return { sanitized: credited, unsanitized };
38435
+ }
38375
38436
  rawTaintReaches(type, sinkLine, credited, seeds) {
38376
38437
  if (seeds.length === 0)
38377
38438
  return false;
@@ -38921,10 +38982,12 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
38921
38982
  if (other.arguments.some((a) => a.expression.includes(needle)))
38922
38983
  return false;
38923
38984
  }
38924
- const receiver = call.receiver;
38985
+ return isJdkClassReceiver(call.receiver, JAVA_FORMATTER_CLASSES, declaredTypeNames, imports);
38986
+ }
38987
+ function isJdkClassReceiver(receiver, classes, declaredTypeNames, imports) {
38925
38988
  const dot = receiver.lastIndexOf(".");
38926
38989
  const simple = dot < 0 ? receiver : receiver.slice(dot + 1);
38927
- const pkg = JAVA_FORMATTER_CLASSES.get(simple);
38990
+ const pkg = classes.get(simple);
38928
38991
  if (pkg === undefined)
38929
38992
  return false;
38930
38993
  if (dot >= 0)
@@ -38941,9 +39004,44 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
38941
39004
  return true;
38942
39005
  return imports.some((imp) => imp.from_package === pkg && (imp.imported_name === simple || imp.is_wildcard));
38943
39006
  }
39007
+ var JAVA_CHARACTER_CLASS = new Map([["Character", "java.lang"]]);
39008
+ var JAVA_CHARACTER_PREDICATES = new Set([
39009
+ "isLetter",
39010
+ "isDigit",
39011
+ "isLetterOrDigit",
39012
+ "isAlphabetic",
39013
+ "isWhitespace",
39014
+ "isSpaceChar",
39015
+ "isUpperCase",
39016
+ "isLowerCase",
39017
+ "isTitleCase",
39018
+ "isISOControl",
39019
+ "isDefined",
39020
+ "isIdeographic",
39021
+ "isMirrored",
39022
+ "isJavaIdentifierStart",
39023
+ "isJavaIdentifierPart",
39024
+ "isUnicodeIdentifierStart",
39025
+ "isUnicodeIdentifierPart",
39026
+ "isIdentifierIgnorable",
39027
+ "isHighSurrogate",
39028
+ "isLowSurrogate",
39029
+ "isSurrogate",
39030
+ "isSurrogatePair",
39031
+ "isValidCodePoint",
39032
+ "isBmpCodePoint",
39033
+ "isSupplementaryCodePoint"
39034
+ ]);
39035
+ function isJavaCharacterPredicateCall(call, declaredTypeNames, imports) {
39036
+ if (!call.receiver || !JAVA_CHARACTER_PREDICATES.has(call.method_name))
39037
+ return false;
39038
+ return isJdkClassReceiver(call.receiver, JAVA_CHARACTER_CLASS, declaredTypeNames, imports);
39039
+ }
38944
39040
  function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
38945
39041
  if (isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls))
38946
39042
  return true;
39043
+ if (isJavaCharacterPredicateCall(call, declaredTypeNames, imports))
39044
+ return true;
38947
39045
  if (call.receiver)
38948
39046
  return false;
38949
39047
  const isCtor = call.is_constructor === true || (call.resolution?.target ?? "").endsWith(".<init>");
@@ -42804,6 +42902,15 @@ class SinkFilterPass {
42804
42902
  const csFields = new Set(ctx.graph.ir.types.flatMap((t) => (t.fields ?? []).map((f) => f.name)));
42805
42903
  const credit = new CSharpCredit(ctx.code, ctx.graph.ir.types.flatMap((t) => t.methods));
42806
42904
  const csSeeds = mergedSources.flatMap((s) => s.variable ? [{ variable: s.variable, line: s.line }] : []);
42905
+ const fieldWritesByType = new Map;
42906
+ const fieldWritesFor = (type) => {
42907
+ let w = fieldWritesByType.get(type);
42908
+ if (!w) {
42909
+ w = credit.fieldWrites(type, csFields);
42910
+ fieldWritesByType.set(type, w);
42911
+ }
42912
+ return w;
42913
+ };
42807
42914
  const xxeHardened = /\bXmlResolver\s*=\s*null\b/.test(ctx.code) || /\bDtdProcessing\s*=\s*(?:DtdProcessing\s*\.\s*)?(?:Prohibit|Ignore)\b/.test(ctx.code);
42808
42915
  filtered = filtered.filter((sink) => {
42809
42916
  if (sink.type === "xxe" && xxeHardened)
@@ -42815,11 +42922,15 @@ class SinkFilterPass {
42815
42922
  const scoped = credit.sanitizedVarsAt(sink.type, sink.line);
42816
42923
  const fileWide = sanitizedByType.get(sink.type);
42817
42924
  const sanitizedVars = new Set(scoped);
42925
+ const fieldWrites = fieldWritesFor(sink.type);
42818
42926
  if (fileWide) {
42819
- for (const v of fileWide)
42820
- if (csFields.has(v))
42927
+ for (const v of fileWide) {
42928
+ if (csFields.has(v) && !fieldWrites.unsanitized.has(v))
42821
42929
  sanitizedVars.add(v);
42930
+ }
42822
42931
  }
42932
+ for (const v of fieldWrites.sanitized)
42933
+ sanitizedVars.add(v);
42823
42934
  for (const v of credit.guardedVarsAt(sink.type, sink.line))
42824
42935
  sanitizedVars.add(v);
42825
42936
  for (const v of credit.safeBuilders(sanitizedVars, sink.line))
@@ -45124,26 +45235,38 @@ function resolveJavaDerivedAnchor(varName, derived, realSourceByVar, codeLines,
45124
45235
  }
45125
45236
  return best;
45126
45237
  }
45127
- function javaReceiverChainDerivedVar(code, localVar, seedVar) {
45238
+ function javaReceiverChainDerivedVar(rhsByLocal, localVar, seedVar) {
45128
45239
  if (!seedVar || localVar === seedVar)
45129
45240
  return false;
45241
+ const rhsList = rhsByLocal.get(localVar);
45242
+ if (!rhsList)
45243
+ return false;
45130
45244
  const esc = seedVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45131
45245
  const receiverOnRhs = new RegExp(`(?:^|[^\\w.$])${esc}\\s*\\.`, "u");
45246
+ return rhsList.some((rhs) => receiverOnRhs.test(rhs));
45247
+ }
45248
+ function indexJavaLocalAssignments(code) {
45132
45249
  const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
45133
45250
  const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
45251
+ const index = new Map;
45134
45252
  for (const line of code.split(`
45135
45253
  `)) {
45136
45254
  const trimmed = line.trimStart();
45137
45255
  if (trimmed.startsWith("//") || trimmed.startsWith("*"))
45138
45256
  continue;
45257
+ if (!line.includes("=") || !line.includes(";"))
45258
+ continue;
45139
45259
  const m = declRe.exec(line) ?? assignRe.exec(line);
45140
- if (!m || m[1] !== localVar)
45260
+ if (!m)
45141
45261
  continue;
45262
+ const list = index.get(m[1]);
45142
45263
  const rhs = stripJavaTrailingComment(m[2]);
45143
- if (receiverOnRhs.test(rhs))
45144
- return true;
45264
+ if (list)
45265
+ list.push(rhs);
45266
+ else
45267
+ index.set(m[1], [rhs]);
45145
45268
  }
45146
- return false;
45269
+ return index;
45147
45270
  }
45148
45271
  function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers) {
45149
45272
  const lines = typeof code === "string" ? code.split(`
@@ -45264,15 +45387,20 @@ class TaintPropagationPass {
45264
45387
  const sinkFilter = ctx.getResult("sink-filter");
45265
45388
  const { sources, sinks, sanitizers } = sinkFilter;
45266
45389
  if (ctx.language === "java" && typeof ctx.code === "string") {
45390
+ const rhsByLocal = indexJavaLocalAssignments(ctx.code);
45391
+ const seen = new Set;
45267
45392
  for (const s of sources) {
45268
- if (!s.variable)
45393
+ if (!s.variable || seen.has(s.variable))
45269
45394
  continue;
45270
- for (const [v] of buildJavaTaintedVars(ctx.code, new Set([s.variable]))) {
45271
- if (v === s.variable)
45272
- continue;
45273
- if (javaReceiverChainDerivedVar(ctx.code, v, s.variable)) {
45395
+ seen.add(s.variable);
45396
+ const seedVar = s.variable;
45397
+ const candidates = [...rhsByLocal.keys()].filter((v) => !constProp.tainted.has(v) && javaReceiverChainDerivedVar(rhsByLocal, v, seedVar));
45398
+ if (candidates.length === 0)
45399
+ continue;
45400
+ const derived = buildJavaTaintedVars(ctx.code, new Set([seedVar]));
45401
+ for (const v of candidates) {
45402
+ if (derived.has(v))
45274
45403
  constProp.tainted.add(v);
45275
- }
45276
45404
  }
45277
45405
  }
45278
45406
  }
@@ -60106,7 +60234,7 @@ var colors = {
60106
60234
  };
60107
60235
 
60108
60236
  // src/version.ts
60109
- var version = "4.16.0";
60237
+ var version = "4.16.1";
60110
60238
 
60111
60239
  // src/formatters.ts
60112
60240
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
@@ -60673,8 +60801,8 @@ function generateSarifResults(results, crossFileData) {
60673
60801
 
60674
60802
  // src/build-info.ts
60675
60803
  var buildInfo = {
60676
- gitSha: "d14e309",
60677
- builtAt: "2026-10-17T21:35:31.408Z"
60804
+ gitSha: "7c01fdc",
60805
+ builtAt: "2026-10-18T03:55:29.307Z"
60678
60806
  };
60679
60807
 
60680
60808
  // src/utils/args.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "4.16.0",
3
+ "version": "4.16.1",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "bin": {
6
6
  "cognium-dev": "dist/cli.js"
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@cognium/project-profile-detect": "1.1.2",
68
- "circle-ir": "4.16.0"
68
+ "circle-ir": "4.16.1"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^26.6.5",