cognium-dev 3.73.0 → 3.75.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 +244 -8
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -11388,6 +11388,12 @@ var DEFAULT_SINKS = [
11388
11388
  { method: "redirect", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
11389
11389
  { method: "Set", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11390
11390
  { method: "Add", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11391
+ { method: "set", class: "headers", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["python"] },
11392
+ { method: "add", class: "headers", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["python"] },
11393
+ { method: "setdefault", class: "headers", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["python"] },
11394
+ { method: "extend", class: "headers", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["python"] },
11395
+ { method: "__setitem__", class: "headers", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["python"] },
11396
+ { method: "set_cookie", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["python"] },
11391
11397
  { method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11392
11398
  { method: "defineProperty", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2], languages: ["javascript", "typescript"] },
11393
11399
  { method: "defineProperties", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1], languages: ["javascript", "typescript"] },
@@ -11437,11 +11443,17 @@ var DEFAULT_SANITIZERS = [
11437
11443
  { method: "toRealPath", class: "Path", removes: ["path_traversal"] },
11438
11444
  { method: "file_name", removes: ["path_traversal"] },
11439
11445
  { 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"] },
11446
+ { method: "Base", class: "filepath", removes: ["path_traversal", "external_taint_escape"] },
11447
+ { method: "Base", class: "path", removes: ["path_traversal", "external_taint_escape"] },
11448
+ { method: "Clean", class: "filepath", removes: ["path_traversal", "external_taint_escape"] },
11449
+ { method: "Clean", class: "path", removes: ["path_traversal", "external_taint_escape"] },
11450
+ { method: "EvalSymlinks", class: "filepath", removes: ["path_traversal", "external_taint_escape"] },
11451
+ { method: "EscapeString", class: "html", removes: ["xss", "external_taint_escape", "log_injection", "open_redirect"] },
11452
+ { method: "HTMLEscapeString", class: "template", removes: ["xss", "external_taint_escape", "log_injection", "open_redirect"] },
11453
+ { method: "JSEscapeString", class: "template", removes: ["xss", "external_taint_escape", "log_injection"] },
11454
+ { method: "URLQueryEscaper", class: "template", removes: ["xss", "external_taint_escape", "open_redirect"] },
11455
+ { method: "QueryEscape", class: "url", removes: ["xss", "external_taint_escape", "open_redirect"] },
11456
+ { method: "PathEscape", class: "url", removes: ["xss", "external_taint_escape", "open_redirect"] },
11445
11457
  { method: "replace", removes: ["log_injection"] },
11446
11458
  { method: "encodeForLDAP", removes: ["ldap_injection"] },
11447
11459
  { method: "encodeForDN", removes: ["ldap_injection"] },
@@ -11992,6 +12004,48 @@ function isSafePythonSubprocessCall(call, pattern, language) {
11992
12004
  }
11993
12005
  return true;
11994
12006
  }
12007
+ function isSafeGoExecCommandCall(call, pattern, language) {
12008
+ if (language !== "go")
12009
+ return false;
12010
+ if (pattern.type !== "command_injection")
12011
+ return false;
12012
+ if (pattern.class !== "exec")
12013
+ return false;
12014
+ if (pattern.method !== "Command" && pattern.method !== "CommandContext")
12015
+ return false;
12016
+ const programArgPos = pattern.method === "CommandContext" ? 1 : 0;
12017
+ const programArg = call.arguments.find((a) => a.position === programArgPos);
12018
+ if (!programArg)
12019
+ return false;
12020
+ let program;
12021
+ if (programArg.literal !== null && programArg.literal !== undefined) {
12022
+ program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
12023
+ } else {
12024
+ const expr = (programArg.expression ?? "").trim();
12025
+ if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
12026
+ return false;
12027
+ }
12028
+ const stripped = expr.slice(1, -1);
12029
+ program = stripped.split("/").pop() ?? stripped;
12030
+ }
12031
+ const SHELL_PROGRAMS = new Set([
12032
+ "sh",
12033
+ "bash",
12034
+ "zsh",
12035
+ "dash",
12036
+ "ash",
12037
+ "ksh",
12038
+ "cmd",
12039
+ "cmd.exe",
12040
+ "powershell",
12041
+ "pwsh",
12042
+ "powershell.exe",
12043
+ "pwsh.exe"
12044
+ ]);
12045
+ if (SHELL_PROGRAMS.has(program))
12046
+ return false;
12047
+ return true;
12048
+ }
11995
12049
  var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
11996
12050
  function argIsClassLiteral(call, position) {
11997
12051
  const arg = call.arguments.find((a) => a.position === position);
@@ -12013,6 +12067,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12013
12067
  if (isSafePythonSubprocessCall(call, pattern, language)) {
12014
12068
  continue;
12015
12069
  }
12070
+ if (isSafeGoExecCommandCall(call, pattern, language)) {
12071
+ continue;
12072
+ }
12016
12073
  if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
12017
12074
  continue;
12018
12075
  }
@@ -12357,6 +12414,10 @@ function receiverMightBeClass(receiver, className) {
12357
12414
  }
12358
12415
  }
12359
12416
  }
12417
+ const chainedCallSuffix = `.${className}()`;
12418
+ if (receiver.endsWith(chainedCallSuffix) || receiver.toLowerCase().endsWith(chainedCallSuffix.toLowerCase())) {
12419
+ return true;
12420
+ }
12360
12421
  if (receiver.includes("::")) {
12361
12422
  const scopePrefix = receiver.match(/^(\w+)::/);
12362
12423
  if (scopePrefix) {
@@ -21241,6 +21302,10 @@ class LanguageSourcesPass {
21241
21302
  additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
21242
21303
  additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
21243
21304
  }
21305
+ if (language === "go") {
21306
+ additionalSanitizers.push(...findGoMapAllowlistGuardSanitizers(code));
21307
+ additionalSanitizers.push(...findGoHtmlTemplateImportSanitizers(code));
21308
+ }
21244
21309
  attachSourceLineCode(additionalSources, additionalSinks, code);
21245
21310
  return { additionalSources, additionalSinks, additionalSanitizers, pyTaintedVars, pySanitizedVars, jsTaintedVars };
21246
21311
  }
@@ -22279,6 +22344,82 @@ function findBashRealpathPrefixGuardSanitizers(code) {
22279
22344
  }
22280
22345
  return sanitizers;
22281
22346
  }
22347
+ function findGoMapAllowlistGuardSanitizers(code) {
22348
+ const sanitizers = [];
22349
+ const lines = code.split(`
22350
+ `);
22351
+ const guardOpen = /^\s*if\s+!\s*([A-Za-z_][A-Za-z0-9_]*)\s*\[\s*[A-Za-z_][A-Za-z0-9_]*\s*\]\s*\{/;
22352
+ const allowlistName = /^(?:[A-Z][A-Z0-9_]+|.*?(allowed|accepted|whitelist|permitted|valid|approved).*)$/i;
22353
+ for (let i2 = 0;i2 < lines.length; i2++) {
22354
+ const m = guardOpen.exec(lines[i2]);
22355
+ if (!m)
22356
+ continue;
22357
+ const mapName = m[1];
22358
+ if (!allowlistName.test(mapName))
22359
+ continue;
22360
+ let depth = 1;
22361
+ let closeLine = -1;
22362
+ let bodyHasTerminator = false;
22363
+ const maxScan = Math.min(lines.length, i2 + 26);
22364
+ for (let j = i2 + 1;j < maxScan; j++) {
22365
+ const line = lines[j];
22366
+ if (/\b(return|panic\s*\(|os\.Exit\s*\()/.test(line)) {
22367
+ bodyHasTerminator = true;
22368
+ }
22369
+ for (const ch of line) {
22370
+ if (ch === "{")
22371
+ depth++;
22372
+ else if (ch === "}")
22373
+ depth--;
22374
+ }
22375
+ if (depth === 0) {
22376
+ closeLine = j;
22377
+ break;
22378
+ }
22379
+ }
22380
+ if (closeLine === -1 || !bodyHasTerminator)
22381
+ continue;
22382
+ for (let l = closeLine + 2;l <= lines.length; l++) {
22383
+ sanitizers.push({
22384
+ type: "go_map_allowlist_guard",
22385
+ method: "if",
22386
+ line: l,
22387
+ sanitizes: [
22388
+ "ssrf",
22389
+ "open_redirect",
22390
+ "path_traversal",
22391
+ "sql_injection",
22392
+ "command_injection",
22393
+ "external_taint_escape"
22394
+ ]
22395
+ });
22396
+ }
22397
+ }
22398
+ return sanitizers;
22399
+ }
22400
+ function findGoHtmlTemplateImportSanitizers(code) {
22401
+ const sanitizers = [];
22402
+ const hasHtmlTemplate = /["\s]html\/template["\s]/.test(code);
22403
+ const hasTextTemplate = /["\s]text\/template["\s]/.test(code);
22404
+ if (!hasHtmlTemplate)
22405
+ return sanitizers;
22406
+ if (hasTextTemplate)
22407
+ return sanitizers;
22408
+ const lines = code.split(`
22409
+ `);
22410
+ const execCall = /\.(Execute|ExecuteTemplate)\s*\(/;
22411
+ for (let i2 = 0;i2 < lines.length; i2++) {
22412
+ if (!execCall.test(lines[i2]))
22413
+ continue;
22414
+ sanitizers.push({
22415
+ type: "html_template_auto_escape",
22416
+ method: "Execute",
22417
+ line: i2 + 1,
22418
+ sanitizes: ["xss", "external_taint_escape", "open_redirect"]
22419
+ });
22420
+ }
22421
+ return sanitizers;
22422
+ }
22282
22423
 
22283
22424
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
22284
22425
  var JS_XSS_SANITIZERS = [
@@ -23103,6 +23244,40 @@ class TaintPropagationPass {
23103
23244
  }
23104
23245
  return true;
23105
23246
  });
23247
+ if (sanitizers && sanitizers.length > 0) {
23248
+ const sanitizersByLine = new Map;
23249
+ for (const san of sanitizers) {
23250
+ const arr = sanitizersByLine.get(san.line) ?? [];
23251
+ arr.push(san);
23252
+ sanitizersByLine.set(san.line, arr);
23253
+ }
23254
+ finalFlows = finalFlows.filter((f) => {
23255
+ if (f.sink_type === "external_taint_escape") {
23256
+ const lo = Math.min(f.source_line, f.sink_line);
23257
+ const hi = Math.max(f.source_line, f.sink_line);
23258
+ for (let line = lo;line <= hi; line++) {
23259
+ const sansAtLine = sanitizersByLine.get(line);
23260
+ if (!sansAtLine)
23261
+ continue;
23262
+ for (const san of sansAtLine) {
23263
+ if (san.sanitizes.includes(f.sink_type)) {
23264
+ return false;
23265
+ }
23266
+ }
23267
+ }
23268
+ return true;
23269
+ }
23270
+ const sansAtSink = sanitizersByLine.get(f.sink_line);
23271
+ if (!sansAtSink || sansAtSink.length === 0)
23272
+ return true;
23273
+ for (const san of sansAtSink) {
23274
+ if (san.sanitizes.includes(f.sink_type)) {
23275
+ return false;
23276
+ }
23277
+ }
23278
+ return true;
23279
+ });
23280
+ }
23106
23281
  if (ctx.language === "java" && typeof ctx.code === "string") {
23107
23282
  finalFlows = finalFlows.filter((f) => {
23108
23283
  if (f.sink_type !== "path_traversal" && f.sink_type !== "xxe")
@@ -23896,7 +24071,19 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
23896
24071
  "BufferedWriter",
23897
24072
  "PrintStream",
23898
24073
  "PrintWriter",
23899
- "ObjectOutputStream"
24074
+ "ObjectOutputStream",
24075
+ "Query",
24076
+ "QueryRow",
24077
+ "QueryContext",
24078
+ "QueryRowContext",
24079
+ "Exec",
24080
+ "ExecContext",
24081
+ "EscapeString",
24082
+ "HTMLEscapeString",
24083
+ "JSEscapeString",
24084
+ "URLQueryEscaper",
24085
+ "Command",
24086
+ "CommandContext"
23900
24087
  ]);
23901
24088
  const sanitizerMethods = new Set;
23902
24089
  for (const san of sanitizers) {
@@ -24314,7 +24501,50 @@ class InterproceduralPass {
24314
24501
  if (additionalSinks.length > 0) {
24315
24502
  attachSourceLineCode([], additionalSinks, ctx.code);
24316
24503
  }
24317
- return { additionalSinks, additionalFlows, interprocedural };
24504
+ let filteredAdditionalFlows = additionalFlows;
24505
+ let filteredAdditionalSinks = additionalSinks;
24506
+ if (sanitizers && sanitizers.length > 0) {
24507
+ const sanitizersByLine = new Map;
24508
+ for (const san of sanitizers) {
24509
+ const arr = sanitizersByLine.get(san.line) ?? [];
24510
+ arr.push(san);
24511
+ sanitizersByLine.set(san.line, arr);
24512
+ }
24513
+ const sanitizedSinkKeys = new Set;
24514
+ filteredAdditionalFlows = additionalFlows.filter((f) => {
24515
+ if (f.sink_type === "external_taint_escape") {
24516
+ const lo = Math.min(f.source_line, f.sink_line);
24517
+ const hi = Math.max(f.source_line, f.sink_line);
24518
+ for (let line = lo;line <= hi; line++) {
24519
+ const sansAtLine = sanitizersByLine.get(line);
24520
+ if (!sansAtLine)
24521
+ continue;
24522
+ for (const san of sansAtLine) {
24523
+ if (san.sanitizes.includes(f.sink_type)) {
24524
+ sanitizedSinkKeys.add(`${f.sink_line}:${f.sink_type}`);
24525
+ return false;
24526
+ }
24527
+ }
24528
+ }
24529
+ return true;
24530
+ }
24531
+ const sansAtSink = sanitizersByLine.get(f.sink_line);
24532
+ if (!sansAtSink || sansAtSink.length === 0)
24533
+ return true;
24534
+ for (const san of sansAtSink) {
24535
+ if (san.sanitizes.includes(f.sink_type)) {
24536
+ return false;
24537
+ }
24538
+ }
24539
+ return true;
24540
+ });
24541
+ filteredAdditionalSinks = additionalSinks.filter((s) => {
24542
+ if (s.type !== "external_taint_escape")
24543
+ return true;
24544
+ return !sanitizedSinkKeys.has(`${s.line}:${s.type}`);
24545
+ });
24546
+ }
24547
+ return { additionalSinks: filteredAdditionalSinks, additionalFlows: filteredAdditionalFlows, interprocedural };
24318
24548
  }
24319
24549
  }
24320
24550
 
@@ -29709,6 +29939,12 @@ class WeakRandomPass {
29709
29939
  return `${rt}.${method}`;
29710
29940
  }
29711
29941
  }
29942
+ if (JAVA_RANDOM_METHODS.has(method)) {
29943
+ if (/^new\s+Random\s*\(/.test(receiver))
29944
+ return `new Random.${method}`;
29945
+ if (/^new\s+SplittableRandom\s*\(/.test(receiver))
29946
+ return `new SplittableRandom.${method}`;
29947
+ }
29712
29948
  if (JAVA_RANDOM_METHODS.has(method) && /ThreadLocalRandom\.current\(\)/.test(receiver)) {
29713
29949
  return `ThreadLocalRandom.current.${method}`;
29714
29950
  }
@@ -32262,7 +32498,7 @@ var colors = {
32262
32498
  };
32263
32499
 
32264
32500
  // src/version.ts
32265
- var version = "3.73.0";
32501
+ var version = "3.75.0";
32266
32502
 
32267
32503
  // src/formatters.ts
32268
32504
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.73.0",
3
+ "version": "3.75.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.73.0"
68
+ "circle-ir": "^3.75.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",