cognium-dev 3.72.0 → 3.74.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 +416 -10
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -11437,11 +11437,17 @@ var DEFAULT_SANITIZERS = [
11437
11437
  { method: "toRealPath", class: "Path", removes: ["path_traversal"] },
11438
11438
  { method: "file_name", removes: ["path_traversal"] },
11439
11439
  { method: "canonicalize", removes: ["path_traversal"] },
11440
- { method: "Base", class: "filepath", removes: ["path_traversal"] },
11441
- { method: "Base", class: "path", removes: ["path_traversal"] },
11442
- { method: "Clean", class: "filepath", removes: ["path_traversal"] },
11443
- { method: "Clean", class: "path", removes: ["path_traversal"] },
11444
- { method: "EvalSymlinks", class: "filepath", removes: ["path_traversal"] },
11440
+ { method: "Base", class: "filepath", removes: ["path_traversal", "external_taint_escape"] },
11441
+ { method: "Base", class: "path", removes: ["path_traversal", "external_taint_escape"] },
11442
+ { method: "Clean", class: "filepath", removes: ["path_traversal", "external_taint_escape"] },
11443
+ { method: "Clean", class: "path", removes: ["path_traversal", "external_taint_escape"] },
11444
+ { method: "EvalSymlinks", class: "filepath", removes: ["path_traversal", "external_taint_escape"] },
11445
+ { method: "EscapeString", class: "html", removes: ["xss", "external_taint_escape", "log_injection", "open_redirect"] },
11446
+ { method: "HTMLEscapeString", class: "template", removes: ["xss", "external_taint_escape", "log_injection", "open_redirect"] },
11447
+ { method: "JSEscapeString", class: "template", removes: ["xss", "external_taint_escape", "log_injection"] },
11448
+ { method: "URLQueryEscaper", class: "template", removes: ["xss", "external_taint_escape", "open_redirect"] },
11449
+ { method: "QueryEscape", class: "url", removes: ["xss", "external_taint_escape", "open_redirect"] },
11450
+ { method: "PathEscape", class: "url", removes: ["xss", "external_taint_escape", "open_redirect"] },
11445
11451
  { method: "replace", removes: ["log_injection"] },
11446
11452
  { method: "encodeForLDAP", removes: ["ldap_injection"] },
11447
11453
  { method: "encodeForDN", removes: ["ldap_injection"] },
@@ -11865,6 +11871,17 @@ function findSources(calls, types, patterns, sourceLines, language) {
11865
11871
  s.variable = m[1];
11866
11872
  }
11867
11873
  }
11874
+ if (language === "go" && sourceLines) {
11875
+ const GO_ASSIGN_LHS = /^\s*(?:var\s+)?([A-Za-z_]\w*)(?:\s*,\s*[A-Za-z_]\w*)*\s*(?::\s*[A-Za-z_][\w.]*\s*)?(?::?=)(?!=)/;
11876
+ for (const s of result) {
11877
+ if (s.variable && s.variable.length > 0)
11878
+ continue;
11879
+ const lineText = sourceLines[s.line - 1] ?? "";
11880
+ const m = GO_ASSIGN_LHS.exec(lineText);
11881
+ if (m)
11882
+ s.variable = m[1];
11883
+ }
11884
+ }
11868
11885
  return result;
11869
11886
  }
11870
11887
  function isInterproceduralTaintableType(typeName) {
@@ -11981,6 +11998,48 @@ function isSafePythonSubprocessCall(call, pattern, language) {
11981
11998
  }
11982
11999
  return true;
11983
12000
  }
12001
+ function isSafeGoExecCommandCall(call, pattern, language) {
12002
+ if (language !== "go")
12003
+ return false;
12004
+ if (pattern.type !== "command_injection")
12005
+ return false;
12006
+ if (pattern.class !== "exec")
12007
+ return false;
12008
+ if (pattern.method !== "Command" && pattern.method !== "CommandContext")
12009
+ return false;
12010
+ const programArgPos = pattern.method === "CommandContext" ? 1 : 0;
12011
+ const programArg = call.arguments.find((a) => a.position === programArgPos);
12012
+ if (!programArg)
12013
+ return false;
12014
+ let program;
12015
+ if (programArg.literal !== null && programArg.literal !== undefined) {
12016
+ program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
12017
+ } else {
12018
+ const expr = (programArg.expression ?? "").trim();
12019
+ if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
12020
+ return false;
12021
+ }
12022
+ const stripped = expr.slice(1, -1);
12023
+ program = stripped.split("/").pop() ?? stripped;
12024
+ }
12025
+ const SHELL_PROGRAMS = new Set([
12026
+ "sh",
12027
+ "bash",
12028
+ "zsh",
12029
+ "dash",
12030
+ "ash",
12031
+ "ksh",
12032
+ "cmd",
12033
+ "cmd.exe",
12034
+ "powershell",
12035
+ "pwsh",
12036
+ "powershell.exe",
12037
+ "pwsh.exe"
12038
+ ]);
12039
+ if (SHELL_PROGRAMS.has(program))
12040
+ return false;
12041
+ return true;
12042
+ }
11984
12043
  var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
11985
12044
  function argIsClassLiteral(call, position) {
11986
12045
  const arg = call.arguments.find((a) => a.position === position);
@@ -12002,6 +12061,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12002
12061
  if (isSafePythonSubprocessCall(call, pattern, language)) {
12003
12062
  continue;
12004
12063
  }
12064
+ if (isSafeGoExecCommandCall(call, pattern, language)) {
12065
+ continue;
12066
+ }
12005
12067
  if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
12006
12068
  continue;
12007
12069
  }
@@ -17863,7 +17925,7 @@ class GoPlugin extends BaseLanguagePlugin {
17863
17925
  type: "command_injection",
17864
17926
  cwe: "CWE-78",
17865
17927
  severity: "critical",
17866
- argPositions: [0]
17928
+ argPositions: []
17867
17929
  },
17868
17930
  {
17869
17931
  method: "CommandContext",
@@ -17871,7 +17933,7 @@ class GoPlugin extends BaseLanguagePlugin {
17871
17933
  type: "command_injection",
17872
17934
  cwe: "CWE-78",
17873
17935
  severity: "critical",
17874
- argPositions: [1]
17936
+ argPositions: []
17875
17937
  },
17876
17938
  {
17877
17939
  method: "Open",
@@ -17960,6 +18022,110 @@ class GoPlugin extends BaseLanguagePlugin {
17960
18022
  cwe: "CWE-502",
17961
18023
  severity: "medium",
17962
18024
  argPositions: [0]
18025
+ },
18026
+ {
18027
+ method: "Print",
18028
+ class: "log",
18029
+ type: "log_injection",
18030
+ cwe: "CWE-117",
18031
+ severity: "medium",
18032
+ argPositions: []
18033
+ },
18034
+ {
18035
+ method: "Println",
18036
+ class: "log",
18037
+ type: "log_injection",
18038
+ cwe: "CWE-117",
18039
+ severity: "medium",
18040
+ argPositions: []
18041
+ },
18042
+ {
18043
+ method: "Printf",
18044
+ class: "log",
18045
+ type: "log_injection",
18046
+ cwe: "CWE-117",
18047
+ severity: "medium",
18048
+ argPositions: []
18049
+ },
18050
+ {
18051
+ method: "Fatal",
18052
+ class: "log",
18053
+ type: "log_injection",
18054
+ cwe: "CWE-117",
18055
+ severity: "medium",
18056
+ argPositions: []
18057
+ },
18058
+ {
18059
+ method: "Fatalln",
18060
+ class: "log",
18061
+ type: "log_injection",
18062
+ cwe: "CWE-117",
18063
+ severity: "medium",
18064
+ argPositions: []
18065
+ },
18066
+ {
18067
+ method: "Fatalf",
18068
+ class: "log",
18069
+ type: "log_injection",
18070
+ cwe: "CWE-117",
18071
+ severity: "medium",
18072
+ argPositions: []
18073
+ },
18074
+ {
18075
+ method: "Panic",
18076
+ class: "log",
18077
+ type: "log_injection",
18078
+ cwe: "CWE-117",
18079
+ severity: "medium",
18080
+ argPositions: []
18081
+ },
18082
+ {
18083
+ method: "Panicln",
18084
+ class: "log",
18085
+ type: "log_injection",
18086
+ cwe: "CWE-117",
18087
+ severity: "medium",
18088
+ argPositions: []
18089
+ },
18090
+ {
18091
+ method: "Panicf",
18092
+ class: "log",
18093
+ type: "log_injection",
18094
+ cwe: "CWE-117",
18095
+ severity: "medium",
18096
+ argPositions: []
18097
+ },
18098
+ {
18099
+ method: "Parse",
18100
+ class: "Template",
18101
+ type: "code_injection",
18102
+ cwe: "CWE-94",
18103
+ severity: "high",
18104
+ argPositions: [0]
18105
+ },
18106
+ {
18107
+ method: "ParseFiles",
18108
+ class: "template",
18109
+ type: "code_injection",
18110
+ cwe: "CWE-94",
18111
+ severity: "high",
18112
+ argPositions: []
18113
+ },
18114
+ {
18115
+ method: "ParseGlob",
18116
+ class: "template",
18117
+ type: "code_injection",
18118
+ cwe: "CWE-94",
18119
+ severity: "high",
18120
+ argPositions: [0]
18121
+ },
18122
+ {
18123
+ method: "ParseFS",
18124
+ class: "template",
18125
+ type: "code_injection",
18126
+ cwe: "CWE-94",
18127
+ severity: "high",
18128
+ argPositions: []
17963
18129
  }
17964
18130
  ];
17965
18131
  }
@@ -21124,6 +21290,11 @@ class LanguageSourcesPass {
21124
21290
  ctx.addFinding(finding);
21125
21291
  }
21126
21292
  additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
21293
+ additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
21294
+ }
21295
+ if (language === "go") {
21296
+ additionalSanitizers.push(...findGoMapAllowlistGuardSanitizers(code));
21297
+ additionalSanitizers.push(...findGoHtmlTemplateImportSanitizers(code));
21127
21298
  }
21128
21299
  attachSourceLineCode(additionalSources, additionalSinks, code);
21129
21300
  return { additionalSources, additionalSinks, additionalSanitizers, pyTaintedVars, pySanitizedVars, jsTaintedVars };
@@ -22093,6 +22264,152 @@ function isSafeBashAllowlistRegex(literal) {
22093
22264
  }
22094
22265
  return consumed === body2.length;
22095
22266
  }
22267
+ function findBashRealpathPrefixGuardSanitizers(code) {
22268
+ const sanitizers = [];
22269
+ const lines = code.split(`
22270
+ `);
22271
+ const caseOpen = /^\s*case\s+"?\$\{?\w+\}?"?\s+in\b/;
22272
+ const esacClose = /^\s*esac\b/;
22273
+ const armOpener = /^\s*([^)\s][^)]*?)\)/;
22274
+ const prefixArm = /^(?:"\$\{?\w+\}?"|"[^"]*"|\/[\w\-./]+|\$\{?\w+\}?|[\w\-./]+)(?:\/|\*)/;
22275
+ const catchAllArm = /^(?:\*|\\\*)$/;
22276
+ let i2 = 0;
22277
+ while (i2 < lines.length) {
22278
+ if (!caseOpen.test(lines[i2])) {
22279
+ i2++;
22280
+ continue;
22281
+ }
22282
+ let caseEnd = -1;
22283
+ for (let j = i2 + 1;j < lines.length; j++) {
22284
+ if (esacClose.test(lines[j])) {
22285
+ caseEnd = j;
22286
+ break;
22287
+ }
22288
+ }
22289
+ if (caseEnd === -1) {
22290
+ i2++;
22291
+ continue;
22292
+ }
22293
+ let hasPrefixArm = false;
22294
+ let hasTerminalCatchAll = false;
22295
+ for (let j = i2 + 1;j < caseEnd; j++) {
22296
+ const armMatch = armOpener.exec(lines[j]);
22297
+ if (!armMatch)
22298
+ continue;
22299
+ const pattern = armMatch[1].trim();
22300
+ if (catchAllArm.test(pattern)) {
22301
+ let bodyEnd = caseEnd;
22302
+ for (let k = j + 1;k < caseEnd; k++) {
22303
+ if (armOpener.test(lines[k])) {
22304
+ bodyEnd = k;
22305
+ break;
22306
+ }
22307
+ }
22308
+ const armBody = lines.slice(j, bodyEnd).join(" ");
22309
+ if (/\b(exit|return|die)\b/.test(armBody)) {
22310
+ hasTerminalCatchAll = true;
22311
+ }
22312
+ } else if (prefixArm.test(pattern)) {
22313
+ hasPrefixArm = true;
22314
+ }
22315
+ }
22316
+ if (hasPrefixArm && hasTerminalCatchAll) {
22317
+ for (let l = i2 + 1;l <= caseEnd + 1; l++) {
22318
+ sanitizers.push({
22319
+ type: "realpath_prefix_guard",
22320
+ method: "case",
22321
+ line: l,
22322
+ sanitizes: [
22323
+ "path_traversal",
22324
+ "command_injection",
22325
+ "code_injection",
22326
+ "ssrf",
22327
+ "open_redirect",
22328
+ "log_injection"
22329
+ ]
22330
+ });
22331
+ }
22332
+ }
22333
+ i2 = caseEnd + 1;
22334
+ }
22335
+ return sanitizers;
22336
+ }
22337
+ function findGoMapAllowlistGuardSanitizers(code) {
22338
+ const sanitizers = [];
22339
+ const lines = code.split(`
22340
+ `);
22341
+ const guardOpen = /^\s*if\s+!\s*([A-Za-z_][A-Za-z0-9_]*)\s*\[\s*[A-Za-z_][A-Za-z0-9_]*\s*\]\s*\{/;
22342
+ const allowlistName = /^(?:[A-Z][A-Z0-9_]+|.*?(allowed|accepted|whitelist|permitted|valid|approved).*)$/i;
22343
+ for (let i2 = 0;i2 < lines.length; i2++) {
22344
+ const m = guardOpen.exec(lines[i2]);
22345
+ if (!m)
22346
+ continue;
22347
+ const mapName = m[1];
22348
+ if (!allowlistName.test(mapName))
22349
+ continue;
22350
+ let depth = 1;
22351
+ let closeLine = -1;
22352
+ let bodyHasTerminator = false;
22353
+ const maxScan = Math.min(lines.length, i2 + 26);
22354
+ for (let j = i2 + 1;j < maxScan; j++) {
22355
+ const line = lines[j];
22356
+ if (/\b(return|panic\s*\(|os\.Exit\s*\()/.test(line)) {
22357
+ bodyHasTerminator = true;
22358
+ }
22359
+ for (const ch of line) {
22360
+ if (ch === "{")
22361
+ depth++;
22362
+ else if (ch === "}")
22363
+ depth--;
22364
+ }
22365
+ if (depth === 0) {
22366
+ closeLine = j;
22367
+ break;
22368
+ }
22369
+ }
22370
+ if (closeLine === -1 || !bodyHasTerminator)
22371
+ continue;
22372
+ for (let l = closeLine + 2;l <= lines.length; l++) {
22373
+ sanitizers.push({
22374
+ type: "go_map_allowlist_guard",
22375
+ method: "if",
22376
+ line: l,
22377
+ sanitizes: [
22378
+ "ssrf",
22379
+ "open_redirect",
22380
+ "path_traversal",
22381
+ "sql_injection",
22382
+ "command_injection",
22383
+ "external_taint_escape"
22384
+ ]
22385
+ });
22386
+ }
22387
+ }
22388
+ return sanitizers;
22389
+ }
22390
+ function findGoHtmlTemplateImportSanitizers(code) {
22391
+ const sanitizers = [];
22392
+ const hasHtmlTemplate = /["\s]html\/template["\s]/.test(code);
22393
+ const hasTextTemplate = /["\s]text\/template["\s]/.test(code);
22394
+ if (!hasHtmlTemplate)
22395
+ return sanitizers;
22396
+ if (hasTextTemplate)
22397
+ return sanitizers;
22398
+ const lines = code.split(`
22399
+ `);
22400
+ const execCall = /\.(Execute|ExecuteTemplate)\s*\(/;
22401
+ for (let i2 = 0;i2 < lines.length; i2++) {
22402
+ if (!execCall.test(lines[i2]))
22403
+ continue;
22404
+ sanitizers.push({
22405
+ type: "html_template_auto_escape",
22406
+ method: "Execute",
22407
+ line: i2 + 1,
22408
+ sanitizes: ["xss", "external_taint_escape", "open_redirect"]
22409
+ });
22410
+ }
22411
+ return sanitizers;
22412
+ }
22096
22413
 
22097
22414
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
22098
22415
  var JS_XSS_SANITIZERS = [
@@ -22917,6 +23234,40 @@ class TaintPropagationPass {
22917
23234
  }
22918
23235
  return true;
22919
23236
  });
23237
+ if (sanitizers && sanitizers.length > 0) {
23238
+ const sanitizersByLine = new Map;
23239
+ for (const san of sanitizers) {
23240
+ const arr = sanitizersByLine.get(san.line) ?? [];
23241
+ arr.push(san);
23242
+ sanitizersByLine.set(san.line, arr);
23243
+ }
23244
+ finalFlows = finalFlows.filter((f) => {
23245
+ if (f.sink_type === "external_taint_escape") {
23246
+ const lo = Math.min(f.source_line, f.sink_line);
23247
+ const hi = Math.max(f.source_line, f.sink_line);
23248
+ for (let line = lo;line <= hi; line++) {
23249
+ const sansAtLine = sanitizersByLine.get(line);
23250
+ if (!sansAtLine)
23251
+ continue;
23252
+ for (const san of sansAtLine) {
23253
+ if (san.sanitizes.includes(f.sink_type)) {
23254
+ return false;
23255
+ }
23256
+ }
23257
+ }
23258
+ return true;
23259
+ }
23260
+ const sansAtSink = sanitizersByLine.get(f.sink_line);
23261
+ if (!sansAtSink || sansAtSink.length === 0)
23262
+ return true;
23263
+ for (const san of sansAtSink) {
23264
+ if (san.sanitizes.includes(f.sink_type)) {
23265
+ return false;
23266
+ }
23267
+ }
23268
+ return true;
23269
+ });
23270
+ }
22920
23271
  if (ctx.language === "java" && typeof ctx.code === "string") {
22921
23272
  finalFlows = finalFlows.filter((f) => {
22922
23273
  if (f.sink_type !== "path_traversal" && f.sink_type !== "xxe")
@@ -23710,7 +24061,19 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
23710
24061
  "BufferedWriter",
23711
24062
  "PrintStream",
23712
24063
  "PrintWriter",
23713
- "ObjectOutputStream"
24064
+ "ObjectOutputStream",
24065
+ "Query",
24066
+ "QueryRow",
24067
+ "QueryContext",
24068
+ "QueryRowContext",
24069
+ "Exec",
24070
+ "ExecContext",
24071
+ "EscapeString",
24072
+ "HTMLEscapeString",
24073
+ "JSEscapeString",
24074
+ "URLQueryEscaper",
24075
+ "Command",
24076
+ "CommandContext"
23714
24077
  ]);
23715
24078
  const sanitizerMethods = new Set;
23716
24079
  for (const san of sanitizers) {
@@ -24128,7 +24491,50 @@ class InterproceduralPass {
24128
24491
  if (additionalSinks.length > 0) {
24129
24492
  attachSourceLineCode([], additionalSinks, ctx.code);
24130
24493
  }
24131
- return { additionalSinks, additionalFlows, interprocedural };
24494
+ let filteredAdditionalFlows = additionalFlows;
24495
+ let filteredAdditionalSinks = additionalSinks;
24496
+ if (sanitizers && sanitizers.length > 0) {
24497
+ const sanitizersByLine = new Map;
24498
+ for (const san of sanitizers) {
24499
+ const arr = sanitizersByLine.get(san.line) ?? [];
24500
+ arr.push(san);
24501
+ sanitizersByLine.set(san.line, arr);
24502
+ }
24503
+ const sanitizedSinkKeys = new Set;
24504
+ filteredAdditionalFlows = additionalFlows.filter((f) => {
24505
+ if (f.sink_type === "external_taint_escape") {
24506
+ const lo = Math.min(f.source_line, f.sink_line);
24507
+ const hi = Math.max(f.source_line, f.sink_line);
24508
+ for (let line = lo;line <= hi; line++) {
24509
+ const sansAtLine = sanitizersByLine.get(line);
24510
+ if (!sansAtLine)
24511
+ continue;
24512
+ for (const san of sansAtLine) {
24513
+ if (san.sanitizes.includes(f.sink_type)) {
24514
+ sanitizedSinkKeys.add(`${f.sink_line}:${f.sink_type}`);
24515
+ return false;
24516
+ }
24517
+ }
24518
+ }
24519
+ return true;
24520
+ }
24521
+ const sansAtSink = sanitizersByLine.get(f.sink_line);
24522
+ if (!sansAtSink || sansAtSink.length === 0)
24523
+ return true;
24524
+ for (const san of sansAtSink) {
24525
+ if (san.sanitizes.includes(f.sink_type)) {
24526
+ return false;
24527
+ }
24528
+ }
24529
+ return true;
24530
+ });
24531
+ filteredAdditionalSinks = additionalSinks.filter((s) => {
24532
+ if (s.type !== "external_taint_escape")
24533
+ return true;
24534
+ return !sanitizedSinkKeys.has(`${s.line}:${s.type}`);
24535
+ });
24536
+ }
24537
+ return { additionalSinks: filteredAdditionalSinks, additionalFlows: filteredAdditionalFlows, interprocedural };
24132
24538
  }
24133
24539
  }
24134
24540
 
@@ -32076,7 +32482,7 @@ var colors = {
32076
32482
  };
32077
32483
 
32078
32484
  // src/version.ts
32079
- var version = "3.72.0";
32485
+ var version = "3.74.0";
32080
32486
 
32081
32487
  // src/formatters.ts
32082
32488
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.72.0",
3
+ "version": "3.74.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.72.0"
68
+ "circle-ir": "^3.74.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",