cognium-dev 4.13.0 → 4.13.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 +135 -19
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -20039,7 +20039,13 @@ class ConstantPropagator {
20039
20039
  }
20040
20040
  return;
20041
20041
  }
20042
- const condValue = this.evaluateExpression(condition);
20042
+ let condValue = this.evaluateExpression(condition);
20043
+ if (!(isKnown(condValue) && condValue.type === "bool")) {
20044
+ const literal = this.literalCondition(condition);
20045
+ if (literal !== null) {
20046
+ condValue = { type: "bool", value: literal, sourceLine: getNodeLine(condition) };
20047
+ }
20048
+ }
20043
20049
  if (isKnown(condValue) && condValue.type === "bool") {
20044
20050
  if (condValue.value === true) {
20045
20051
  if (alternative) {
@@ -20188,12 +20194,27 @@ class ConstantPropagator {
20188
20194
  }
20189
20195
  return;
20190
20196
  }
20191
- const caseGroups = switchBlock.children.filter((c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section");
20197
+ const caseGroups = switchBlock.children.filter((c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section" || c.type === "switch_case" || c.type === "switch_default");
20198
+ if (caseGroups.length === 0) {
20199
+ this.visit(switchBlock);
20200
+ return;
20201
+ }
20192
20202
  if (switchValue && isKnown(switchValue)) {
20193
20203
  let matchingIdx = -1;
20194
20204
  let defaultIdx = -1;
20195
20205
  for (let i2 = 0;i2 < caseGroups.length; i2++) {
20196
20206
  const caseGroup = caseGroups[i2];
20207
+ if (caseGroup.type === "switch_default") {
20208
+ defaultIdx = i2;
20209
+ continue;
20210
+ }
20211
+ if (caseGroup.type === "switch_case") {
20212
+ const label = caseGroup.childForFieldName("value");
20213
+ const caseValue = label ? this.literalValue(label) : null;
20214
+ if (caseValue !== null && caseValue === switchValue.value)
20215
+ matchingIdx = i2;
20216
+ continue;
20217
+ }
20197
20218
  for (const child of caseGroup.children) {
20198
20219
  if (child.type === "switch_label") {
20199
20220
  const labelText = getNodeText2(child, this.source);
@@ -20297,6 +20318,30 @@ class ConstantPropagator {
20297
20318
  }
20298
20319
  return false;
20299
20320
  }
20321
+ literalCondition(node) {
20322
+ if (node.type === "parenthesized_expression") {
20323
+ const inner = node.children.find((c) => c.isNamed);
20324
+ return inner ? this.literalCondition(inner) : null;
20325
+ }
20326
+ if (node.type === "boolean_literal") {
20327
+ const text = getNodeText2(node, this.source);
20328
+ return text === "true" ? true : text === "false" ? false : null;
20329
+ }
20330
+ if (node.type === "binary_expression") {
20331
+ const left = node.childForFieldName("left");
20332
+ const right = node.childForFieldName("right");
20333
+ const op = node.childForFieldName("operator");
20334
+ const opText = op ? getNodeText2(op, this.source) : "";
20335
+ if (!left || !right || opText !== "==" && opText !== "!=")
20336
+ return null;
20337
+ const l = this.literalValue(left);
20338
+ const r = this.literalValue(right);
20339
+ if (l === null || r === null)
20340
+ return null;
20341
+ return opText === "==" ? l === r : l !== r;
20342
+ }
20343
+ return null;
20344
+ }
20300
20345
  literalValue(node) {
20301
20346
  const text = getNodeText2(node, this.source);
20302
20347
  if (node.type === "integer_literal" || node.type === "decimal_integer_literal") {
@@ -34134,6 +34179,9 @@ var ENDS_STATEMENT_RE = /[;{}]\s*(?:\/\/.*)?$/;
34134
34179
  function escapeRegex(s) {
34135
34180
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34136
34181
  }
34182
+ function withoutStringLiterals(text) {
34183
+ return text.replace(/@"(?:[^"]|"")*"|"(?:[^"\\]|\\.)*"/g, '""');
34184
+ }
34137
34185
  function mentions(text, name2) {
34138
34186
  return new RegExp(`(?<![\\w])${escapeRegex(name2)}(?![\\w])`).test(text);
34139
34187
  }
@@ -34147,6 +34195,19 @@ function insideParens(text, open) {
34147
34195
  }
34148
34196
  return null;
34149
34197
  }
34198
+ function lastCallArguments(text) {
34199
+ const close = text.lastIndexOf(")");
34200
+ if (close < 0)
34201
+ return null;
34202
+ let depth = 0;
34203
+ for (let i2 = close;i2 >= 0; i2--) {
34204
+ if (text[i2] === ")")
34205
+ depth++;
34206
+ else if (text[i2] === "(" && --depth === 0)
34207
+ return text.slice(i2 + 1, close);
34208
+ }
34209
+ return null;
34210
+ }
34150
34211
  function leadingIf(text) {
34151
34212
  const m = /^\s*(?:\}\s*)?(?:else\s+)?if\s*\(/.exec(text);
34152
34213
  if (!m)
@@ -34271,6 +34332,65 @@ class CSharpCredit {
34271
34332
  sanitizedVarsAt(type, sinkLine) {
34272
34333
  return this.sanitizedVarsIn(type, this.methodAt(sinkLine)?.start_line ?? 1, sinkLine, true);
34273
34334
  }
34335
+ rawTaintReaches(type, sinkLine, credited, seeds) {
34336
+ if (seeds.length === 0)
34337
+ return false;
34338
+ const method = this.methodAt(sinkLine);
34339
+ const from = method?.start_line ?? 1;
34340
+ const tainted = new Set;
34341
+ const stmts = this.statements(from, sinkLine);
34342
+ for (let i2 = 0;i2 < stmts.length; i2++) {
34343
+ const st = stmts[i2];
34344
+ const end = i2 + 1 < stmts.length ? stmts[i2 + 1].line : sinkLine;
34345
+ const m = ASSIGN_RE.exec(st.text);
34346
+ if (m) {
34347
+ const rhs = withoutStringLiterals(m[2]);
34348
+ if ([...tainted].some((v) => mentions(rhs, v)))
34349
+ tainted.add(m[1]);
34350
+ else
34351
+ tainted.delete(m[1]);
34352
+ }
34353
+ for (const s of seeds)
34354
+ if (s.line >= st.line && s.line < end)
34355
+ tainted.add(s.variable);
34356
+ }
34357
+ if (tainted.size === 0)
34358
+ return false;
34359
+ const call = withoutStringLiterals(this.sinkText(sinkLine));
34360
+ const rest = this.withoutSanitizerCalls(type, lastCallArguments(call) ?? call);
34361
+ if (this.builtinSanitizes(type, rest))
34362
+ return false;
34363
+ for (const v of tainted)
34364
+ if (!credited.has(v) && mentions(rest, v))
34365
+ return true;
34366
+ return false;
34367
+ }
34368
+ withoutSanitizerCalls(type, text) {
34369
+ const res = [];
34370
+ for (const { re, type: t } of CSHARP_SANITIZER_RES)
34371
+ if (t === type)
34372
+ res.push(re);
34373
+ for (const name2 of this.helpers(type))
34374
+ res.push(new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`));
34375
+ let out2 = text;
34376
+ for (const re of res) {
34377
+ for (let guard = 0;guard < 16; guard++) {
34378
+ const m = re.exec(out2);
34379
+ if (!m)
34380
+ break;
34381
+ if (!m[0].endsWith("(")) {
34382
+ out2 = out2.slice(0, m.index) + out2.slice(m.index + m[0].length);
34383
+ continue;
34384
+ }
34385
+ const open = m.index + m[0].length - 1;
34386
+ const inner = insideParens(out2, open);
34387
+ if (inner === null)
34388
+ break;
34389
+ out2 = out2.slice(0, m.index) + out2.slice(open + inner.length + 2);
34390
+ }
34391
+ }
34392
+ return out2;
34393
+ }
34274
34394
  sinkText(line) {
34275
34395
  const text = this.lines[line - 1] ?? "";
34276
34396
  const guarded = leadingIf(text);
@@ -34463,21 +34583,19 @@ class CSharpCredit {
34463
34583
  }
34464
34584
  return new Set([...state].filter(([, safe]) => safe).map(([name2]) => name2));
34465
34585
  }
34466
- creditedOnLine(line) {
34586
+ creditedOnLine(line, seeds = []) {
34467
34587
  const text = this.sinkText(line);
34468
34588
  const types = new Set(CSHARP_SANITIZER_RES.map((r) => r.type));
34469
34589
  for (const t of INERT_WHEN_ALPHANUMERIC)
34470
34590
  types.add(t);
34471
34591
  types.add("ssrf");
34472
34592
  for (const type of types) {
34473
- if (this.sanitizes(type, text))
34474
- return true;
34475
34593
  const credited = new Set([...this.sanitizedVarsAt(type, line), ...this.guardedVarsAt(type, line)]);
34476
34594
  for (const v of this.safeBuilders(credited, line))
34477
34595
  credited.add(v);
34478
- for (const v of credited)
34479
- if (mentions(text, v))
34480
- return true;
34596
+ const covered = this.sanitizes(type, text) || [...credited].some((v) => mentions(text, v));
34597
+ if (covered && !this.rawTaintReaches(type, line, credited, seeds))
34598
+ return true;
34481
34599
  }
34482
34600
  return this.fixedHostRequest(line);
34483
34601
  }
@@ -35764,13 +35882,13 @@ class SinkFilterPass {
35764
35882
  const sanitizedByType = csharpSanitizedVarsByType(ctx.code);
35765
35883
  const csFields = new Set(ctx.graph.ir.types.flatMap((t) => (t.fields ?? []).map((f) => f.name)));
35766
35884
  const credit = new CSharpCredit(ctx.code, ctx.graph.ir.types.flatMap((t) => t.methods));
35885
+ const csSeeds = mergedSources.flatMap((s) => s.variable ? [{ variable: s.variable, line: s.line }] : []);
35767
35886
  const xxeHardened = /\bXmlResolver\s*=\s*null\b/.test(ctx.code) || /\bDtdProcessing\s*=\s*(?:DtdProcessing\s*\.\s*)?(?:Prohibit|Ignore)\b/.test(ctx.code);
35768
35887
  filtered = filtered.filter((sink) => {
35769
35888
  if (sink.type === "xxe" && xxeHardened)
35770
35889
  return false;
35771
35890
  const sinkLineText = sourceLines[sink.line - 1] ?? "";
35772
- if (credit.sanitizes(sink.type, sinkLineText))
35773
- return false;
35891
+ const inlineSanitized = credit.sanitizes(sink.type, sinkLineText);
35774
35892
  if (sink.type === "ssrf" && credit.fixedHostRequest(sink.line))
35775
35893
  return false;
35776
35894
  const scoped = credit.sanitizedVarsAt(sink.type, sink.line);
@@ -35786,11 +35904,8 @@ class SinkFilterPass {
35786
35904
  for (const v of credit.safeBuilders(sanitizedVars, sink.line))
35787
35905
  sanitizedVars.add(v);
35788
35906
  const received = credit.sinkText(sink.line);
35789
- for (const v of sanitizedVars) {
35790
- if (new RegExp(`(?<![\\w])${escapeRegex2(v)}(?![\\w])`).test(received))
35791
- return false;
35792
- }
35793
- return true;
35907
+ const covered = inlineSanitized || [...sanitizedVars].some((v) => new RegExp(`(?<![\\w])${escapeRegex2(v)}(?![\\w])`).test(received));
35908
+ return !covered || credit.rawTaintReaches(sink.type, sink.line, sanitizedVars, csSeeds);
35794
35909
  });
35795
35910
  }
35796
35911
  if (language === "csharp") {
@@ -40364,9 +40479,10 @@ class InterproceduralPass {
40364
40479
  });
40365
40480
  }
40366
40481
  if (csCredit) {
40482
+ const csSeeds = sources.flatMap((s) => s.variable ? [{ variable: s.variable, line: s.line }] : []);
40367
40483
  const credited = new Set;
40368
40484
  filteredAdditionalFlows = filteredAdditionalFlows.filter((f) => {
40369
- if (f.sink_type !== "external_taint_escape" || !csCredit.creditedOnLine(f.sink_line))
40485
+ if (f.sink_type !== "external_taint_escape" || !csCredit.creditedOnLine(f.sink_line, csSeeds))
40370
40486
  return true;
40371
40487
  credited.add(f.sink_line);
40372
40488
  return false;
@@ -51854,7 +51970,7 @@ var colors = {
51854
51970
  };
51855
51971
 
51856
51972
  // src/version.ts
51857
- var version = "4.13.0";
51973
+ var version = "4.13.1";
51858
51974
 
51859
51975
  // src/formatters.ts
51860
51976
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
@@ -52406,8 +52522,8 @@ function generateSarifResults(results, crossFileData) {
52406
52522
 
52407
52523
  // src/build-info.ts
52408
52524
  var buildInfo = {
52409
- gitSha: "a45b21b",
52410
- builtAt: "2026-10-08T13:38:40.827Z"
52525
+ gitSha: "51b2da7",
52526
+ builtAt: "2026-10-08T18:47:29.958Z"
52411
52527
  };
52412
52528
 
52413
52529
  // src/utils/args.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "4.13.0",
3
+ "version": "4.13.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.13.0"
68
+ "circle-ir": "4.13.1"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^26.6.4",