cognium-dev 3.140.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 +442 -26
  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);
@@ -5678,19 +5684,48 @@ function resolveReceiverType(receiver, context) {
5678
5684
  return resolveFqn(simple, context);
5679
5685
  }
5680
5686
  }
5681
- const chained = receiver.match(/^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)\s*\([^()]*\)$/);
5682
- if (chained) {
5683
- const varName = chained[1];
5684
- const methodName = chained[2];
5685
- const varType = context.localVarTypes.get(varName) ?? context.paramTypes.get(varName) ?? context.fieldTypes.get(varName);
5686
- if (varType) {
5687
- const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[stripGenerics(varType)]?.[methodName];
5687
+ const chain = splitChainedReceiver(receiver);
5688
+ if (chain) {
5689
+ const prefixType = resolveReceiverType(chain.prefix, context);
5690
+ if (prefixType.simpleName) {
5691
+ const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[prefixType.simpleName]?.[chain.methodName];
5688
5692
  if (returnType)
5689
5693
  return resolveFqn(returnType, context);
5690
5694
  }
5691
5695
  }
5692
5696
  return { simpleName: null, fqn: null };
5693
5697
  }
5698
+ function splitChainedReceiver(receiver) {
5699
+ if (!receiver.endsWith(")"))
5700
+ return null;
5701
+ let depth = 0;
5702
+ let openIdx = -1;
5703
+ for (let i2 = receiver.length - 1;i2 >= 0; i2--) {
5704
+ const c = receiver[i2];
5705
+ if (c === ")")
5706
+ depth++;
5707
+ else if (c === "(") {
5708
+ depth--;
5709
+ if (depth === 0) {
5710
+ openIdx = i2;
5711
+ break;
5712
+ }
5713
+ }
5714
+ }
5715
+ if (openIdx <= 0)
5716
+ return null;
5717
+ const beforeParen = receiver.substring(0, openIdx);
5718
+ const dotIdx = beforeParen.lastIndexOf(".");
5719
+ if (dotIdx <= 0)
5720
+ return null;
5721
+ const methodName = beforeParen.substring(dotIdx + 1).trim();
5722
+ if (!/^[A-Za-z_$][\w$]*$/.test(methodName))
5723
+ return null;
5724
+ const prefix = beforeParen.substring(0, dotIdx).trim();
5725
+ if (!prefix)
5726
+ return null;
5727
+ return { prefix, methodName };
5728
+ }
5694
5729
  var JAVA_CHAINED_FACTORY_RETURN_TYPES = {
5695
5730
  HttpServletRequest: {
5696
5731
  getSession: "HttpSession",
@@ -5702,6 +5737,31 @@ var JAVA_CHAINED_FACTORY_RETURN_TYPES = {
5702
5737
  },
5703
5738
  ServletContext: {
5704
5739
  getRequestDispatcher: "RequestDispatcher"
5740
+ },
5741
+ DocumentBuilderFactory: {
5742
+ newInstance: "DocumentBuilderFactory",
5743
+ newDocumentBuilder: "DocumentBuilder"
5744
+ },
5745
+ SAXParserFactory: {
5746
+ newInstance: "SAXParserFactory",
5747
+ newSAXParser: "SAXParser"
5748
+ },
5749
+ SAXParser: {
5750
+ getXMLReader: "XMLReader"
5751
+ },
5752
+ XPathFactory: {
5753
+ newInstance: "XPathFactory",
5754
+ newXPath: "XPath"
5755
+ },
5756
+ TransformerFactory: {
5757
+ newInstance: "TransformerFactory",
5758
+ newTransformer: "Transformer"
5759
+ },
5760
+ XMLInputFactory: {
5761
+ newInstance: "XMLInputFactory",
5762
+ newFactory: "XMLInputFactory",
5763
+ createXMLStreamReader: "XMLStreamReader",
5764
+ createXMLEventReader: "XMLEventReader"
5705
5765
  }
5706
5766
  };
5707
5767
  function resolveFqn(simpleName, context) {
@@ -10678,6 +10738,8 @@ var DEFAULT_SINKS = [
10678
10738
  { method: "sendError", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10679
10739
  { method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
10680
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] },
10681
10743
  { method: "setContentType", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10682
10744
  { method: "setAttribute", class: "PageContext", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10683
10745
  { method: "addAttribute", class: "Model", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
@@ -11614,11 +11676,62 @@ var DEFAULT_SANITIZERS = [
11614
11676
  { method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11615
11677
  { method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
11616
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
+ ];
11617
11729
  function getDefaultConfig() {
11618
11730
  return {
11619
11731
  sources: DEFAULT_SOURCES,
11620
11732
  sinks: DEFAULT_SINKS,
11621
- sanitizers: DEFAULT_SANITIZERS
11733
+ sanitizers: DEFAULT_SANITIZERS,
11734
+ sinkSemantics: DEFAULT_SINK_SEMANTICS
11622
11735
  };
11623
11736
  }
11624
11737
  var DEFAULT_HEADER_RULES = [
@@ -11947,7 +12060,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11947
12060
  severity: "medium",
11948
12061
  line: paramLine,
11949
12062
  confidence: param.type ? 0.7 : 0.5,
11950
- in_method: method.name
12063
+ in_method: method.name,
12064
+ ...language === "java" ? { variable: param.name } : {}
11951
12065
  });
11952
12066
  }
11953
12067
  }
@@ -12445,7 +12559,19 @@ function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
12445
12559
  return false;
12446
12560
  }
12447
12561
  var TEMPLATE_LITERAL_RECEIVER_RE = /^Template\(\s*(?:"[^"\\]*"|'[^'\\]*')\s*\)$/;
12448
- 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) {
12449
12575
  if (language !== "python")
12450
12576
  return false;
12451
12577
  if (pattern.type !== "xss" && pattern.type !== "code_injection")
@@ -12461,7 +12587,10 @@ function isSafeJinjaRenderCall(call, pattern, language) {
12461
12587
  }
12462
12588
  if (method === "render") {
12463
12589
  const receiver = (call.receiver ?? "").trim();
12464
- 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;
12465
12594
  }
12466
12595
  return false;
12467
12596
  }
@@ -12509,7 +12638,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12509
12638
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
12510
12639
  continue;
12511
12640
  }
12512
- if (isSafeJinjaRenderCall(call, pattern, language)) {
12641
+ if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
12513
12642
  continue;
12514
12643
  }
12515
12644
  const location = formatCallLocation(call);
@@ -12517,6 +12646,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12517
12646
  const confidence = calculateSinkConfidence(call, pattern);
12518
12647
  const existing = sinkMap.get(key);
12519
12648
  if (!existing || confidence > existing.confidence) {
12649
+ const receiverType = call.receiver_type;
12650
+ const simpleClass = receiverType ? receiverType.split(".").pop() || undefined : undefined;
12520
12651
  sinkMap.set(key, {
12521
12652
  type: pattern.type,
12522
12653
  cwe: pattern.cwe,
@@ -12524,7 +12655,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12524
12655
  line: call.location.line,
12525
12656
  confidence,
12526
12657
  method: call.method_name,
12527
- argPositions: pattern.arg_positions
12658
+ argPositions: pattern.arg_positions,
12659
+ class: simpleClass
12528
12660
  });
12529
12661
  }
12530
12662
  }
@@ -13424,12 +13556,12 @@ function formatCallCode(call) {
13424
13556
  // ../circle-ir/dist/analysis/findings.js
13425
13557
  function canSourceReachSink(sourceType, sinkType) {
13426
13558
  const sourceToSinkMapping = {
13427
- 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"],
13428
13560
  http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
13429
13561
  http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
13430
13562
  http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
13431
13563
  http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary"],
13432
- 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"],
13433
13565
  io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf"],
13434
13566
  env_input: ["command_injection", "path_traversal"],
13435
13567
  db_input: ["xss", "sql_injection"],
@@ -13442,6 +13574,15 @@ function canSourceReachSink(sourceType, sinkType) {
13442
13574
  const validSinks = sourceToSinkMapping[sourceType];
13443
13575
  return validSinks ? validSinks.includes(sinkType) : false;
13444
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
+ }
13445
13586
 
13446
13587
  // ../circle-ir/dist/analysis/findings-instrumentation.js
13447
13588
  var instrumentEnabled = false;
@@ -23548,6 +23689,68 @@ function buildRustTaintedVars(sourceCode, seedVars) {
23548
23689
  }
23549
23690
  return derived;
23550
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
+ }
23551
23754
  var BASH_POSITIONAL_PARAMS = new Set(["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*"]);
23552
23755
  var BASH_UNTRUSTED_ENV_PATTERNS = [
23553
23756
  /^USER_INPUT$/i,
@@ -23928,7 +24131,7 @@ function findBashRealpathPrefixGuardSanitizers(code) {
23928
24131
  const caseOpen = /^\s*case\s+"?\$\{?\w+\}?"?\s+in\b/;
23929
24132
  const esacClose = /^\s*esac\b/;
23930
24133
  const armOpener = /^\s*([^)\s][^)]*?)\)/;
23931
- const prefixArm = /^(?:"\$\{?\w+\}?"|"[^"]*"|\/[\w\-./]+|\$\{?\w+\}?|[\w\-./]+)(?:\/|\*)/;
24134
+ const prefixArm = /^(?:"\$\{?\w+\}?"|"[^"]*"|\/[\w\-./]+|\$\{?\w+\}?|https?:\/\/[\w\-.]+|[\w\-./]+)(?:\/|\*)/;
23932
24135
  const catchAllArm = /^(?:\*|\\\*)$/;
23933
24136
  let i2 = 0;
23934
24137
  while (i2 < lines.length) {
@@ -24861,10 +25064,13 @@ function findJavaArgvFormExecSanitizers(code) {
24861
25064
  `);
24862
25065
  const argvExecRe = /\.\s*exec\s*\(\s*new\s+String\s*\[\s*\]\s*\{/;
24863
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;
24864
25068
  for (let i2 = 0;i2 < lines.length; i2++) {
24865
25069
  const text = lines[i2];
24866
25070
  if (!argvExecRe.test(text) && !argvPbRe.test(text))
24867
25071
  continue;
25072
+ if (shellInStringRe.test(text))
25073
+ continue;
24868
25074
  sanitizers.push({
24869
25075
  type: "java_argv_form_exec",
24870
25076
  method: "exec",
@@ -27015,6 +27221,20 @@ function findPythonMongoengineWhereNosqlInjectionFindings(code, file) {
27015
27221
  }
27016
27222
  return findings;
27017
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
+ }
27018
27238
  function findJavaUrlOpenStreamSsrfFindings(code, file) {
27019
27239
  const findings = [];
27020
27240
  if (typeof code !== "string" || code.length === 0)
@@ -27079,6 +27299,8 @@ function findJavaUrlOpenStreamSsrfFindings(code, file) {
27079
27299
  }
27080
27300
  if (!tainted)
27081
27301
  continue;
27302
+ if (hasHostAllowlistBeforeSink(taintedVars, lines, i2))
27303
+ continue;
27082
27304
  const key = `${i2 + 1}:${op}`;
27083
27305
  if (seen.has(key))
27084
27306
  continue;
@@ -28389,6 +28611,94 @@ function findJsTemplateInjectionSstiFindings(code, file) {
28389
28611
  return findings;
28390
28612
  }
28391
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
+
28392
28702
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
28393
28703
  var JS_XSS_SANITIZERS = [
28394
28704
  /\bDOMPurify\.sanitize\s*\(/,
@@ -28970,7 +29280,7 @@ class SinkFilterPass {
28970
29280
  filtered = filtered.filter((sink) => {
28971
29281
  if (sink.type !== "command_injection")
28972
29282
  return true;
28973
- if (sink.method !== "ProcessBuilder")
29283
+ if (sink.method !== "ProcessBuilder" && sink.method !== "start")
28974
29284
  return true;
28975
29285
  const sinkLineText = sourceLines[sink.line - 1] ?? "";
28976
29286
  if (!/\bnew\s+ProcessBuilder\s*\(/.test(sinkLineText))
@@ -29807,6 +30117,54 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
29807
30117
  });
29808
30118
  }
29809
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
+
29810
30168
  // ../circle-ir/dist/analysis/passes/taint-propagation-pass.js
29811
30169
  class TaintPropagationPass {
29812
30170
  name = "taint-propagation";
@@ -30501,6 +30859,27 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30501
30859
  }
30502
30860
  }
30503
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
+ }
30504
30883
  const reCache = new Map;
30505
30884
  for (const s of sourcesWithVar) {
30506
30885
  if (reCache.has(s.variable))
@@ -30535,6 +30914,8 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30535
30914
  const re = reCache.get(source.variable);
30536
30915
  if (!re || !re.test(expr))
30537
30916
  continue;
30917
+ if (!sourceSemanticsAllowed(source, sink.type))
30918
+ continue;
30538
30919
  if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
30539
30920
  continue;
30540
30921
  if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
@@ -30562,8 +30943,6 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30562
30943
  }
30563
30944
  const sourcesByLine = new Map;
30564
30945
  for (const s of sources) {
30565
- if (s.variable && s.variable.length > 0)
30566
- continue;
30567
30946
  const arr = sourcesByLine.get(s.line) ?? [];
30568
30947
  arr.push(s);
30569
30948
  sourcesByLine.set(s.line, arr);
@@ -30577,6 +30956,20 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
30577
30956
  for (const source of colocSources) {
30578
30957
  if (!canSourceReachSink(source.type, sink.type))
30579
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
+ }
30580
30973
  if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
30581
30974
  continue;
30582
30975
  }
@@ -31165,7 +31558,9 @@ var TIER_1_METHOD_ANNOTATIONS = new Set([
31165
31558
  "DELETE",
31166
31559
  "PATCH",
31167
31560
  "HEAD",
31168
- "OPTIONS"
31561
+ "OPTIONS",
31562
+ "DataBoundConstructor",
31563
+ "DataBoundSetter"
31169
31564
  ]);
31170
31565
  var TIER_1_CLASS_ANNOTATIONS = new Set([
31171
31566
  "RestController",
@@ -31190,7 +31585,13 @@ var TIER_1_BY_SUPERTYPE = new Map([
31190
31585
  ["ChannelInboundHandler", new Set(["channelRead", "channelReadComplete"])],
31191
31586
  ["ChannelInboundHandlerAdapter", new Set(["channelRead", "channelReadComplete"])],
31192
31587
  ["ChannelDuplexHandler", new Set(["channelRead", "channelReadComplete"])],
31193
- ["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"])]
31194
31595
  ]);
31195
31596
  var TIER_3_CLASS_SUFFIXES = [
31196
31597
  "Util",
@@ -35700,6 +36101,14 @@ function isProtocolMandatedCryptoFile(file, code) {
35700
36101
  }
35701
36102
 
35702
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
+ }
35703
36112
  var TEST_PATH_RE3 = /(?:^|[\\/])(?:test|tests|spec|specs|__tests?__|__mocks?__|fixtures?|testdata)(?:[\\/]|$)/i;
35704
36113
  var TEST_FILENAME_RE = /(?:\.(?:test|spec)\.[cm]?[jt]sx?|_test\.go|_test\.py|Test\.java|Tests\.java)$/i;
35705
36114
  function isTestFile(file) {
@@ -36050,6 +36459,7 @@ class ScanSecretsPass {
36050
36459
  if (isTestFile(file) || isGeneratedFile(file)) {
36051
36460
  return { providerFindings: 0, entropyFindings: 0 };
36052
36461
  }
36462
+ const demoPath = DEMO_PATH_RE.test(file);
36053
36463
  const lines = ctx.code.split(`
36054
36464
  `);
36055
36465
  const prior = ctx.getFindings?.() ?? [];
@@ -36080,14 +36490,15 @@ class ScanSecretsPass {
36080
36490
  if (seen.has(key))
36081
36491
  continue;
36082
36492
  seen.add(key);
36493
+ const dg = applyDemoDowngrade(demoPath, pattern.severity, pattern.level);
36083
36494
  ctx.addFinding({
36084
36495
  id: `hardcoded-credential-${file}-${lineNum}`,
36085
36496
  pass: this.name,
36086
36497
  category: this.category,
36087
36498
  rule_id: "hardcoded-credential",
36088
36499
  cwe: "CWE-798",
36089
- severity: pattern.severity,
36090
- level: pattern.level,
36500
+ severity: dg.severity,
36501
+ level: dg.level,
36091
36502
  message: `Hardcoded credential: ${pattern.name} detected`,
36092
36503
  file,
36093
36504
  line: lineNum,
@@ -36109,14 +36520,15 @@ class ScanSecretsPass {
36109
36520
  if (seen.has(key))
36110
36521
  continue;
36111
36522
  seen.add(key);
36523
+ const dg = applyDemoDowngrade(demoPath, "high", "error");
36112
36524
  ctx.addFinding({
36113
36525
  id: `hardcoded-credential-${file}-${lineNum}`,
36114
36526
  pass: this.name,
36115
36527
  category: this.category,
36116
36528
  rule_id: "hardcoded-credential",
36117
36529
  cwe: "CWE-798",
36118
- severity: "high",
36119
- level: "error",
36530
+ severity: dg.severity,
36531
+ level: dg.level,
36120
36532
  message: `Hardcoded credential: \`${hit.name}\` assigned a literal value`,
36121
36533
  file,
36122
36534
  line: lineNum,
@@ -40737,7 +41149,11 @@ async function analyze(code, filePath, language, options = {}) {
40737
41149
  pipeline.add(new TaintMatcherPass);
40738
41150
  pipeline.add(new ConstantPropagationPass(tree));
40739
41151
  pipeline.add(new LanguageSourcesPass);
41152
+ if (!disabledPasses.has("source-semantics"))
41153
+ pipeline.add(new SourceSemanticsPass);
40740
41154
  pipeline.add(new SinkFilterPass);
41155
+ if (!disabledPasses.has("sink-semantics"))
41156
+ pipeline.add(new SinkSemanticsPass);
40741
41157
  pipeline.add(new TaintPropagationPass);
40742
41158
  pipeline.add(new InterproceduralPass({
40743
41159
  enableEntryPointGate: options.enableEntryPointGate ?? true
@@ -41665,7 +42081,7 @@ var colors = {
41665
42081
  };
41666
42082
 
41667
42083
  // src/version.ts
41668
- var version = "3.140.0";
42084
+ var version = "3.145.0";
41669
42085
 
41670
42086
  // src/formatters.ts
41671
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.140.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.140.0"
69
+ "circle-ir": "^3.145.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",