cognium-dev 3.181.0 → 3.182.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 +208 -9
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -9440,6 +9440,10 @@ function processGoBlock(node, defs, uses, scopeStack, counters) {
9440
9440
  } else if (child.type === "for_statement") {
9441
9441
  const rangeClause = findChildByTypeGo(child, "range_clause");
9442
9442
  if (rangeClause) {
9443
+ const right = rangeClause.childForFieldName("right");
9444
+ if (right) {
9445
+ extractGoUses(right, uses, scopeStack);
9446
+ }
9443
9447
  const left = rangeClause.childForFieldName("left");
9444
9448
  if (left) {
9445
9449
  extractGoLhsDefs(left, defs, scopeStack, child.startPosition.row + 1);
@@ -9447,6 +9451,7 @@ function processGoBlock(node, defs, uses, scopeStack, counters) {
9447
9451
  }
9448
9452
  } else if (child.type === "call_expression") {
9449
9453
  extractGoUses(child, uses, scopeStack);
9454
+ recordGoOpaqueCodecDestDef(child, defs, scopeStack);
9450
9455
  } else if (child.type === "return_statement") {
9451
9456
  for (let i2 = 0;i2 < child.childCount; i2++) {
9452
9457
  const expr = child.child(i2);
@@ -9546,6 +9551,70 @@ function findChildByTypeGo(node, type) {
9546
9551
  }
9547
9552
  return null;
9548
9553
  }
9554
+ var GO_OPAQUE_CODEC_METHODS = new Set([
9555
+ "Unmarshal",
9556
+ "Decode",
9557
+ "NewDecoder",
9558
+ "UnmarshalYAML",
9559
+ "UnmarshalJSON",
9560
+ "UnmarshalText",
9561
+ "UnmarshalBinary"
9562
+ ]);
9563
+ function recordGoOpaqueCodecDestDef(call, defs, scopeStack) {
9564
+ const fn = call.childForFieldName("function");
9565
+ if (!fn || fn.type !== "selector_expression")
9566
+ return;
9567
+ const fieldNode = fn.childForFieldName("field");
9568
+ if (!fieldNode)
9569
+ return;
9570
+ const method = getNodeText(fieldNode);
9571
+ if (!GO_OPAQUE_CODEC_METHODS.has(method))
9572
+ return;
9573
+ const argsNode = call.childForFieldName("arguments");
9574
+ if (!argsNode)
9575
+ return;
9576
+ const args2 = [];
9577
+ for (let i2 = 0;i2 < argsNode.childCount; i2++) {
9578
+ const c = argsNode.child(i2);
9579
+ if (!c)
9580
+ continue;
9581
+ if (c.type === "," || c.type === "(" || c.type === ")")
9582
+ continue;
9583
+ args2.push(c);
9584
+ }
9585
+ if (args2.length === 0)
9586
+ return;
9587
+ const destArg = args2.length >= 2 ? args2[1] : args2[0];
9588
+ const destName = extractGoAddressableVarName(destArg);
9589
+ if (!destName || destName === "_")
9590
+ return;
9591
+ const line = call.startPosition.row + 1;
9592
+ const def = {
9593
+ id: defs.length + 1,
9594
+ variable: destName,
9595
+ kind: "local",
9596
+ line
9597
+ };
9598
+ defs.push(def);
9599
+ currentScope(scopeStack).set(destName, def.id);
9600
+ }
9601
+ function extractGoAddressableVarName(node) {
9602
+ if (node.type === "unary_expression") {
9603
+ const operand = node.childForFieldName("operand") ?? node.child(1) ?? null;
9604
+ if (operand)
9605
+ return extractGoAddressableVarName(operand);
9606
+ return null;
9607
+ }
9608
+ if (node.type === "identifier") {
9609
+ return getNodeText(node);
9610
+ }
9611
+ if (node.type === "selector_expression") {
9612
+ const field = node.childForFieldName("field");
9613
+ if (field)
9614
+ return getNodeText(field);
9615
+ }
9616
+ return null;
9617
+ }
9549
9618
  // ../circle-ir/dist/core/extractors/runtime-registrations.js
9550
9619
  var HTTP_VERB_METHODS = new Set([
9551
9620
  "get",
@@ -16432,15 +16501,23 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
16432
16501
  const callsAtSink = callsByLine.get(sink.line) ?? [];
16433
16502
  for (const call of callsAtSink) {
16434
16503
  for (const arg of call.arguments) {
16435
- if (arg.variable) {
16436
- if (sink.argPositions && sink.argPositions.length > 0) {
16437
- if (!sink.argPositions.includes(arg.position)) {
16438
- continue;
16439
- }
16504
+ if (sink.argPositions && sink.argPositions.length > 0) {
16505
+ if (!sink.argPositions.includes(arg.position)) {
16506
+ continue;
16440
16507
  }
16441
- for (const use of usesAtSink) {
16442
- if (use.variable === arg.variable && use.def_id !== null) {
16508
+ }
16509
+ const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
16510
+ {
16511
+ for (const use of candidateUses) {
16512
+ if (use.def_id !== null) {
16443
16513
  if (allTaintedDefIds.has(use.def_id)) {
16514
+ if (!arg.variable) {
16515
+ if (typeof arg.expression !== "string" || arg.expression.length === 0)
16516
+ continue;
16517
+ const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
16518
+ if (!re.test(arg.expression))
16519
+ continue;
16520
+ }
16444
16521
  const taintInfo = taintByDefId.get(use.def_id);
16445
16522
  if (taintInfo) {
16446
16523
  const isSanitized = checkSanitized(taintInfo.line, sink.line, sink.type, sanitizersByLine, {
@@ -16468,7 +16545,16 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
16468
16545
  }
16469
16546
  }
16470
16547
  }
16471
- return { taintedVars, flows, reachableSinks };
16548
+ const seen = new Set;
16549
+ const deduped = [];
16550
+ for (const f of flows) {
16551
+ const key = `${f.source.line}|${f.sink.line}|${f.sink.type}|${f.path.map((p) => p.variable).join(">")}`;
16552
+ if (seen.has(key))
16553
+ continue;
16554
+ seen.add(key);
16555
+ deduped.push(f);
16556
+ }
16557
+ return { taintedVars, flows: deduped, reachableSinks };
16472
16558
  }
16473
16559
  function findInitialTaint(sources, callsByLine, defsByLine) {
16474
16560
  const tainted = [];
@@ -33393,6 +33479,12 @@ class TaintPropagationPass {
33393
33479
  continue;
33394
33480
  pushIfNew(f);
33395
33481
  }
33482
+ if (ctx.language === "go" && typeof ctx.code === "string") {
33483
+ const goGlobalFlows = detectGoPackageGlobalFlows(ctx.code, calls, sources, sinks, constProp.unreachableLines) ?? [];
33484
+ for (const f of goGlobalFlows) {
33485
+ pushIfNew(f);
33486
+ }
33487
+ }
33396
33488
  const sanitizedNames = constProp.sanitizedVars;
33397
33489
  let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
33398
33490
  if (f.path.length === 0)
@@ -33649,6 +33741,113 @@ function isInJavaSanitizedMethod(code, types, sinkLine, sinkType) {
33649
33741
  }
33650
33742
  return false;
33651
33743
  }
33744
+ function detectGoPackageGlobalFlows(code, _calls, sources, sinks, unreachableLines) {
33745
+ const flows = [];
33746
+ if (!code || sinks.length === 0)
33747
+ return flows;
33748
+ const lines = code.split(`
33749
+ `);
33750
+ const packageVars = new Set;
33751
+ let inVarBlock = false;
33752
+ for (const line of lines) {
33753
+ const stripped = line.replace(/\s+$/, "");
33754
+ if (!stripped)
33755
+ continue;
33756
+ if (/^[ \t]/.test(line)) {
33757
+ if (inVarBlock) {
33758
+ const m = stripped.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s+/);
33759
+ if (m)
33760
+ packageVars.add(m[1]);
33761
+ if (/^\s*\)/.test(stripped))
33762
+ inVarBlock = false;
33763
+ }
33764
+ continue;
33765
+ }
33766
+ if (/^var\s*\(/.test(stripped)) {
33767
+ inVarBlock = true;
33768
+ continue;
33769
+ }
33770
+ if (inVarBlock && /^\)/.test(stripped)) {
33771
+ inVarBlock = false;
33772
+ continue;
33773
+ }
33774
+ const varOne = stripped.match(/^var\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
33775
+ if (varOne)
33776
+ packageVars.add(varOne[1]);
33777
+ }
33778
+ if (packageVars.size === 0)
33779
+ return flows;
33780
+ const sourceVarNames = new Set;
33781
+ for (const s of sources) {
33782
+ if (typeof s.variable === "string" && s.variable.length > 0) {
33783
+ sourceVarNames.add(s.variable);
33784
+ }
33785
+ }
33786
+ const GO_SOURCE_RE = /\b(r\.(?:URL\.Query|Form|PostForm|Header|Cookie|Body)|mux\.Vars|c\.(?:Query|Param|PostForm|GetHeader|Cookie)|ctx\.(?:Query|Param|PostForm|GetHeader|Cookie))\b/;
33787
+ const writes = [];
33788
+ for (let i2 = 0;i2 < lines.length; i2++) {
33789
+ const line = lines[i2];
33790
+ if (unreachableLines.has(i2 + 1))
33791
+ continue;
33792
+ if (/:=/.test(line))
33793
+ continue;
33794
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+)$/);
33795
+ if (!m)
33796
+ continue;
33797
+ const [, lhs, rhs] = m;
33798
+ if (!packageVars.has(lhs))
33799
+ continue;
33800
+ let taintedBy = null;
33801
+ if (GO_SOURCE_RE.test(rhs)) {
33802
+ const src = sources.find((s) => s.line === i2 + 1);
33803
+ taintedBy = src ? { line: src.line, type: src.type } : { line: i2 + 1, type: "http_param" };
33804
+ } else {
33805
+ for (const v of sourceVarNames) {
33806
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
33807
+ if (re.test(rhs)) {
33808
+ const src = sources.find((s) => s.variable === v);
33809
+ if (src) {
33810
+ taintedBy = { line: src.line, type: src.type };
33811
+ break;
33812
+ }
33813
+ }
33814
+ }
33815
+ }
33816
+ if (!taintedBy)
33817
+ continue;
33818
+ writes.push({ varName: lhs, line: i2 + 1, sourceLine: taintedBy.line, sourceType: taintedBy.type });
33819
+ }
33820
+ if (writes.length === 0)
33821
+ return flows;
33822
+ for (const sink of sinks) {
33823
+ if (unreachableLines.has(sink.line))
33824
+ continue;
33825
+ const sinkLineText = lines[sink.line - 1] ?? "";
33826
+ if (!sinkLineText)
33827
+ continue;
33828
+ for (const w of writes) {
33829
+ if (w.line >= sink.line)
33830
+ continue;
33831
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${w.varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
33832
+ if (!re.test(sinkLineText))
33833
+ continue;
33834
+ flows.push({
33835
+ source_line: w.sourceLine,
33836
+ sink_line: sink.line,
33837
+ source_type: w.sourceType,
33838
+ sink_type: sink.type,
33839
+ path: [
33840
+ { variable: w.varName, line: w.sourceLine, type: "source" },
33841
+ { variable: w.varName, line: w.line, type: "assignment" },
33842
+ { variable: w.varName, line: sink.line, type: "sink" }
33843
+ ],
33844
+ confidence: 0.9,
33845
+ sanitized: false
33846
+ });
33847
+ }
33848
+ }
33849
+ return flows;
33850
+ }
33652
33851
  function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code, types) {
33653
33852
  const flows = [];
33654
33853
  const callsByLine = new Map;
@@ -45451,7 +45650,7 @@ var colors = {
45451
45650
  };
45452
45651
 
45453
45652
  // src/version.ts
45454
- var version = "3.181.0";
45653
+ var version = "3.182.0";
45455
45654
 
45456
45655
  // src/formatters.ts
45457
45656
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.181.0",
3
+ "version": "3.182.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",
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@cognium/project-profile-detect": "^1.1.0",
69
- "circle-ir": "^3.181.0"
69
+ "circle-ir": "^3.182.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",