cognium-dev 3.191.0 → 3.193.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 +111 -12
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -15947,22 +15947,36 @@ class CrossFileResolver {
15947
15947
  if (source.type === "interprocedural_param")
15948
15948
  continue;
15949
15949
  const sourceVar = source.variable ?? this.getLocalDefVarAt(ir, source.line);
15950
+ const reachable = sourceVar ? this.collectTaintReachable(ir, sourceVar, source.line, filePath) : undefined;
15950
15951
  for (const call of ir.calls) {
15951
15952
  if (call.location.line < source.line)
15952
15953
  continue;
15953
15954
  const resolved = this.resolveCall(call, filePath);
15954
15955
  if (!resolved || resolved.targetFile === filePath)
15955
15956
  continue;
15956
- if (sourceVar) {
15957
- const argMentions = call.arguments.some((arg) => {
15958
- if (arg.variable === sourceVar)
15959
- return true;
15960
- if (arg.expression && new RegExp(`\\b${sourceVar}\\b`).test(arg.expression))
15961
- return true;
15962
- return false;
15963
- });
15964
- if (!argMentions)
15957
+ if (reachable) {
15958
+ let matchedName;
15959
+ for (const arg of call.arguments) {
15960
+ if (arg.variable && reachable.names.has(arg.variable)) {
15961
+ matchedName = arg.variable;
15962
+ break;
15963
+ }
15964
+ if (arg.expression) {
15965
+ for (const v of reachable.names) {
15966
+ if (new RegExp(`\\b${v}\\b`).test(arg.expression)) {
15967
+ matchedName = v;
15968
+ break;
15969
+ }
15970
+ }
15971
+ if (matchedName)
15972
+ break;
15973
+ }
15974
+ }
15975
+ if (!matchedName)
15976
+ continue;
15977
+ if (this.taintClobbered(ir, call.location.line, matchedName, reachable.defIds)) {
15965
15978
  continue;
15979
+ }
15966
15980
  }
15967
15981
  const targetIR = this.fileIRs.get(resolved.targetFile);
15968
15982
  if (!targetIR || targetIR.taint.sinks.length === 0)
@@ -16023,7 +16037,8 @@ class CrossFileResolver {
16023
16037
  file: callerFile,
16024
16038
  line: src.line,
16025
16039
  type: src.type,
16026
- hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }]
16040
+ hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }],
16041
+ defIds: this.collectTaintReachable(callerIR, src.variable, src.line, callerFile).defIds
16027
16042
  });
16028
16043
  }
16029
16044
  const callsInMethod = callerIdx.callsByMethod.get(method) ?? [];
@@ -16052,7 +16067,8 @@ class CrossFileResolver {
16052
16067
  file: sourceFile,
16053
16068
  line: sourceLine,
16054
16069
  type: sourceType,
16055
- hopChain: baseChain
16070
+ hopChain: baseChain,
16071
+ defIds: this.forwardReachableDefs(callerIR, [def.id], callerFile)
16056
16072
  });
16057
16073
  }
16058
16074
  }
@@ -16065,6 +16081,9 @@ class CrossFileResolver {
16065
16081
  const matched = this.matchTaintedArg(arg, tainted);
16066
16082
  if (!matched)
16067
16083
  continue;
16084
+ if (this.taintClobbered(callerIR, call.location.line, matched.var, matched.origin.defIds)) {
16085
+ continue;
16086
+ }
16068
16087
  const calleeNode = methodIndex.get(resolved.targetMethod);
16069
16088
  if (!calleeNode)
16070
16089
  continue;
@@ -16398,6 +16417,62 @@ class CrossFileResolver {
16398
16417
  }
16399
16418
  return;
16400
16419
  }
16420
+ collectTaintReachable(ir, sourceVar, sourceLine, filePath) {
16421
+ const defs = ir.dfg.defs;
16422
+ let seedDefs = defs.filter((d) => d.variable === sourceVar && d.line === sourceLine);
16423
+ if (seedDefs.length === 0) {
16424
+ const named = defs.filter((d) => d.variable === sourceVar).sort((a, b) => a.line - b.line);
16425
+ if (named.length > 0)
16426
+ seedDefs = [named[0]];
16427
+ }
16428
+ const defIds = this.forwardReachableDefs(ir, seedDefs.map((d) => d.id), filePath);
16429
+ const names = new Set([sourceVar]);
16430
+ for (const d of defs) {
16431
+ if (d.variable && defIds.has(d.id))
16432
+ names.add(d.variable);
16433
+ }
16434
+ return { names, defIds };
16435
+ }
16436
+ forwardReachableDefs(ir, seedDefIds, filePath) {
16437
+ const defIds = new Set(seedDefIds);
16438
+ const chains = ir.dfg.chains;
16439
+ if (!chains || chains.length === 0 || seedDefIds.length === 0)
16440
+ return defIds;
16441
+ const lineByDefId = new Map;
16442
+ for (const d of ir.dfg.defs)
16443
+ lineByDefId.set(d.id, d.line);
16444
+ const crossFileCallLines = new Set;
16445
+ for (const call of ir.calls) {
16446
+ const resolved = this.resolveCall(call, filePath);
16447
+ if (resolved && resolved.targetFile !== filePath) {
16448
+ crossFileCallLines.add(call.location.line);
16449
+ }
16450
+ }
16451
+ const frontier = [...seedDefIds];
16452
+ const visited = new Set(frontier);
16453
+ while (frontier.length > 0) {
16454
+ const cur = frontier.shift();
16455
+ for (const ch of chains) {
16456
+ if (ch.from_def !== cur || visited.has(ch.to_def))
16457
+ continue;
16458
+ visited.add(ch.to_def);
16459
+ const toLine = lineByDefId.get(ch.to_def);
16460
+ if (toLine !== undefined && crossFileCallLines.has(toLine))
16461
+ continue;
16462
+ defIds.add(ch.to_def);
16463
+ frontier.push(ch.to_def);
16464
+ }
16465
+ }
16466
+ return defIds;
16467
+ }
16468
+ taintClobbered(ir, callLine, varName, taintDefIds) {
16469
+ if (!taintDefIds || taintDefIds.size === 0)
16470
+ return false;
16471
+ const use = ir.dfg.uses.find((u) => u.line === callLine && u.variable === varName && u.def_id !== null);
16472
+ if (!use || use.def_id === null)
16473
+ return false;
16474
+ return !taintDefIds.has(use.def_id);
16475
+ }
16401
16476
  findRealSourceLineInMethod(ir, method) {
16402
16477
  for (const src of ir.taint.sources) {
16403
16478
  if (src.type === "interprocedural_param")
@@ -24977,6 +25052,7 @@ class LanguageSourcesPass {
24977
25052
  }
24978
25053
  additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
24979
25054
  additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
25055
+ additionalSanitizers.push(...findBashShellQuoteSanitizers(code));
24980
25056
  }
24981
25057
  if (language === "go") {
24982
25058
  additionalSanitizers.push(...findGoMapAllowlistGuardSanitizers(code));
@@ -26426,6 +26502,29 @@ function hasIntegrityVerifierAnywhere(lines) {
26426
26502
  }
26427
26503
  return false;
26428
26504
  }
26505
+ function findBashShellQuoteSanitizers(code) {
26506
+ const sanitizers = [];
26507
+ const lines = code.split(`
26508
+ `);
26509
+ const PRINTF_Q_RE = /\$\(\s*printf\s+['"]%q['"]\s+["']?\$/;
26510
+ const AT_Q_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*@Q\}/;
26511
+ for (let i2 = 0;i2 < lines.length; i2++) {
26512
+ const line = lines[i2];
26513
+ if (line.trimStart().startsWith("#"))
26514
+ continue;
26515
+ if (!PRINTF_Q_RE.test(line) && !AT_Q_RE.test(line))
26516
+ continue;
26517
+ for (let l = i2 + 1;l <= lines.length; l++) {
26518
+ sanitizers.push({
26519
+ type: "shell_quote",
26520
+ method: PRINTF_Q_RE.test(line) ? "printf '%q'" : "${var@Q}",
26521
+ line: l,
26522
+ sanitizes: ["command_injection", "code_injection"]
26523
+ });
26524
+ }
26525
+ }
26526
+ return sanitizers;
26527
+ }
26429
26528
  function findBashRegexAllowlistSanitizers(code) {
26430
26529
  const sanitizers = [];
26431
26530
  const lines = code.split(`
@@ -45980,7 +46079,7 @@ var colors = {
45980
46079
  };
45981
46080
 
45982
46081
  // src/version.ts
45983
- var version = "3.191.0";
46082
+ var version = "3.193.0";
45984
46083
 
45985
46084
  // src/formatters.ts
45986
46085
  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.191.0",
3
+ "version": "3.193.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.191.0"
69
+ "circle-ir": "^3.193.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",