cognium-dev 3.104.0 → 3.105.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 +289 -10
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -18060,6 +18060,18 @@ function isHighConfidence(finding) {
18060
18060
  return finding.confidence === undefined || finding.confidence === "high";
18061
18061
  }
18062
18062
 
18063
+ // ../circle-ir/dist/analysis/library-api-surface-downgrade.js
18064
+ var LIBRARY_API_SURFACE_TAG = "library-api-surface:caller-responsibility";
18065
+ function applyLibraryApiSurfaceDowngrade(findings) {
18066
+ return findings.map((f) => {
18067
+ if (!f.tags?.includes(LIBRARY_API_SURFACE_TAG))
18068
+ return f;
18069
+ if (f.severity === "medium" || f.severity === "low")
18070
+ return f;
18071
+ return { ...f, severity: "medium", level: "warning" };
18072
+ });
18073
+ }
18074
+
18063
18075
  // ../circle-ir/dist/languages/registry.js
18064
18076
  class DefaultLanguageRegistry {
18065
18077
  plugins = new Map;
@@ -23545,6 +23557,39 @@ var PROTOCOL_CLIENT_PACKAGES = [
23545
23557
  var OS_EXEC_RECEIVER_RE = /\b(?:Runtime|ProcessBuilder|DefaultExecutor|Executor|Exec|Launcher|ProcStarter|ProcessExecutor|RuntimeUtil)\s*[.(]/;
23546
23558
  var PROCESS_BUILDER_ARGV_FORM_RE = /\bnew\s+ProcessBuilder\s*\(\s*(?:Arrays\.asList\b|List\.of\b|Collections\.singletonList\b|new\s+ArrayList\b|new\s+String\s*\[\s*\]\s*\{|"[^"]*"\s*,)/;
23547
23559
  var JAVA_THROW_STATEMENT_RE = /^\s*throw\s+new\s+\w+(?:Exception|Error)\b/;
23560
+ var JEXL_ENGINE_TYPES = new Set(["JexlEngine", "Jexl", "JxltEngine"]);
23561
+ var JEXL_EXPRESSION_TYPE_RE = /(?:Jexl)?(?:Expression|Script|Template)(?:Script)?$/;
23562
+ var TEMPLATE_COMPILE_RECEIVER_TYPES = new Set([
23563
+ "Handlebars",
23564
+ "Mustache",
23565
+ "MustacheFactory",
23566
+ "DefaultMustacheFactory",
23567
+ "Pebble",
23568
+ "PebbleEngine",
23569
+ "PebbleEngineBuilder",
23570
+ "VelocityEngine",
23571
+ "Velocity",
23572
+ "Configuration",
23573
+ "Freemarker",
23574
+ "FreeMarker",
23575
+ "TemplateEngine",
23576
+ "SpringTemplateEngine",
23577
+ "Thymeleaf"
23578
+ ]);
23579
+ var SPI_GET_RESOURCES_RE = /\.getResources\s*\(\s*["'][^"']*META-INF\/services\/[^"']*["']\s*\)/;
23580
+ var CLASSLOADER_PARENT_TYPES = new Set([
23581
+ "ClassLoader",
23582
+ "URLClassLoader",
23583
+ "SecureClassLoader"
23584
+ ]);
23585
+ var CLASSLOADER_NAME_RE = /(?:CachingProvider|ClassLoader|SpiLoader)$/;
23586
+ var LOADCLASS_OVERRIDE_METHOD_RE = /\b(?:public|protected)\s+(?:[\w<>?,\s]+\s+)?(?:loadClass|findClass)\s*\(\s*String\b/;
23587
+ var JAVA_CLASS_DECL_RE = /\bclass\s+([A-Z]\w*)\b(?:\s+extends\s+([A-Z][\w.]*))?(?:\s+implements\s+([A-Z][\w.,<>\s?]*))?/;
23588
+ var SQL_BUILDER_CLASS_RE = /(?:Dialect|SqlBuilder|Quoter|Wrapper|SqlGenerator|QueryBuilder)$/;
23589
+ var SQL_BUILDER_WRAPPER_CALL_RE = /\.(?:wrap|quote|escape|identifier)\s*\(/;
23590
+ var SQL_EXTRACTION_RETURN_RE = /^(?:String|CharSequence|Optional\s*<\s*String\s*>)$/;
23591
+ var SQL_EXTRACTION_NAME_RE = /^(?:get|extract|build).*(?:[Ss]ql|[Qq]uery)|^toSql|.*Statement.*ToString$|.*Query.*String$/;
23592
+ var SQL_EXTRACTION_PRIMITIVE_INPUT_RE = /^(?:String|CharSequence)$/;
23548
23593
  function resolveJavaReceiverType(receiver, sinkLine, sourceLines) {
23549
23594
  if (!receiver || !/^[A-Za-z_]\w*$/.test(receiver))
23550
23595
  return null;
@@ -23561,6 +23606,48 @@ function resolveJavaReceiverType(receiver, sinkLine, sourceLines) {
23561
23606
  }
23562
23607
  return null;
23563
23608
  }
23609
+ function findEnclosingClassDecl(sinkLine, sourceLines) {
23610
+ const end = Math.min(sourceLines.length, sinkLine);
23611
+ for (let i2 = end - 1;i2 >= 0; i2--) {
23612
+ const ln = sourceLines[i2] ?? "";
23613
+ const m = ln.match(JAVA_CLASS_DECL_RE);
23614
+ if (!m)
23615
+ continue;
23616
+ const implementsTypes = m[3] ? m[3].split(",").map((s) => s.replace(/<[^>]*>/g, "").trim()).filter(Boolean).map((s) => {
23617
+ const parts2 = s.split(".");
23618
+ return parts2[parts2.length - 1] ?? s;
23619
+ }) : [];
23620
+ const extendsRaw = m[2] ?? null;
23621
+ const extendsType = extendsRaw ? (() => {
23622
+ const p = extendsRaw.split(".");
23623
+ return p[p.length - 1] ?? extendsRaw;
23624
+ })() : null;
23625
+ return { name: m[1], extendsType, implementsTypes };
23626
+ }
23627
+ return null;
23628
+ }
23629
+ function isInsideLoadClassOverride(sinkLine, sourceLines) {
23630
+ const limit = Math.max(0, sinkLine - 200);
23631
+ for (let i2 = sinkLine - 2;i2 >= limit; i2--) {
23632
+ const ln = sourceLines[i2] ?? "";
23633
+ if (LOADCLASS_OVERRIDE_METHOD_RE.test(ln))
23634
+ return true;
23635
+ if (/\bclass\s+[A-Z]\w*\b/.test(ln))
23636
+ return false;
23637
+ }
23638
+ return false;
23639
+ }
23640
+ function findEnclosingMethodFromIr(types, sinkLine) {
23641
+ for (const t of types) {
23642
+ if (sinkLine < t.start_line || sinkLine > t.end_line)
23643
+ continue;
23644
+ for (const m of t.methods) {
23645
+ if (sinkLine >= m.start_line && sinkLine <= m.end_line)
23646
+ return m;
23647
+ }
23648
+ }
23649
+ return null;
23650
+ }
23564
23651
  function isJavaLiteralOrAnnotationAccessor(expr) {
23565
23652
  const e = expr.trim();
23566
23653
  if (e === "")
@@ -23842,6 +23929,168 @@ class SinkFilterPass {
23842
23929
  return true;
23843
23930
  });
23844
23931
  }
23932
+ if (language === "java") {
23933
+ const sourceLines = ctx.code.split(`
23934
+ `);
23935
+ for (const sink of filtered) {
23936
+ if (sink.type !== "code_injection")
23937
+ continue;
23938
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
23939
+ const receiverMatch = sinkLineText.match(/\b(\w+)\s*\.\s*(\w+)\s*\(/);
23940
+ const receiver = receiverMatch?.[1];
23941
+ const method = sink.method ?? receiverMatch?.[2];
23942
+ let shouldTag = false;
23943
+ if (method === "createExpression" && receiver) {
23944
+ const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
23945
+ if (recvType && JEXL_ENGINE_TYPES.has(recvType))
23946
+ shouldTag = true;
23947
+ }
23948
+ if (!shouldTag && method === "evaluate" && receiver) {
23949
+ const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
23950
+ if (recvType && JEXL_EXPRESSION_TYPE_RE.test(recvType))
23951
+ shouldTag = true;
23952
+ }
23953
+ if (!shouldTag && method === "compile" && receiver) {
23954
+ const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
23955
+ if (recvType && TEMPLATE_COMPILE_RECEIVER_TYPES.has(recvType))
23956
+ shouldTag = true;
23957
+ }
23958
+ if (!shouldTag && method === "forName") {
23959
+ const start2 = Math.max(0, sink.line - 31);
23960
+ const end = Math.min(sourceLines.length, sink.line + 30);
23961
+ for (let i2 = start2;i2 < end; i2++) {
23962
+ if (SPI_GET_RESOURCES_RE.test(sourceLines[i2] ?? "")) {
23963
+ shouldTag = true;
23964
+ break;
23965
+ }
23966
+ }
23967
+ }
23968
+ if (!shouldTag && method && (method === "loadClass" || method === "findClass" || method === "forName")) {
23969
+ const enclosing = findEnclosingClassDecl(sink.line, sourceLines);
23970
+ if (enclosing) {
23971
+ const extendsClassLoader = enclosing.extendsType !== null && CLASSLOADER_PARENT_TYPES.has(enclosing.extendsType);
23972
+ const implementsSpi = enclosing.implementsTypes.some((t) => /CachingProvider$/.test(t));
23973
+ const nameLooksLikeLoader = CLASSLOADER_NAME_RE.test(enclosing.name);
23974
+ if (extendsClassLoader || implementsSpi || nameLooksLikeLoader)
23975
+ shouldTag = true;
23976
+ }
23977
+ if (!shouldTag && isInsideLoadClassOverride(sink.line, sourceLines)) {
23978
+ shouldTag = true;
23979
+ }
23980
+ }
23981
+ if (shouldTag) {
23982
+ const existing = sink.tags ?? [];
23983
+ if (!existing.includes(LIBRARY_API_SURFACE_TAG)) {
23984
+ sink.tags = [...existing, LIBRARY_API_SURFACE_TAG];
23985
+ }
23986
+ }
23987
+ }
23988
+ }
23989
+ if (language === "java") {
23990
+ const sourceLines = ctx.code.split(`
23991
+ `);
23992
+ filtered = filtered.filter((sink) => {
23993
+ if (sink.type !== "code_injection")
23994
+ return true;
23995
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
23996
+ const receiverMatch = sinkLineText.match(/\b(\w+)\s*\.\s*(\w+)\s*\(/);
23997
+ const receiver = receiverMatch?.[1];
23998
+ if (!receiver)
23999
+ return true;
24000
+ const start2 = Math.max(0, sink.line - 200);
24001
+ let arrayName = null;
24002
+ const foreachRe = new RegExp(`\\bfor\\s*\\(\\s*\\w[\\w<>?]*\\s+${receiver}\\s*:\\s*(\\w+)\\b`);
24003
+ for (let i2 = sink.line - 1;i2 >= start2; i2--) {
24004
+ const ln = sourceLines[i2] ?? "";
24005
+ const fm = ln.match(foreachRe);
24006
+ if (fm) {
24007
+ arrayName = fm[1];
24008
+ break;
24009
+ }
24010
+ }
24011
+ if (!arrayName) {
24012
+ const methodNameMatch = sinkLineText.match(/\.(\w+)\s*\(/);
24013
+ const methodNameRe = methodNameMatch ? methodNameMatch[1] : "\\w+";
24014
+ const idxRe = new RegExp(`\\b(\\w+)\\s*\\[[^\\]]+\\]\\s*\\.\\s*${methodNameRe}\\s*\\(`);
24015
+ const im = sinkLineText.match(idxRe);
24016
+ if (im)
24017
+ arrayName = im[1];
24018
+ }
24019
+ if (!arrayName)
24020
+ return true;
24021
+ const declRe = new RegExp(`\\bprivate\\s+static\\s+final\\s+(?:[\\w<>?,\\s]+)\\[\\s*\\]\\s+${arrayName}\\s*=`);
24022
+ let declStart = -1;
24023
+ for (let i2 = 0;i2 < sink.line; i2++) {
24024
+ if (declRe.test(sourceLines[i2] ?? "")) {
24025
+ declStart = i2;
24026
+ break;
24027
+ }
24028
+ }
24029
+ if (declStart < 0)
24030
+ return true;
24031
+ let body2 = "";
24032
+ for (let i2 = declStart;i2 < Math.min(sourceLines.length, declStart + 40); i2++) {
24033
+ body2 += (sourceLines[i2] ?? "") + `
24034
+ `;
24035
+ if (/\}\s*;/.test(sourceLines[i2] ?? ""))
24036
+ break;
24037
+ }
24038
+ const initMatch = body2.match(/=\s*(?:new\s+[\w<>?,\s]+\[\s*\]\s*)?\{([\s\S]*?)\}\s*;/);
24039
+ if (!initMatch)
24040
+ return true;
24041
+ const elements = initMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
24042
+ if (elements.length === 0)
24043
+ return true;
24044
+ const allNewLiterals = elements.every((e) => /^new\s+[A-Z]\w*\s*\([^)]*\)$/.test(e));
24045
+ if (allNewLiterals)
24046
+ return false;
24047
+ return true;
24048
+ });
24049
+ }
24050
+ if (language === "java") {
24051
+ const sourceLines = ctx.code.split(`
24052
+ `);
24053
+ filtered = filtered.filter((sink) => {
24054
+ if (sink.type !== "sql_injection")
24055
+ return true;
24056
+ const enclosing = findEnclosingClassDecl(sink.line, sourceLines);
24057
+ if (!enclosing)
24058
+ return true;
24059
+ if (!SQL_BUILDER_CLASS_RE.test(enclosing.name))
24060
+ return true;
24061
+ const lo = Math.max(0, sink.line - 11);
24062
+ const hi = Math.min(sourceLines.length, sink.line + 10);
24063
+ for (let i2 = lo;i2 < hi; i2++) {
24064
+ if (SQL_BUILDER_WRAPPER_CALL_RE.test(sourceLines[i2] ?? ""))
24065
+ return false;
24066
+ }
24067
+ return true;
24068
+ });
24069
+ }
24070
+ if (language === "java") {
24071
+ const types = graph.ir.types ?? [];
24072
+ if (types.length > 0) {
24073
+ filtered = filtered.filter((sink) => {
24074
+ if (sink.type !== "sql_injection")
24075
+ return true;
24076
+ const enclosing = findEnclosingMethodFromIr(types, sink.line);
24077
+ if (!enclosing)
24078
+ return true;
24079
+ const ret = (enclosing.return_type ?? "").replace(/\s+/g, " ").trim();
24080
+ if (!SQL_EXTRACTION_RETURN_RE.test(ret))
24081
+ return true;
24082
+ if (!SQL_EXTRACTION_NAME_RE.test(enclosing.name))
24083
+ return true;
24084
+ const firstParamType = enclosing.parameters[0]?.type ?? null;
24085
+ if (firstParamType === null)
24086
+ return true;
24087
+ const simpleType = firstParamType.replace(/<[^>]*>/g, "").trim();
24088
+ if (SQL_EXTRACTION_PRIMITIVE_INPUT_RE.test(simpleType))
24089
+ return true;
24090
+ return false;
24091
+ });
24092
+ }
24093
+ }
23845
24094
  return { sources, sinks: filtered, sanitizers };
23846
24095
  }
23847
24096
  }
@@ -34862,6 +35111,21 @@ async function analyze(code, filePath, language, options = {}) {
34862
35111
  flows: interProc.additionalFlows,
34863
35112
  interprocedural: interProc.interprocedural
34864
35113
  };
35114
+ if (taint.flows && taint.flows.length > 0 && taint.sinks.length > 0) {
35115
+ const sinkTagsByKey = new Map;
35116
+ for (const s of taint.sinks) {
35117
+ if (!s.tags || s.tags.length === 0)
35118
+ continue;
35119
+ sinkTagsByKey.set(`${s.line}:${s.type}`, s.tags);
35120
+ }
35121
+ if (sinkTagsByKey.size > 0) {
35122
+ for (const f of taint.flows) {
35123
+ const tags = sinkTagsByKey.get(`${f.sink_line}:${f.sink_type}`);
35124
+ if (tags && !f.tags)
35125
+ f.tags = [...tags];
35126
+ }
35127
+ }
35128
+ }
34865
35129
  const unresolved = detectUnresolved(calls, types, dfg);
34866
35130
  const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
34867
35131
  const metricValues = new MetricRunner().run({ meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched }, code, language);
@@ -34874,7 +35138,8 @@ async function analyze(code, filePath, language, options = {}) {
34874
35138
  });
34875
35139
  emitFindingsInstrumentation(filePath, findings, taint);
34876
35140
  const verifiedFindings = applyConfidenceFilter(findings, options.includeSpeculative === true);
34877
- const cappedFindings = applyPerFileFindingCap(filePath, verifiedFindings, options.perFileFindingCap ?? DEFAULT_PER_FILE_FINDING_CAP);
35141
+ const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
35142
+ const cappedFindings = applyPerFileFindingCap(filePath, downgradedFindings, options.perFileFindingCap ?? DEFAULT_PER_FILE_FINDING_CAP);
34878
35143
  return {
34879
35144
  meta,
34880
35145
  types,
@@ -35053,9 +35318,17 @@ var colors = {
35053
35318
  };
35054
35319
 
35055
35320
  // src/version.ts
35056
- var version = "3.104.0";
35321
+ var version = "3.105.0";
35057
35322
 
35058
35323
  // src/formatters.ts
35324
+ var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
35325
+ function applyTagSeverityDowngrade(severity, tags) {
35326
+ if (!tags || !tags.includes(LIBRARY_API_SURFACE_TAG2))
35327
+ return severity;
35328
+ if (severity === "critical" || severity === "high")
35329
+ return "medium";
35330
+ return severity;
35331
+ }
35059
35332
  var SINK_SEVERITY = {
35060
35333
  sql_injection: "critical",
35061
35334
  nosql_injection: "high",
@@ -35397,7 +35670,8 @@ function formatResults(results, verbose, crossFileData) {
35397
35670
  const cweTag = vuln.cwe ? ` [${vuln.cwe}]` : "";
35398
35671
  const severityUpper = vuln.severity.charAt(0).toUpperCase() + vuln.severity.slice(1);
35399
35672
  const categoryTag = vuln.category && vuln.category !== "security" ? ` [${vuln.category}]` : "";
35400
- lines.push(` ${colorFn(`[${icon}]`)} ${colorFn(vuln.type)} (${severityUpper})${cweTag}${categoryTag}`);
35673
+ const libraryApiTag = vuln.tags?.includes(LIBRARY_API_SURFACE_TAG2) ? ` ${colors.cyan("[library-api-surface]")}` : "";
35674
+ lines.push(` ${colorFn(`[${icon}]`)} ${colorFn(vuln.type)} (${severityUpper})${cweTag}${categoryTag}${libraryApiTag}`);
35401
35675
  lines.push(` Line ${vuln.line}: ${vuln.message}`);
35402
35676
  const help = VULNERABILITY_HELP[vuln.type];
35403
35677
  const fixText = vuln.fix ?? help?.fix;
@@ -35522,7 +35796,8 @@ function generateSarifResults(results, crossFileData) {
35522
35796
  properties: {
35523
35797
  cwe: vuln.cwe,
35524
35798
  severity: vuln.severity,
35525
- ...vuln.fix ? { fix: vuln.fix } : {}
35799
+ ...vuln.fix ? { fix: vuln.fix } : {},
35800
+ ...vuln.tags && vuln.tags.length > 0 ? { tags: vuln.tags } : {}
35526
35801
  }
35527
35802
  });
35528
35803
  }
@@ -35949,11 +36224,12 @@ async function scanFile(filePath, language, analyzeOpts) {
35949
36224
  });
35950
36225
  const vulnerabilities = (result.taint.flows || []).map((flow) => ({
35951
36226
  type: flow.sink_type,
35952
- severity: SINK_SEVERITY[flow.sink_type] ?? "high",
36227
+ severity: applyTagSeverityDowngrade(SINK_SEVERITY[flow.sink_type] ?? "high", flow.tags),
35953
36228
  message: `${flow.sink_type} vulnerability: tainted data flows from line ${flow.source_line} to line ${flow.sink_line}`,
35954
36229
  line: flow.sink_line,
35955
36230
  cwe: SINK_CWE[flow.sink_type],
35956
- category: "security"
36231
+ category: "security",
36232
+ ...flow.tags && flow.tags.length > 0 ? { tags: [...flow.tags] } : {}
35957
36233
  }));
35958
36234
  for (const finding of result.findings ?? []) {
35959
36235
  vulnerabilities.push({
@@ -35963,7 +36239,8 @@ async function scanFile(filePath, language, analyzeOpts) {
35963
36239
  line: finding.line,
35964
36240
  cwe: finding.cwe,
35965
36241
  fix: finding.fix,
35966
- category: finding.category ?? "reliability"
36242
+ category: finding.category ?? "reliability",
36243
+ ...finding.tags && finding.tags.length > 0 ? { tags: [...finding.tags] } : {}
35967
36244
  });
35968
36245
  }
35969
36246
  return { file: filePath, vulnerabilities };
@@ -35989,11 +36266,12 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
35989
36266
  const results = projectResult.files.map(({ file, analysis }) => {
35990
36267
  const vulnerabilities = (analysis.taint.flows || []).map((flow) => ({
35991
36268
  type: flow.sink_type,
35992
- severity: SINK_SEVERITY[flow.sink_type] ?? "high",
36269
+ severity: applyTagSeverityDowngrade(SINK_SEVERITY[flow.sink_type] ?? "high", flow.tags),
35993
36270
  message: `${flow.sink_type} vulnerability: tainted data flows from line ${flow.source_line} to line ${flow.sink_line}`,
35994
36271
  line: flow.sink_line,
35995
36272
  cwe: SINK_CWE[flow.sink_type],
35996
- category: "security"
36273
+ category: "security",
36274
+ ...flow.tags && flow.tags.length > 0 ? { tags: [...flow.tags] } : {}
35997
36275
  }));
35998
36276
  for (const finding of analysis.findings ?? []) {
35999
36277
  vulnerabilities.push({
@@ -36003,7 +36281,8 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
36003
36281
  line: finding.line,
36004
36282
  cwe: finding.cwe,
36005
36283
  fix: finding.fix,
36006
- category: finding.category ?? "reliability"
36284
+ category: finding.category ?? "reliability",
36285
+ ...finding.tags && finding.tags.length > 0 ? { tags: [...finding.tags] } : {}
36007
36286
  });
36008
36287
  }
36009
36288
  return { file, vulnerabilities };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.104.0",
3
+ "version": "3.105.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.104.0"
68
+ "circle-ir": "^3.105.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",