cognium-dev 3.141.0 → 3.145.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 +381 -19
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -5668,6 +5668,12 @@ function resolveReceiverType(receiver, context) {
5668
5668
  }
5669
5669
  if (receiver === "super")
5670
5670
  return { simpleName: null, fqn: null };
5671
+ const ctorMatch = receiver.match(/^new\s+([A-Za-z_$][\w$.]*)\s*[<(]/);
5672
+ if (ctorMatch) {
5673
+ const ctorClass = ctorMatch[1];
5674
+ const simple = ctorClass.includes(".") ? ctorClass.substring(ctorClass.lastIndexOf(".") + 1) : ctorClass;
5675
+ return resolveFqn(simple, context);
5676
+ }
5671
5677
  const declaredType = context.localVarTypes.get(receiver) ?? context.paramTypes.get(receiver) ?? context.fieldTypes.get(receiver);
5672
5678
  if (declaredType) {
5673
5679
  return resolveFqn(stripGenerics(declaredType), context);
@@ -10732,6 +10738,8 @@ var DEFAULT_SINKS = [
10732
10738
  { method: "sendError", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10733
10739
  { method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
10734
10740
  { method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
10741
+ { method: "Cookie", class: "constructor", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0, 1] },
10742
+ { method: "addCookie", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0] },
10735
10743
  { method: "setContentType", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10736
10744
  { method: "setAttribute", class: "PageContext", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10737
10745
  { method: "addAttribute", class: "Model", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
@@ -11668,11 +11676,62 @@ var DEFAULT_SANITIZERS = [
11668
11676
  { method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11669
11677
  { method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
11670
11678
  ];
11679
+ var DEFAULT_SINK_SEMANTICS = [
11680
+ {
11681
+ signature: "Jedis#executeCommand",
11682
+ real_class: "db_protocol",
11683
+ overrides: ["command_injection", "code_injection"],
11684
+ note: "Redis wire-protocol serialization, not OS exec"
11685
+ },
11686
+ {
11687
+ signature: "Connection#executeCommand",
11688
+ real_class: "db_protocol",
11689
+ overrides: ["command_injection", "code_injection"],
11690
+ note: "Jedis abstract Connection base"
11691
+ },
11692
+ {
11693
+ signature: "JedisCluster#executeCommand",
11694
+ real_class: "db_protocol",
11695
+ overrides: ["command_injection", "code_injection"],
11696
+ note: "Jedis cluster client"
11697
+ },
11698
+ {
11699
+ signature: "Func1#exec",
11700
+ real_class: "functional_dispatch",
11701
+ overrides: ["command_injection", "code_injection"],
11702
+ note: "RxJava functional dispatch, not OS exec"
11703
+ },
11704
+ {
11705
+ signature: "Action0#call",
11706
+ real_class: "functional_dispatch",
11707
+ overrides: ["command_injection"],
11708
+ note: "RxJava Action0 dispatch"
11709
+ },
11710
+ {
11711
+ signature: "Action1#call",
11712
+ real_class: "functional_dispatch",
11713
+ overrides: ["command_injection"],
11714
+ note: "RxJava Action1 dispatch"
11715
+ },
11716
+ {
11717
+ signature: "Unsafe#defineAnonymousClass",
11718
+ real_class: "jdk_internal",
11719
+ overrides: ["code_injection"],
11720
+ note: "sun.misc.Unsafe JDK-internal reflective bridge"
11721
+ },
11722
+ {
11723
+ signature: "MethodHandle#invokeExact",
11724
+ real_class: "jdk_internal",
11725
+ overrides: ["code_injection"],
11726
+ note: "java.lang.invoke.MethodHandle — JDK-internal"
11727
+ }
11728
+ ];
11671
11729
  function getDefaultConfig() {
11672
11730
  return {
11673
11731
  sources: DEFAULT_SOURCES,
11674
11732
  sinks: DEFAULT_SINKS,
11675
- sanitizers: DEFAULT_SANITIZERS
11733
+ sanitizers: DEFAULT_SANITIZERS,
11734
+ sinkSemantics: DEFAULT_SINK_SEMANTICS
11676
11735
  };
11677
11736
  }
11678
11737
  var DEFAULT_HEADER_RULES = [
@@ -12001,7 +12060,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
12001
12060
  severity: "medium",
12002
12061
  line: paramLine,
12003
12062
  confidence: param.type ? 0.7 : 0.5,
12004
- in_method: method.name
12063
+ in_method: method.name,
12064
+ ...language === "java" ? { variable: param.name } : {}
12005
12065
  });
12006
12066
  }
12007
12067
  }
@@ -12499,7 +12559,19 @@ function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
12499
12559
  return false;
12500
12560
  }
12501
12561
  var TEMPLATE_LITERAL_RECEIVER_RE = /^Template\(\s*(?:"[^"\\]*"|'[^'\\]*')\s*\)$/;
12502
- function isSafeJinjaRenderCall(call, pattern, language) {
12562
+ var JINJA_AUTOESCAPE_TRUE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*True\b/;
12563
+ var JINJA_SELECT_AUTOESCAPE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*select_autoescape\s*\(/;
12564
+ var JINJA_AUTOESCAPE_FALSE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*False\b/;
12565
+ function fileHasSafeJinjaEnvironment(sourceLines) {
12566
+ if (!sourceLines || sourceLines.length === 0)
12567
+ return false;
12568
+ const text = sourceLines.join(`
12569
+ `);
12570
+ if (JINJA_AUTOESCAPE_FALSE_RE.test(text))
12571
+ return false;
12572
+ return JINJA_AUTOESCAPE_TRUE_RE.test(text) || JINJA_SELECT_AUTOESCAPE_RE.test(text);
12573
+ }
12574
+ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
12503
12575
  if (language !== "python")
12504
12576
  return false;
12505
12577
  if (pattern.type !== "xss" && pattern.type !== "code_injection")
@@ -12515,7 +12587,10 @@ function isSafeJinjaRenderCall(call, pattern, language) {
12515
12587
  }
12516
12588
  if (method === "render") {
12517
12589
  const receiver = (call.receiver ?? "").trim();
12518
- return TEMPLATE_LITERAL_RECEIVER_RE.test(receiver);
12590
+ if (TEMPLATE_LITERAL_RECEIVER_RE.test(receiver))
12591
+ return true;
12592
+ if (fileHasSafeJinjaEnvironment(sourceLines))
12593
+ return true;
12519
12594
  }
12520
12595
  return false;
12521
12596
  }
@@ -12563,7 +12638,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12563
12638
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
12564
12639
  continue;
12565
12640
  }
12566
- if (isSafeJinjaRenderCall(call, pattern, language)) {
12641
+ if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
12567
12642
  continue;
12568
12643
  }
12569
12644
  const location = formatCallLocation(call);
@@ -12571,6 +12646,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12571
12646
  const confidence = calculateSinkConfidence(call, pattern);
12572
12647
  const existing = sinkMap.get(key);
12573
12648
  if (!existing || confidence > existing.confidence) {
12649
+ const receiverType = call.receiver_type;
12650
+ const simpleClass = receiverType ? receiverType.split(".").pop() || undefined : undefined;
12574
12651
  sinkMap.set(key, {
12575
12652
  type: pattern.type,
12576
12653
  cwe: pattern.cwe,
@@ -12578,7 +12655,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12578
12655
  line: call.location.line,
12579
12656
  confidence,
12580
12657
  method: call.method_name,
12581
- argPositions: pattern.arg_positions
12658
+ argPositions: pattern.arg_positions,
12659
+ class: simpleClass
12582
12660
  });
12583
12661
  }
12584
12662
  }
@@ -13478,12 +13556,12 @@ function formatCallCode(call) {
13478
13556
  // ../circle-ir/dist/analysis/findings.js
13479
13557
  function canSourceReachSink(sourceType, sinkType) {
13480
13558
  const sourceToSinkMapping = {
13481
- http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
13559
+ http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization"],
13482
13560
  http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
13483
13561
  http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
13484
13562
  http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
13485
13563
  http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary"],
13486
- http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
13564
+ http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization"],
13487
13565
  io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf"],
13488
13566
  env_input: ["command_injection", "path_traversal"],
13489
13567
  db_input: ["xss", "sql_injection"],
@@ -13496,6 +13574,15 @@ function canSourceReachSink(sourceType, sinkType) {
13496
13574
  const validSinks = sourceToSinkMapping[sourceType];
13497
13575
  return validSinks ? validSinks.includes(sinkType) : false;
13498
13576
  }
13577
+ function sourceSemanticsAllowed(source, sinkType) {
13578
+ if (source.constant === true) {
13579
+ return false;
13580
+ }
13581
+ if (source.spi === true) {
13582
+ return sinkType === "code_injection";
13583
+ }
13584
+ return true;
13585
+ }
13499
13586
 
13500
13587
  // ../circle-ir/dist/analysis/findings-instrumentation.js
13501
13588
  var instrumentEnabled = false;
@@ -23602,6 +23689,68 @@ function buildRustTaintedVars(sourceCode, seedVars) {
23602
23689
  }
23603
23690
  return derived;
23604
23691
  }
23692
+ function buildJavaTaintedVars(sourceCode, seedVars) {
23693
+ const derived = new Map;
23694
+ const knownTainted = new Set(seedVars);
23695
+ const lines = sourceCode.split(`
23696
+ `);
23697
+ const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
23698
+ const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
23699
+ const JAVA_KEYWORDS = new Set([
23700
+ "if",
23701
+ "else",
23702
+ "while",
23703
+ "for",
23704
+ "do",
23705
+ "switch",
23706
+ "case",
23707
+ "return",
23708
+ "throw",
23709
+ "try",
23710
+ "catch",
23711
+ "finally",
23712
+ "new",
23713
+ "this",
23714
+ "super",
23715
+ "break",
23716
+ "continue",
23717
+ "default",
23718
+ "class",
23719
+ "interface",
23720
+ "enum"
23721
+ ]);
23722
+ let changed = true;
23723
+ let guard = 0;
23724
+ while (changed && guard < lines.length + 2) {
23725
+ changed = false;
23726
+ guard++;
23727
+ for (let i2 = 0;i2 < lines.length; i2++) {
23728
+ const line = lines[i2];
23729
+ const trimmed = line.trimStart();
23730
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
23731
+ continue;
23732
+ const declMatch = declRe.exec(line);
23733
+ const assignMatch = !declMatch ? assignRe.exec(line) : null;
23734
+ const m = declMatch ?? assignMatch;
23735
+ if (!m)
23736
+ continue;
23737
+ const lhs = m[1];
23738
+ const rhs = m[2];
23739
+ if (JAVA_KEYWORDS.has(lhs))
23740
+ continue;
23741
+ if (knownTainted.has(lhs))
23742
+ continue;
23743
+ const escaped = (v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23744
+ const ref = [...knownTainted].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${escaped(v)}(?![\\p{L}\\p{N}_])`, "u").test(rhs));
23745
+ if (ref) {
23746
+ derived.set(lhs, i2 + 1);
23747
+ knownTainted.add(lhs);
23748
+ changed = true;
23749
+ }
23750
+ }
23751
+ }
23752
+ return derived;
23753
+ }
23605
23754
  var BASH_POSITIONAL_PARAMS = new Set(["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*"]);
23606
23755
  var BASH_UNTRUSTED_ENV_PATTERNS = [
23607
23756
  /^USER_INPUT$/i,
@@ -23982,7 +24131,7 @@ function findBashRealpathPrefixGuardSanitizers(code) {
23982
24131
  const caseOpen = /^\s*case\s+"?\$\{?\w+\}?"?\s+in\b/;
23983
24132
  const esacClose = /^\s*esac\b/;
23984
24133
  const armOpener = /^\s*([^)\s][^)]*?)\)/;
23985
- const prefixArm = /^(?:"\$\{?\w+\}?"|"[^"]*"|\/[\w\-./]+|\$\{?\w+\}?|[\w\-./]+)(?:\/|\*)/;
24134
+ const prefixArm = /^(?:"\$\{?\w+\}?"|"[^"]*"|\/[\w\-./]+|\$\{?\w+\}?|https?:\/\/[\w\-.]+|[\w\-./]+)(?:\/|\*)/;
23986
24135
  const catchAllArm = /^(?:\*|\\\*)$/;
23987
24136
  let i2 = 0;
23988
24137
  while (i2 < lines.length) {
@@ -24915,10 +25064,13 @@ function findJavaArgvFormExecSanitizers(code) {
24915
25064
  `);
24916
25065
  const argvExecRe = /\.\s*exec\s*\(\s*new\s+String\s*\[\s*\]\s*\{/;
24917
25066
  const argvPbRe = /\bnew\s+ProcessBuilder\s*\(\s*new\s+String\s*\[\s*\]\s*\{/;
25067
+ const shellInStringRe = /new\s+String\s*\[\s*\]\s*\{\s*"(?:\/(?:usr\/)?bin\/(?:sh|bash|zsh|ksh|dash)|(?:sh|bash|zsh|ksh|dash)|cmd(?:\.exe)?|powershell(?:\.exe)?|pwsh)"\s*,\s*"(?:-c|\/c|-Command|-command)"/i;
24918
25068
  for (let i2 = 0;i2 < lines.length; i2++) {
24919
25069
  const text = lines[i2];
24920
25070
  if (!argvExecRe.test(text) && !argvPbRe.test(text))
24921
25071
  continue;
25072
+ if (shellInStringRe.test(text))
25073
+ continue;
24922
25074
  sanitizers.push({
24923
25075
  type: "java_argv_form_exec",
24924
25076
  method: "exec",
@@ -27069,6 +27221,20 @@ function findPythonMongoengineWhereNosqlInjectionFindings(code, file) {
27069
27221
  }
27070
27222
  return findings;
27071
27223
  }
27224
+ function hasHostAllowlistBeforeSink(taintedVars, lines, sinkLineIdx) {
27225
+ for (let i2 = 0;i2 < sinkLineIdx; i2++) {
27226
+ const t = lines[i2];
27227
+ for (const v of taintedVars) {
27228
+ const containsRe = new RegExp(`\\.\\s*contains\\s*\\(\\s*${v}\\s*\\.\\s*getHost\\s*\\(\\s*\\)\\s*\\)`);
27229
+ if (containsRe.test(t))
27230
+ return true;
27231
+ const equalsRe = new RegExp(`\\b${v}\\s*\\.\\s*getHost\\s*\\(\\s*\\)\\s*\\.\\s*equals(?:IgnoreCase)?\\s*\\(\\s*"[^"]+"\\s*\\)`);
27232
+ if (equalsRe.test(t))
27233
+ return true;
27234
+ }
27235
+ }
27236
+ return false;
27237
+ }
27072
27238
  function findJavaUrlOpenStreamSsrfFindings(code, file) {
27073
27239
  const findings = [];
27074
27240
  if (typeof code !== "string" || code.length === 0)
@@ -27133,6 +27299,8 @@ function findJavaUrlOpenStreamSsrfFindings(code, file) {
27133
27299
  }
27134
27300
  if (!tainted)
27135
27301
  continue;
27302
+ if (hasHostAllowlistBeforeSink(taintedVars, lines, i2))
27303
+ continue;
27136
27304
  const key = `${i2 + 1}:${op}`;
27137
27305
  if (seen.has(key))
27138
27306
  continue;
@@ -28443,6 +28611,94 @@ function findJsTemplateInjectionSstiFindings(code, file) {
28443
28611
  return findings;
28444
28612
  }
28445
28613
 
28614
+ // ../circle-ir/dist/analysis/passes/source-semantics-pass.js
28615
+ var DEMO_PATH_RE = /(?:^|\/)(?:demo|example|examples|samples|integration-tests|integration_tests)(?:\/|$)/i;
28616
+ var CONST_STRING_ASSIGN_RE = /^\s*(?:final\s+|static\s+final\s+)?[A-Za-z_][\w.<>\[\]]*\s+[A-Za-z_]\w*\s*=\s*"[^"]*"\s*;?\s*$/;
28617
+ var STATIC_FINAL_RE = /^\s*(?:public\s+|private\s+|protected\s+)?static\s+final\s+/;
28618
+ var ENUM_CONST_REF_RE = /=\s*[A-Z][A-Za-z0-9_]*\.[A-Z][A-Z0-9_]*\s*;?\s*$/;
28619
+ function isConstantSource(code) {
28620
+ if (!code)
28621
+ return false;
28622
+ if (CONST_STRING_ASSIGN_RE.test(code))
28623
+ return true;
28624
+ if (STATIC_FINAL_RE.test(code)) {
28625
+ const rhs = code.split("=").slice(1).join("=").trim();
28626
+ if (rhs.length === 0)
28627
+ return false;
28628
+ if (/^"[^"]*"\s*;?\s*$/.test(rhs))
28629
+ return true;
28630
+ if (/^-?\d+(?:\.\d+)?[fFdDlL]?\s*;?\s*$/.test(rhs))
28631
+ return true;
28632
+ if (/^(?:true|false)\s*;?\s*$/.test(rhs))
28633
+ return true;
28634
+ if (/^[A-Za-z_][\w.]*\s*;?\s*$/.test(rhs))
28635
+ return true;
28636
+ return false;
28637
+ }
28638
+ if (ENUM_CONST_REF_RE.test(code))
28639
+ return true;
28640
+ return false;
28641
+ }
28642
+ var SERVICE_LOADER_RE = /\bServiceLoader\.(?:load|loadInstalled|stream)\s*\(/;
28643
+ var CLASS_FOR_NAME_RE = /\bClass\.forName\s*\(/;
28644
+ var META_INF_SERVICES_RE = /getResources?\s*\(\s*"META-INF\/services\//;
28645
+ var SPI_WINDOW = 30;
28646
+ function isSpiSource(source, lines) {
28647
+ const code = source.code;
28648
+ if (!code)
28649
+ return false;
28650
+ if (SERVICE_LOADER_RE.test(code))
28651
+ return true;
28652
+ if (CLASS_FOR_NAME_RE.test(code)) {
28653
+ const start2 = Math.max(0, source.line - 1 - SPI_WINDOW);
28654
+ const end = Math.min(lines.length, source.line - 1 + SPI_WINDOW + 1);
28655
+ for (let i2 = start2;i2 < end; i2++) {
28656
+ if (META_INF_SERVICES_RE.test(lines[i2]))
28657
+ return true;
28658
+ }
28659
+ }
28660
+ return false;
28661
+ }
28662
+ function isDemoPathFile(file) {
28663
+ if (!file)
28664
+ return false;
28665
+ return DEMO_PATH_RE.test(file);
28666
+ }
28667
+
28668
+ class SourceSemanticsPass {
28669
+ name = "source-semantics";
28670
+ category = "security";
28671
+ run(ctx) {
28672
+ const { graph, code } = ctx;
28673
+ const sources = graph.ir.taint.sources;
28674
+ if (sources.length === 0) {
28675
+ return { constantCount: 0, spiCount: 0, demoPathCount: 0 };
28676
+ }
28677
+ const file = graph.ir.meta.file;
28678
+ const demoPath = isDemoPathFile(file);
28679
+ const lines = code.split(`
28680
+ `);
28681
+ let constantCount = 0;
28682
+ let spiCount = 0;
28683
+ let demoPathCount = 0;
28684
+ for (const source of sources) {
28685
+ if (isConstantSource(source.code)) {
28686
+ source.constant = true;
28687
+ constantCount++;
28688
+ }
28689
+ if (isSpiSource(source, lines)) {
28690
+ source.spi = true;
28691
+ spiCount++;
28692
+ }
28693
+ if (demoPath) {
28694
+ source.demoPath = true;
28695
+ demoPathCount++;
28696
+ }
28697
+ }
28698
+ return { constantCount, spiCount, demoPathCount };
28699
+ }
28700
+ }
28701
+
28446
28702
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
28447
28703
  var JS_XSS_SANITIZERS = [
28448
28704
  /\bDOMPurify\.sanitize\s*\(/,
@@ -29024,7 +29280,7 @@ class SinkFilterPass {
29024
29280
  filtered = filtered.filter((sink) => {
29025
29281
  if (sink.type !== "command_injection")
29026
29282
  return true;
29027
- if (sink.method !== "ProcessBuilder")
29283
+ if (sink.method !== "ProcessBuilder" && sink.method !== "start")
29028
29284
  return true;
29029
29285
  const sinkLineText = sourceLines[sink.line - 1] ?? "";
29030
29286
  if (!/\bnew\s+ProcessBuilder\s*\(/.test(sinkLineText))
@@ -29861,6 +30117,54 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
29861
30117
  });
29862
30118
  }
29863
30119
 
30120
+ // ../circle-ir/dist/analysis/passes/sink-semantics-pass.js
30121
+ function buildRegistry(entries) {
30122
+ const registry = new Map;
30123
+ for (const entry of entries) {
30124
+ const existing = registry.get(entry.signature);
30125
+ if (existing) {
30126
+ for (const t of entry.overrides)
30127
+ existing.add(t);
30128
+ } else {
30129
+ registry.set(entry.signature, new Set(entry.overrides));
30130
+ }
30131
+ }
30132
+ return registry;
30133
+ }
30134
+
30135
+ class SinkSemanticsPass {
30136
+ name = "sink-semantics";
30137
+ category = "security";
30138
+ run(ctx) {
30139
+ const { graph, config } = ctx;
30140
+ const entries = config.sinkSemantics ?? [];
30141
+ if (entries.length === 0) {
30142
+ return { droppedCount: 0, registrySize: 0 };
30143
+ }
30144
+ const registry = buildRegistry(entries);
30145
+ const sinks = graph.ir.taint.sinks;
30146
+ let droppedCount = 0;
30147
+ const kept = sinks.filter((sink) => {
30148
+ if (!sink.class || !sink.method)
30149
+ return true;
30150
+ const signature = `${sink.class}#${sink.method}`;
30151
+ const overrides = registry.get(signature);
30152
+ if (!overrides)
30153
+ return true;
30154
+ if (overrides.has(sink.type)) {
30155
+ droppedCount++;
30156
+ return false;
30157
+ }
30158
+ return true;
30159
+ });
30160
+ if (droppedCount > 0) {
30161
+ sinks.length = 0;
30162
+ sinks.push(...kept);
30163
+ }
30164
+ return { droppedCount, registrySize: registry.size };
30165
+ }
30166
+ }
30167
+
29864
30168
  // ../circle-ir/dist/analysis/passes/taint-propagation-pass.js
29865
30169
  class TaintPropagationPass {
29866
30170
  name = "taint-propagation";
@@ -30555,6 +30859,27 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30555
30859
  }
30556
30860
  }
30557
30861
  }
30862
+ if (language === "java" && typeof code === "string" && sourcesWithVar.length > 0) {
30863
+ const seedVars = new Set(sourcesWithVar.map((s) => s.variable));
30864
+ const derived = buildJavaTaintedVars(code, seedVars);
30865
+ if (derived.size > 0) {
30866
+ let anchor = sourcesWithVar[0];
30867
+ for (const s of sourcesWithVar) {
30868
+ if (s.line < anchor.line)
30869
+ anchor = s;
30870
+ }
30871
+ const existingVars = new Set(sourcesWithVar.map((s) => s.variable));
30872
+ for (const [varName] of derived) {
30873
+ if (!varName || existingVars.has(varName))
30874
+ continue;
30875
+ sourcesWithVar.push({
30876
+ ...anchor,
30877
+ variable: varName
30878
+ });
30879
+ existingVars.add(varName);
30880
+ }
30881
+ }
30882
+ }
30558
30883
  const reCache = new Map;
30559
30884
  for (const s of sourcesWithVar) {
30560
30885
  if (reCache.has(s.variable))
@@ -30589,6 +30914,8 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30589
30914
  const re = reCache.get(source.variable);
30590
30915
  if (!re || !re.test(expr))
30591
30916
  continue;
30917
+ if (!sourceSemanticsAllowed(source, sink.type))
30918
+ continue;
30592
30919
  if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
30593
30920
  continue;
30594
30921
  if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
@@ -30616,8 +30943,6 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30616
30943
  }
30617
30944
  const sourcesByLine = new Map;
30618
30945
  for (const s of sources) {
30619
- if (s.variable && s.variable.length > 0)
30620
- continue;
30621
30946
  const arr = sourcesByLine.get(s.line) ?? [];
30622
30947
  arr.push(s);
30623
30948
  sourcesByLine.set(s.line, arr);
@@ -30631,6 +30956,20 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30631
30956
  for (const source of colocSources) {
30632
30957
  if (!canSourceReachSink(source.type, sink.type))
30633
30958
  continue;
30959
+ if (!sourceSemanticsAllowed(source, sink.type))
30960
+ continue;
30961
+ const sourceVar = source.variable;
30962
+ if (sourceVar && sourceVar.length > 0) {
30963
+ const sinkCode = sink.code;
30964
+ if (!sinkCode) {
30965
+ continue;
30966
+ }
30967
+ const assignMatch = sinkCode.match(/^\s*(?:[A-Za-z_][\w.<>[\]\s,?]*\s+)?[A-Za-z_]\w*\s*=(?!=)\s*/);
30968
+ const rhs = assignMatch ? sinkCode.slice(assignMatch[0].length) : sinkCode;
30969
+ if (new RegExp(`\\b${sourceVar}\\b`).test(rhs)) {
30970
+ continue;
30971
+ }
30972
+ }
30634
30973
  if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
30635
30974
  continue;
30636
30975
  }
@@ -31219,7 +31558,9 @@ var TIER_1_METHOD_ANNOTATIONS = new Set([
31219
31558
  "DELETE",
31220
31559
  "PATCH",
31221
31560
  "HEAD",
31222
- "OPTIONS"
31561
+ "OPTIONS",
31562
+ "DataBoundConstructor",
31563
+ "DataBoundSetter"
31223
31564
  ]);
31224
31565
  var TIER_1_CLASS_ANNOTATIONS = new Set([
31225
31566
  "RestController",
@@ -31244,7 +31585,13 @@ var TIER_1_BY_SUPERTYPE = new Map([
31244
31585
  ["ChannelInboundHandler", new Set(["channelRead", "channelReadComplete"])],
31245
31586
  ["ChannelInboundHandlerAdapter", new Set(["channelRead", "channelReadComplete"])],
31246
31587
  ["ChannelDuplexHandler", new Set(["channelRead", "channelReadComplete"])],
31247
- ["NettyRequestProcessor", new Set(["process"])]
31588
+ ["NettyRequestProcessor", new Set(["process"])],
31589
+ ["Converter", new Set(["marshal", "unmarshal"])],
31590
+ ["SingleValueConverter", new Set(["fromString", "toString"])],
31591
+ ["ConverterMatcher", new Set(["marshal", "unmarshal"])],
31592
+ ["AbstractReflectionConverter", new Set(["marshal", "unmarshal", "doMarshal", "doUnmarshal"])],
31593
+ ["AbstractSingleValueConverter", new Set(["fromString", "toString"])],
31594
+ ["AbstractCollectionConverter", new Set(["marshal", "unmarshal"])]
31248
31595
  ]);
31249
31596
  var TIER_3_CLASS_SUFFIXES = [
31250
31597
  "Util",
@@ -35754,6 +36101,14 @@ function isProtocolMandatedCryptoFile(file, code) {
35754
36101
  }
35755
36102
 
35756
36103
  // ../circle-ir/dist/analysis/passes/scan-secrets-pass.js
36104
+ function applyDemoDowngrade(demoPath, severity, level) {
36105
+ if (!demoPath)
36106
+ return { severity, level };
36107
+ if (severity === "high") {
36108
+ return { severity: "low", level: "note" };
36109
+ }
36110
+ return { severity, level };
36111
+ }
35757
36112
  var TEST_PATH_RE3 = /(?:^|[\\/])(?:test|tests|spec|specs|__tests?__|__mocks?__|fixtures?|testdata)(?:[\\/]|$)/i;
35758
36113
  var TEST_FILENAME_RE = /(?:\.(?:test|spec)\.[cm]?[jt]sx?|_test\.go|_test\.py|Test\.java|Tests\.java)$/i;
35759
36114
  function isTestFile(file) {
@@ -36104,6 +36459,7 @@ class ScanSecretsPass {
36104
36459
  if (isTestFile(file) || isGeneratedFile(file)) {
36105
36460
  return { providerFindings: 0, entropyFindings: 0 };
36106
36461
  }
36462
+ const demoPath = DEMO_PATH_RE.test(file);
36107
36463
  const lines = ctx.code.split(`
36108
36464
  `);
36109
36465
  const prior = ctx.getFindings?.() ?? [];
@@ -36134,14 +36490,15 @@ class ScanSecretsPass {
36134
36490
  if (seen.has(key))
36135
36491
  continue;
36136
36492
  seen.add(key);
36493
+ const dg = applyDemoDowngrade(demoPath, pattern.severity, pattern.level);
36137
36494
  ctx.addFinding({
36138
36495
  id: `hardcoded-credential-${file}-${lineNum}`,
36139
36496
  pass: this.name,
36140
36497
  category: this.category,
36141
36498
  rule_id: "hardcoded-credential",
36142
36499
  cwe: "CWE-798",
36143
- severity: pattern.severity,
36144
- level: pattern.level,
36500
+ severity: dg.severity,
36501
+ level: dg.level,
36145
36502
  message: `Hardcoded credential: ${pattern.name} detected`,
36146
36503
  file,
36147
36504
  line: lineNum,
@@ -36163,14 +36520,15 @@ class ScanSecretsPass {
36163
36520
  if (seen.has(key))
36164
36521
  continue;
36165
36522
  seen.add(key);
36523
+ const dg = applyDemoDowngrade(demoPath, "high", "error");
36166
36524
  ctx.addFinding({
36167
36525
  id: `hardcoded-credential-${file}-${lineNum}`,
36168
36526
  pass: this.name,
36169
36527
  category: this.category,
36170
36528
  rule_id: "hardcoded-credential",
36171
36529
  cwe: "CWE-798",
36172
- severity: "high",
36173
- level: "error",
36530
+ severity: dg.severity,
36531
+ level: dg.level,
36174
36532
  message: `Hardcoded credential: \`${hit.name}\` assigned a literal value`,
36175
36533
  file,
36176
36534
  line: lineNum,
@@ -40791,7 +41149,11 @@ async function analyze(code, filePath, language, options = {}) {
40791
41149
  pipeline.add(new TaintMatcherPass);
40792
41150
  pipeline.add(new ConstantPropagationPass(tree));
40793
41151
  pipeline.add(new LanguageSourcesPass);
41152
+ if (!disabledPasses.has("source-semantics"))
41153
+ pipeline.add(new SourceSemanticsPass);
40794
41154
  pipeline.add(new SinkFilterPass);
41155
+ if (!disabledPasses.has("sink-semantics"))
41156
+ pipeline.add(new SinkSemanticsPass);
40795
41157
  pipeline.add(new TaintPropagationPass);
40796
41158
  pipeline.add(new InterproceduralPass({
40797
41159
  enableEntryPointGate: options.enableEntryPointGate ?? true
@@ -41719,7 +42081,7 @@ var colors = {
41719
42081
  };
41720
42082
 
41721
42083
  // src/version.ts
41722
- var version = "3.141.0";
42084
+ var version = "3.145.0";
41723
42085
 
41724
42086
  // src/formatters.ts
41725
42087
  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.141.0",
3
+ "version": "3.145.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.141.0"
69
+ "circle-ir": "^3.145.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",