cognium-dev 4.9.22 → 4.9.24

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 +331 -9
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -14400,6 +14400,35 @@ function classifyGoDestinationType(decl) {
14400
14400
  return "unsafe";
14401
14401
  return "safe";
14402
14402
  }
14403
+ function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
14404
+ if (language !== "go")
14405
+ return false;
14406
+ if (pattern.type !== "xss" || pattern.class !== "fmt")
14407
+ return false;
14408
+ if (!/^Fprint(?:f|ln)?$/.test(call.method_name))
14409
+ return false;
14410
+ if (!sourceLines)
14411
+ return true;
14412
+ const writer = call.arguments.find((a) => a.position === 0);
14413
+ const name2 = (writer?.expression ?? "").trim();
14414
+ if (!/^[A-Za-z_]\w*$/.test(name2))
14415
+ return true;
14416
+ const rwRe = new RegExp(`\\b${name2}\\s+(?:net/)?http\\.ResponseWriter\\b`);
14417
+ for (let i2 = call.location.line - 1;i2 >= 0; i2--) {
14418
+ const line = sourceLines[i2] ?? "";
14419
+ const funcIdx = line.search(/\bfunc\b/);
14420
+ if (funcIdx < 0)
14421
+ continue;
14422
+ const header = sourceLines.slice(i2, i2 + 4).join(" ");
14423
+ const brace = header.indexOf("{");
14424
+ const params = header.slice(funcIdx, brace >= 0 ? brace : header.length);
14425
+ if (rwRe.test(params))
14426
+ return false;
14427
+ if (/^\s*func\b/.test(line))
14428
+ return true;
14429
+ }
14430
+ return true;
14431
+ }
14403
14432
  function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
14404
14433
  if (language !== "go")
14405
14434
  return false;
@@ -14545,6 +14574,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
14545
14574
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
14546
14575
  continue;
14547
14576
  }
14577
+ if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
14578
+ continue;
14579
+ }
14548
14580
  if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
14549
14581
  continue;
14550
14582
  }
@@ -16814,6 +16846,9 @@ function buildFileIndex(ir) {
16814
16846
  }
16815
16847
  return { callsByLine, defsByLine, usesByLine, callsByMethod, sinksByMethod, defsByMethod };
16816
16848
  }
16849
+ function escapeRegExp(s) {
16850
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16851
+ }
16817
16852
 
16818
16853
  class CrossFileResolver {
16819
16854
  fileIndexes = new WeakMap;
@@ -17182,7 +17217,7 @@ class CrossFileResolver {
17182
17217
  candidates.push(arg.variable);
17183
17218
  if (arg.expression) {
17184
17219
  for (const [name2] of paramNameToIndex) {
17185
- const re = new RegExp(`\\b${name2}\\b`);
17220
+ const re = new RegExp(`\\b${escapeRegExp(name2)}\\b`);
17186
17221
  if (re.test(arg.expression))
17187
17222
  candidates.push(name2);
17188
17223
  }
@@ -17266,7 +17301,7 @@ class CrossFileResolver {
17266
17301
  }
17267
17302
  if (arg.expression) {
17268
17303
  for (const v of reachable.names) {
17269
- if (new RegExp(`\\b${v}\\b`).test(arg.expression)) {
17304
+ if (new RegExp(`\\b${escapeRegExp(v)}\\b`).test(arg.expression)) {
17270
17305
  matchedName = v;
17271
17306
  break;
17272
17307
  }
@@ -17697,7 +17732,7 @@ class CrossFileResolver {
17697
17732
  }
17698
17733
  if (arg.expression) {
17699
17734
  for (const [tv, origin] of tainted) {
17700
- const re = new RegExp(`\\b${tv}\\b`);
17735
+ const re = new RegExp(`\\b${escapeRegExp(tv)}\\b`);
17701
17736
  if (re.test(arg.expression))
17702
17737
  return { var: tv, origin };
17703
17738
  }
@@ -24126,7 +24161,23 @@ class GoPlugin extends BaseLanguagePlugin {
24126
24161
  type: "xss",
24127
24162
  cwe: "CWE-79",
24128
24163
  severity: "high",
24129
- argPositions: [1]
24164
+ argPositions: [1, 2, 3, 4, 5, 6]
24165
+ },
24166
+ {
24167
+ method: "Fprint",
24168
+ class: "fmt",
24169
+ type: "xss",
24170
+ cwe: "CWE-79",
24171
+ severity: "high",
24172
+ argPositions: [1, 2, 3, 4, 5, 6]
24173
+ },
24174
+ {
24175
+ method: "Fprintln",
24176
+ class: "fmt",
24177
+ type: "xss",
24178
+ cwe: "CWE-79",
24179
+ severity: "high",
24180
+ argPositions: [1, 2, 3, 4, 5, 6]
24130
24181
  },
24131
24182
  {
24132
24183
  method: "Write",
@@ -25810,6 +25861,8 @@ class LanguageSourcesPass {
25810
25861
  additionalSources.push(...findGoArgvSources(code, language));
25811
25862
  additionalSources.push(...findGoArchiveEntrySources(code, language));
25812
25863
  additionalSources.push(...findGoMultipartUploadSources(code, language));
25864
+ additionalSources.push(...findGoGrpcRequestSources(code, language));
25865
+ additionalSources.push(...findGoRequestBodySources(code, language));
25813
25866
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
25814
25867
  for (const s of jsDOMSinks) {
25815
25868
  const alreadyExists = additionalSinks.some((x) => x.line === s.line && x.cwe === s.cwe);
@@ -26555,6 +26608,127 @@ function findGoMultipartUploadSources(sourceCode, language) {
26555
26608
  }
26556
26609
  return sources;
26557
26610
  }
26611
+ function findGoGrpcRequestSources(sourceCode, language) {
26612
+ if (language !== "go")
26613
+ return [];
26614
+ if (!sourceCode.includes("context.Context"))
26615
+ return [];
26616
+ const sources = [];
26617
+ const lines = sourceCode.split(`
26618
+ `);
26619
+ const sigRe = /^func\s*\([^)]*\)\s*[A-Z]\w*\s*\(\s*\w+\s+context\.Context\s*,\s*([A-Za-z_]\w*)\s+\*(?:\w+\.)?\w*(?:Request|Req)\s*\)\s*\(\s*\*[\w.]+\s*,\s*error\s*\)\s*\{/;
26620
+ const controlRe = /^\s*(?:\}\s*else\s+)?(?:if|for|switch|case|return)\b/;
26621
+ for (let i2 = 0;i2 < lines.length; i2++) {
26622
+ const sig = sigRe.exec(lines[i2]);
26623
+ if (!sig)
26624
+ continue;
26625
+ const param = sig[1];
26626
+ if (param === "_")
26627
+ continue;
26628
+ const readRe = new RegExp(`(?<![\\w.])${param}\\s*\\.\\s*(?:Get[A-Z]\\w*\\s*\\(\\s*\\)|[A-Z]\\w*\\b(?!\\s*\\())`);
26629
+ const directReadRe = new RegExp(`^(?:(?:\\[\\]\\w+|string)\\s*\\(\\s*)*${param}(?:\\s*\\.\\s*(?:Get[A-Z]\\w*\\s*\\(\\s*\\)|[A-Z]\\w*\\b(?!\\s*\\()))+(?:\\s*\\))*$`);
26630
+ const numericParseRe = new RegExp(`\\bstrconv\\s*\\.\\s*(?:Atoi|ParseInt|ParseUint|ParseFloat|ParseBool)\\s*\\(\\s*${param}\\s*\\.`);
26631
+ let end = i2 + 1;
26632
+ while (end < lines.length && !/^\}/.test(lines[end]))
26633
+ end++;
26634
+ for (let j = i2 + 1;j < end; j++) {
26635
+ const line = lines[j];
26636
+ const read = readRe.exec(line);
26637
+ if (!read)
26638
+ continue;
26639
+ if (numericParseRe.test(line))
26640
+ continue;
26641
+ let variable;
26642
+ const unmarshal = /\bUnmarshal\w*\s*\([^&]*&\s*([A-Za-z_]\w*)/.exec(line);
26643
+ const assign = /^\s*(?:var\s+)?([A-Za-z_][\w\s,]*?)\s*:?=(?!=)\s*(.+)$/.exec(line);
26644
+ if (unmarshal && line.indexOf(read[0]) < line.indexOf("&")) {
26645
+ variable = unmarshal[1];
26646
+ } else if (assign && directReadRe.test(assign[2].trim()) && !controlRe.test(line)) {
26647
+ variable = assign[1].split(",").map((v) => v.trim()).find((v) => v !== "_" && v !== "err" && v !== "ok" && /^[A-Za-z_]\w*$/.test(v));
26648
+ if (!variable)
26649
+ continue;
26650
+ } else if (controlRe.test(line)) {
26651
+ continue;
26652
+ } else {
26653
+ variable = param;
26654
+ }
26655
+ const lineNumber = j + 1;
26656
+ if (sources.some((sc) => sc.line === lineNumber && sc.variable === variable))
26657
+ continue;
26658
+ sources.push({
26659
+ type: "http_body",
26660
+ location: `${read[0].replace(/\s+/g, "")} — field of the gRPC request message \`${param}\``,
26661
+ severity: "high",
26662
+ line: lineNumber,
26663
+ confidence: 0.85,
26664
+ variable
26665
+ });
26666
+ }
26667
+ i2 = end;
26668
+ }
26669
+ return sources;
26670
+ }
26671
+ function findGoRequestBodySources(sourceCode, language) {
26672
+ if (language !== "go")
26673
+ return [];
26674
+ if (!sourceCode.includes(".Body"))
26675
+ return [];
26676
+ const lines = sourceCode.split(`
26677
+ `);
26678
+ const reqNames = new Set;
26679
+ const paramRe = /([A-Za-z_]\w*)\s+\*http\.Request\b/g;
26680
+ for (const line of lines) {
26681
+ let m;
26682
+ paramRe.lastIndex = 0;
26683
+ while ((m = paramRe.exec(line)) !== null)
26684
+ reqNames.add(m[1]);
26685
+ }
26686
+ if (reqNames.size === 0)
26687
+ return [];
26688
+ const bodyAlt = [...reqNames].map((n) => n.replace(/\$/g, "\\$")).join("|");
26689
+ const bodyRef = `(?<![\\w.])(?:${bodyAlt})\\s*\\.\\s*Body\\b`;
26690
+ const sources = [];
26691
+ const push = (variable, line, how) => {
26692
+ if (variable === "_" || variable === "")
26693
+ return;
26694
+ if (sources.some((s) => s.line === line && s.variable === variable))
26695
+ return;
26696
+ sources.push({
26697
+ type: "http_body",
26698
+ location: `${how} — HTTP request body`,
26699
+ severity: "high",
26700
+ line,
26701
+ confidence: 0.85,
26702
+ ...variable ? { variable } : {}
26703
+ });
26704
+ };
26705
+ const bodyRe = new RegExp(bodyRef);
26706
+ const decodeRe = new RegExp(`(?:json|xml)\\s*\\.\\s*NewDecoder\\s*\\(\\s*(?:io\\.NopCloser\\s*\\(\\s*)?(?:${bodyAlt})\\s*\\.\\s*Body\\b[^)]*\\)\\s*\\)?\\s*\\.\\s*Decode\\s*\\(\\s*&\\s*([A-Za-z_]\\w*)`);
26707
+ const readAllRe = new RegExp(`\\b([A-Za-z_]\\w*)\\s*,\\s*\\w+\\s*:?=\\s*(?:io|ioutil)\\s*\\.\\s*ReadAll\\s*\\(\\s*(?:${bodyAlt})\\s*\\.\\s*Body\\b`);
26708
+ const copyRe = new RegExp(`\\bio\\s*\\.\\s*Copy\\s*\\(\\s*([A-Za-z_]\\w*|&\\s*[A-Za-z_]\\w*)\\s*,\\s*(?:${bodyAlt})\\s*\\.\\s*Body\\b`);
26709
+ for (let i2 = 0;i2 < lines.length; i2++) {
26710
+ const line = lines[i2];
26711
+ if (!bodyRe.test(line))
26712
+ continue;
26713
+ const n = i2 + 1;
26714
+ const dec = decodeRe.exec(line);
26715
+ if (dec) {
26716
+ push(dec[1], n, `json/xml decode of ${dec[0].split(".")[0]}.Body`);
26717
+ continue;
26718
+ }
26719
+ const ra = readAllRe.exec(line);
26720
+ if (ra) {
26721
+ push(ra[1], n, "io.ReadAll(req.Body)");
26722
+ continue;
26723
+ }
26724
+ const cp = copyRe.exec(line);
26725
+ if (cp) {
26726
+ push(cp[1].replace(/^&\s*/, ""), n, "io.Copy(dst, req.Body)");
26727
+ continue;
26728
+ }
26729
+ }
26730
+ return sources;
26731
+ }
26558
26732
  function findCSharpRequestSources(sourceCode, language) {
26559
26733
  if (language !== "csharp")
26560
26734
  return [];
@@ -37487,7 +37661,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
37487
37661
  for (const other of taintedVars) {
37488
37662
  if (other === varName)
37489
37663
  continue;
37490
- const re = new RegExp(`\\b${other}\\b`);
37664
+ const re = new RegExp(`\\b${other.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
37491
37665
  if (!re.test(rhs))
37492
37666
  continue;
37493
37667
  anyReferenced = true;
@@ -37718,7 +37892,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
37718
37892
  }
37719
37893
  const assignMatch = sinkCode.match(/^\s*(?:[A-Za-z_][\w.<>[\]\s,?]*\s+)?[A-Za-z_]\w*\s*=(?!=)\s*/);
37720
37894
  const rhs = assignMatch ? sinkCode.slice(assignMatch[0].length) : sinkCode;
37721
- if (new RegExp(`\\b${sourceVar}\\b`).test(rhs)) {
37895
+ if (new RegExp(`\\b${sourceVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(rhs)) {
37722
37896
  continue;
37723
37897
  }
37724
37898
  }
@@ -37790,6 +37964,116 @@ function pickScopedSource(sources, sinkLine, methodName, types, taintedVar) {
37790
37964
  }
37791
37965
 
37792
37966
  // ../circle-ir/dist/analysis/interprocedural.js
37967
+ var GO_BUILTIN_CALLS = new Set([
37968
+ "len",
37969
+ "cap",
37970
+ "make",
37971
+ "new",
37972
+ "append",
37973
+ "copy",
37974
+ "delete",
37975
+ "close",
37976
+ "panic",
37977
+ "recover",
37978
+ "print",
37979
+ "println",
37980
+ "min",
37981
+ "max",
37982
+ "clear",
37983
+ "string",
37984
+ "int",
37985
+ "int8",
37986
+ "int16",
37987
+ "int32",
37988
+ "int64",
37989
+ "uint",
37990
+ "uint8",
37991
+ "uint16",
37992
+ "uint32",
37993
+ "uint64",
37994
+ "float32",
37995
+ "float64",
37996
+ "bool",
37997
+ "byte",
37998
+ "rune",
37999
+ "error"
38000
+ ]);
38001
+ var GO_ERROR_AND_LOG_CALLS = new Set([
38002
+ "Errorf",
38003
+ "Sprintf",
38004
+ "Sprint",
38005
+ "Sprintln",
38006
+ "Print",
38007
+ "Printf",
38008
+ "Println",
38009
+ "Fatal",
38010
+ "Fatalf",
38011
+ "Fatalln",
38012
+ "Panic",
38013
+ "Panicf",
38014
+ "Panicln",
38015
+ "Debug",
38016
+ "Debugf",
38017
+ "Debugln",
38018
+ "Debugw",
38019
+ "Info",
38020
+ "Infof",
38021
+ "Infoln",
38022
+ "Infow",
38023
+ "InfoS",
38024
+ "Warn",
38025
+ "Warnf",
38026
+ "Warnln",
38027
+ "Warnw",
38028
+ "Warning",
38029
+ "Warningf",
38030
+ "Warningln",
38031
+ "Error",
38032
+ "Errorln",
38033
+ "Errorw",
38034
+ "ErrorS",
38035
+ "Trace",
38036
+ "Tracef",
38037
+ "V",
38038
+ "WithError",
38039
+ "WithField",
38040
+ "WithFields",
38041
+ "WithValues",
38042
+ "Msg",
38043
+ "Msgf"
38044
+ ]);
38045
+ var GO_ERROR_PACKAGES = new Set(["errors", "status", "xerrors", "multierr", "multierror"]);
38046
+ var GO_PURE_PACKAGES = new Set([
38047
+ "strings",
38048
+ "strconv",
38049
+ "bytes",
38050
+ "unicode",
38051
+ "utf8",
38052
+ "path",
38053
+ "filepath",
38054
+ "math",
38055
+ "sort",
38056
+ "slices",
38057
+ "maps",
38058
+ "time",
38059
+ "regexp",
38060
+ "url",
38061
+ "hex",
38062
+ "base64"
38063
+ ]);
38064
+ function isGoNonEscapeCall(call) {
38065
+ const name2 = call.method_name;
38066
+ const recv = (call.receiver ?? "").split(".").pop() ?? "";
38067
+ if (!call.receiver && GO_BUILTIN_CALLS.has(name2))
38068
+ return true;
38069
+ if (GO_ERROR_PACKAGES.has(recv))
38070
+ return true;
38071
+ if (GO_PURE_PACKAGES.has(recv))
38072
+ return true;
38073
+ if (GO_ERROR_AND_LOG_CALLS.has(name2))
38074
+ return true;
38075
+ return false;
38076
+ }
37793
38077
  function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourcesOrSanitizers, sinksOrOptions, sanitizersArg, optionsArg = {}) {
37794
38078
  let graph;
37795
38079
  let sources;
@@ -38021,6 +38305,9 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
38021
38305
  if (!targetMethod) {
38022
38306
  if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name) && !ormQueryBuilderMethods.has(call.method_name)) {
38023
38307
  const isBash = graph.ir.meta.language === "bash";
38308
+ if (graph.ir.meta.language === "go" && isGoNonEscapeCall(call)) {
38309
+ continue;
38310
+ }
38024
38311
  const bashSafeBuiltins = new Set([
38025
38312
  "echo",
38026
38313
  "printf",
@@ -48324,6 +48611,20 @@ function computeProjectProfileSummary(fileAnalyses) {
48324
48611
  }
48325
48612
  return { byShape, byEnv, totalFiles: fileAnalyses.length };
48326
48613
  }
48614
+ function longestLineLength(code) {
48615
+ let max = 0, start2 = 0;
48616
+ for (let i2 = 0;i2 < code.length; i2++) {
48617
+ if (code.charCodeAt(i2) === 10) {
48618
+ if (i2 - start2 > max)
48619
+ max = i2 - start2;
48620
+ start2 = i2 + 1;
48621
+ }
48622
+ }
48623
+ if (code.length - start2 > max)
48624
+ max = code.length - start2;
48625
+ return max;
48626
+ }
48627
+ var MAX_ANALYZABLE_LINE_LENGTH = 1e4;
48327
48628
  async function analyze(code, filePath, language, options = {}) {
48328
48629
  if (!initialized) {
48329
48630
  await initAnalyzer(options);
@@ -48375,6 +48676,27 @@ async function analyze(code, filePath, language, options = {}) {
48375
48676
  meta.projectProfile = makeProfileResolver2(options.projectProfile)(filePath);
48376
48677
  }
48377
48678
  phaseEnd("extractMeta", tMeta);
48679
+ if (longestLineLength(code) > MAX_ANALYZABLE_LINE_LENGTH) {
48680
+ logger.warn("Skipping analysis of a minified / single-line file", {
48681
+ filePath,
48682
+ language,
48683
+ codeLength: code.length
48684
+ });
48685
+ return {
48686
+ meta,
48687
+ types: [],
48688
+ calls: [],
48689
+ cfg: { blocks: [], edges: [] },
48690
+ dfg: { defs: [], uses: [], chains: [] },
48691
+ taint: { sources: [], sinks: [], sanitizers: [] },
48692
+ imports: [],
48693
+ exports: [],
48694
+ unresolved: [],
48695
+ enriched: {},
48696
+ metrics: { file: filePath, metrics: [] },
48697
+ parse_status: parseStatus
48698
+ };
48699
+ }
48378
48700
  const tTypes = phaseStart();
48379
48701
  const types = extractTypes(tree, nodeCache, language);
48380
48702
  phaseEnd("extractTypes", tTypes);
@@ -49846,7 +50168,7 @@ var colors = {
49846
50168
  };
49847
50169
 
49848
50170
  // src/version.ts
49849
- var version = "4.9.22";
50171
+ var version = "4.9.24";
49850
50172
 
49851
50173
  // src/formatters.ts
49852
50174
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
@@ -50391,8 +50713,8 @@ function generateSarifResults(results, crossFileData) {
50391
50713
 
50392
50714
  // src/build-info.ts
50393
50715
  var buildInfo = {
50394
- gitSha: "54a142d",
50395
- builtAt: "2026-09-22T01:39:02.544Z"
50716
+ gitSha: "527cda0",
50717
+ builtAt: "2026-09-23T02:30:52.145Z"
50396
50718
  };
50397
50719
 
50398
50720
  // src/utils/args.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "4.9.22",
3
+ "version": "4.9.24",
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",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@cognium/project-profile-detect": "1.1.1",
70
- "circle-ir": "4.9.22"
70
+ "circle-ir": "4.9.24"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@types/node": "^26.6.1",