cognium-dev 3.62.0 → 3.65.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 (2) hide show
  1. package/dist/cli.js +160 -17
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -11633,7 +11633,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11633
11633
  location: formatCallLocation(call),
11634
11634
  severity: pattern.severity,
11635
11635
  line: call.location.line,
11636
- confidence: 1
11636
+ confidence: 1,
11637
+ in_method: call.in_method ?? undefined
11637
11638
  });
11638
11639
  }
11639
11640
  }
@@ -11650,7 +11651,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11650
11651
  location: `@${pattern.annotation} ${param.name} in ${method.name}`,
11651
11652
  severity: pattern.severity,
11652
11653
  line: paramLine,
11653
- confidence: 1
11654
+ confidence: 1,
11655
+ in_method: method.name
11654
11656
  });
11655
11657
  }
11656
11658
  }
@@ -11672,7 +11674,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11672
11674
  location: `@${pattern.method_annotation} ${param.name} in ${method.name}`,
11673
11675
  severity: pattern.severity,
11674
11676
  line: paramLine,
11675
- confidence: 1
11677
+ confidence: 1,
11678
+ in_method: method.name
11676
11679
  });
11677
11680
  }
11678
11681
  }
@@ -11701,7 +11704,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11701
11704
  severity: "high",
11702
11705
  line: paramLine,
11703
11706
  confidence: 1,
11704
- variable: param.name
11707
+ variable: param.name,
11708
+ in_method: method.name
11705
11709
  });
11706
11710
  }
11707
11711
  }
@@ -11722,7 +11726,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11722
11726
  location: `${param.type || "any"} ${param.name} in ${method.name}`,
11723
11727
  severity: "medium",
11724
11728
  line: paramLine,
11725
- confidence: param.type ? 0.7 : 0.5
11729
+ confidence: param.type ? 0.7 : 0.5,
11730
+ in_method: method.name
11726
11731
  });
11727
11732
  }
11728
11733
  }
@@ -11742,7 +11747,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11742
11747
  location: `${arg.expression} in ${call.in_method || "anonymous"}`,
11743
11748
  severity: "high",
11744
11749
  line: call.location.line,
11745
- confidence: 1
11750
+ confidence: 1,
11751
+ in_method: call.in_method ?? undefined
11746
11752
  });
11747
11753
  }
11748
11754
  }
@@ -11762,7 +11768,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11762
11768
  location: `${arg.expression} in ${call.in_method || "anonymous"}`,
11763
11769
  severity: "high",
11764
11770
  line: call.location.line,
11765
- confidence: 1
11771
+ confidence: 1,
11772
+ in_method: call.in_method ?? undefined
11766
11773
  });
11767
11774
  }
11768
11775
  break;
@@ -11795,6 +11802,21 @@ function findSources(calls, types, patterns, sourceLines, language) {
11795
11802
  s.variable = m[1];
11796
11803
  }
11797
11804
  }
11805
+ if (language === "java" && sourceLines) {
11806
+ const JAVA_ASSIGN_LHS = /^\s*(?:(?:final|public|private|protected|static|synchronized|volatile|transient)\s+)*(?:[A-Za-z_][\w.]*(?:\s*<[^=]*>)?(?:\s*\[\s*\])*\s+)?([A-Za-z_]\w*)\s*=(?!=)/;
11807
+ for (const s of result) {
11808
+ if (s.variable && s.variable.length > 0)
11809
+ continue;
11810
+ const lineText = sourceLines[s.line - 1] ?? "";
11811
+ const m = JAVA_ASSIGN_LHS.exec(lineText);
11812
+ if (!m)
11813
+ continue;
11814
+ const rhs = lineText.slice(m[0].length).trimStart();
11815
+ if (/^new\b/.test(rhs))
11816
+ continue;
11817
+ s.variable = m[1];
11818
+ }
11819
+ }
11798
11820
  return result;
11799
11821
  }
11800
11822
  function isInterproceduralTaintableType(typeName) {
@@ -22200,6 +22222,8 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
22200
22222
  for (const def of defsNextLine) {
22201
22223
  const callsOnSourceLine = callsByLine.get(source.line) ?? [];
22202
22224
  if (callsOnSourceLine.length > 0) {
22225
+ if (source.variable && def.variable !== source.variable)
22226
+ continue;
22203
22227
  tainted.push({
22204
22228
  variable: def.variable,
22205
22229
  defId: def.id,
@@ -22402,13 +22426,13 @@ class TaintPropagationPass {
22402
22426
  confidence: flow.confidence,
22403
22427
  sanitized: flow.sanitized
22404
22428
  }));
22405
- const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines) ?? [];
22429
+ const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
22406
22430
  for (const f of arrayFlows) {
22407
22431
  if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
22408
22432
  flows.push(f);
22409
22433
  }
22410
22434
  }
22411
- const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code) ?? [];
22435
+ const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
22412
22436
  for (const f of collectionFlows) {
22413
22437
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
22414
22438
  continue;
@@ -22459,7 +22483,7 @@ class TaintPropagationPass {
22459
22483
  flows.push(f);
22460
22484
  }
22461
22485
  const sanitizedNames = constProp.sanitizedVars;
22462
- const finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
22486
+ let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
22463
22487
  if (f.path.length === 0)
22464
22488
  return true;
22465
22489
  const sourceVar = f.path[0].variable;
@@ -22473,10 +22497,74 @@ class TaintPropagationPass {
22473
22497
  }
22474
22498
  return true;
22475
22499
  });
22500
+ if (ctx.language === "java" && typeof ctx.code === "string") {
22501
+ finalFlows = finalFlows.filter((f) => {
22502
+ if (f.sink_type !== "path_traversal" && f.sink_type !== "xxe")
22503
+ return true;
22504
+ if (!isInJavaSanitizedMethod(ctx.code, types, f.sink_line, f.sink_type))
22505
+ return true;
22506
+ return false;
22507
+ });
22508
+ }
22509
+ if (finalFlows.length > 1) {
22510
+ const bestByKey = new Map;
22511
+ for (const f of finalFlows) {
22512
+ const key = `${f.source_line}|${f.sink_line}|${f.sink_type}`;
22513
+ const cur = bestByKey.get(key);
22514
+ if (!cur || f.confidence > cur.confidence) {
22515
+ bestByKey.set(key, f);
22516
+ }
22517
+ }
22518
+ finalFlows = finalFlows.filter((f) => {
22519
+ const key = `${f.source_line}|${f.sink_line}|${f.sink_type}`;
22520
+ return bestByKey.get(key) === f;
22521
+ });
22522
+ }
22476
22523
  return { flows: finalFlows };
22477
22524
  }
22478
22525
  }
22479
- function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code) {
22526
+ function isInJavaSanitizedMethod(code, types, sinkLine, sinkType) {
22527
+ if (!types || types.length === 0)
22528
+ return false;
22529
+ let methodStart = -1;
22530
+ let methodEnd = -1;
22531
+ for (const t of types) {
22532
+ for (const m of t.methods) {
22533
+ if (sinkLine >= m.start_line && sinkLine <= m.end_line) {
22534
+ methodStart = m.start_line;
22535
+ methodEnd = m.end_line;
22536
+ break;
22537
+ }
22538
+ }
22539
+ if (methodStart > 0)
22540
+ break;
22541
+ }
22542
+ if (methodStart < 0)
22543
+ return false;
22544
+ const lines = code.split(`
22545
+ `);
22546
+ const body2 = lines.slice(methodStart - 1, methodEnd).join(`
22547
+ `);
22548
+ if (sinkType === "path_traversal") {
22549
+ if (!/\.getCanonicalPath\s*\(/.test(body2))
22550
+ return false;
22551
+ if (!/\.startsWith\s*\([^)]*getCanonicalPath/.test(body2))
22552
+ return false;
22553
+ if (!/\bthrow\s+new\b/.test(body2))
22554
+ return false;
22555
+ return true;
22556
+ }
22557
+ if (sinkType === "xxe") {
22558
+ const setFeatureRe = /\.setFeature\s*\(\s*"(?:[^"]*disallow-doctype-decl|[^"]*external-general-entities|[^"]*external-parameter-entities|[^"]*load-external-dtd)"/;
22559
+ if (setFeatureRe.test(body2))
22560
+ return true;
22561
+ if (/\.setProperty\s*\([^,]*SUPPORT_DTD[^,]*,\s*false\s*\)/.test(body2))
22562
+ return true;
22563
+ return false;
22564
+ }
22565
+ return false;
22566
+ }
22567
+ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code, types) {
22480
22568
  const flows = [];
22481
22569
  const callsByLine = new Map;
22482
22570
  for (const call of calls) {
@@ -22497,8 +22585,11 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
22497
22585
  const varName = arg.variable;
22498
22586
  const scopedName = call.in_method ? `${call.in_method}:${varName}` : varName;
22499
22587
  if (taintedVars.has(varName) || taintedVars.has(scopedName)) {
22500
- const source = sources[0];
22588
+ const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, varName);
22501
22589
  if (source) {
22590
+ if (source.variable && source.variable !== varName && source.in_method && call.in_method && source.in_method !== call.in_method) {
22591
+ continue;
22592
+ }
22502
22593
  if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line)) {
22503
22594
  continue;
22504
22595
  }
@@ -22534,8 +22625,11 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
22534
22625
  const collectionVar = match[1];
22535
22626
  const scopedCollection = call.in_method ? `${call.in_method}:${collectionVar}` : collectionVar;
22536
22627
  if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
22537
- const source = sources[0];
22628
+ const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, collectionVar);
22538
22629
  if (source) {
22630
+ if (source.variable && source.variable !== collectionVar && source.in_method && call.in_method && source.in_method !== call.in_method) {
22631
+ continue;
22632
+ }
22539
22633
  if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line)) {
22540
22634
  continue;
22541
22635
  }
@@ -22561,7 +22655,7 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
22561
22655
  }
22562
22656
  return flows;
22563
22657
  }
22564
- function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, unreachableLines) {
22658
+ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, unreachableLines, types) {
22565
22659
  const flows = [];
22566
22660
  const callsByLine = new Map;
22567
22661
  for (const call of calls) {
@@ -22586,7 +22680,7 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
22586
22680
  if (taintedIndices) {
22587
22681
  const isTainted = taintedIndices.has(indexStr) || taintedIndices.has("*");
22588
22682
  if (isTainted) {
22589
- const source = sources[0];
22683
+ const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, arrayName);
22590
22684
  if (source) {
22591
22685
  flows.push({
22592
22686
  source_line: source.line,
@@ -22690,11 +22784,12 @@ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
22690
22784
  const strLit = `(?:"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|\`[^\`\\\\]*(?:\\\\.[^\`\\\\]*)*\`)`;
22691
22785
  const reNaked = new RegExp(`^\\s*${variable}\\s*(?::?=)\\s*${strLit}\\s*;?\\s*$`);
22692
22786
  const reGuarded = new RegExp(`^\\s*if\\b.*\\b${variable}\\s*=\\s*${strLit}\\s*;?\\s*$`);
22787
+ const reSwitchCase = new RegExp(`^\\s*(?:case\\b.*?|default\\s*):\\s*${variable}\\s*=\\s*${strLit}\\s*;?\\s*(?:break\\s*;?)?\\s*$`);
22693
22788
  for (let i2 = lo;i2 < hi; i2++) {
22694
22789
  const line = lines[i2];
22695
22790
  if (!line)
22696
22791
  continue;
22697
- if (reNaked.test(line) || reGuarded.test(line))
22792
+ if (reNaked.test(line) || reGuarded.test(line) || reSwitchCase.test(line))
22698
22793
  return true;
22699
22794
  }
22700
22795
  return false;
@@ -22821,6 +22916,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22821
22916
  for (const source of sourcesWithVar) {
22822
22917
  if (source.line >= sink.line)
22823
22918
  continue;
22919
+ if (source.in_method && call.in_method && source.in_method !== call.in_method) {
22920
+ continue;
22921
+ }
22824
22922
  const re = reCache.get(source.variable);
22825
22923
  if (!re || !re.test(expr))
22826
22924
  continue;
@@ -22887,6 +22985,51 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22887
22985
  }
22888
22986
  return flows;
22889
22987
  }
22988
+ function pickScopedSource(sources, sinkLine, methodName, types, taintedVar) {
22989
+ if (sources.length === 0)
22990
+ return;
22991
+ const closestPreceding = (cands) => {
22992
+ let best;
22993
+ for (const s of cands) {
22994
+ if (s.line >= sinkLine)
22995
+ continue;
22996
+ if (!best || s.line > best.line)
22997
+ best = s;
22998
+ }
22999
+ return best;
23000
+ };
23001
+ if (taintedVar) {
23002
+ const byVar = sources.filter((s) => s.variable === taintedVar);
23003
+ const pick = closestPreceding(byVar);
23004
+ if (pick)
23005
+ return pick;
23006
+ }
23007
+ if (methodName && types && types.length > 0) {
23008
+ let methodStart = -1;
23009
+ let methodEnd = -1;
23010
+ for (const t of types) {
23011
+ for (const m of t.methods) {
23012
+ if (m.name === methodName) {
23013
+ methodStart = m.start_line;
23014
+ methodEnd = m.end_line;
23015
+ break;
23016
+ }
23017
+ }
23018
+ if (methodStart > 0)
23019
+ break;
23020
+ }
23021
+ if (methodStart > 0 && methodEnd >= methodStart) {
23022
+ const inScope = sources.filter((s) => s.line >= methodStart && s.line <= methodEnd);
23023
+ const pick = closestPreceding(inScope);
23024
+ if (pick)
23025
+ return pick;
23026
+ }
23027
+ }
23028
+ const globalPick = closestPreceding(sources);
23029
+ if (globalPick)
23030
+ return globalPick;
23031
+ return sources[0];
23032
+ }
22890
23033
 
22891
23034
  // ../circle-ir/dist/analysis/interprocedural.js
22892
23035
  function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourcesOrSanitizers, sinksOrOptions, sanitizersArg, optionsArg = {}) {
@@ -30904,7 +31047,7 @@ var colors = {
30904
31047
  };
30905
31048
 
30906
31049
  // src/version.ts
30907
- var version = "3.62.0";
31050
+ var version = "3.65.0";
30908
31051
 
30909
31052
  // src/formatters.ts
30910
31053
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.62.0",
3
+ "version": "3.65.0",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,7 +65,7 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "circle-ir": "^3.62.0"
68
+ "circle-ir": "^3.65.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",