cognium-dev 3.65.0 → 3.67.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 +313 -6
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -11038,9 +11038,11 @@ var DEFAULT_SINKS = [
11038
11038
  { method: "rmdir", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
11039
11039
  { method: "createReadStream", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
11040
11040
  { method: "createWriteStream", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
11041
- { method: "query", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11042
- { method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11043
- { method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11041
+ { method: "query", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11042
+ { method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11043
+ { method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11044
+ { method: "execute", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11045
+ { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11044
11046
  { method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11045
11047
  { method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
11046
11048
  { method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
@@ -11053,6 +11055,9 @@ var DEFAULT_SINKS = [
11053
11055
  { method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11054
11056
  { method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11055
11057
  { method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11058
+ { method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11059
+ { method: "parse", class: "protobufjs", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11060
+ { method: "parse", class: "Root", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11056
11061
  { method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
11057
11062
  { method: "findOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
11058
11063
  { method: "updateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
@@ -12249,6 +12254,9 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12249
12254
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
12250
12255
  return true;
12251
12256
  }
12257
+ if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
12258
+ return true;
12259
+ }
12252
12260
  return false;
12253
12261
  } else if (!call.receiver && !call.receiver_type) {
12254
12262
  const target = call.resolution?.target;
@@ -15265,6 +15273,9 @@ class DefaultLanguageRegistry {
15265
15273
  if (language === "tsx") {
15266
15274
  return this.plugins.get("javascript");
15267
15275
  }
15276
+ if (language === "typescript") {
15277
+ return this.plugins.get("javascript");
15278
+ }
15268
15279
  return this.plugins.get(language);
15269
15280
  }
15270
15281
  getForFile(filePath) {
@@ -19080,10 +19091,26 @@ class CrossFileResolver {
19080
19091
  }
19081
19092
  resolveWithReceiver(call, fromFile) {
19082
19093
  const receiver = call.receiver;
19083
- const receiverType = this.inferReceiverType(receiver, fromFile);
19094
+ const receiverType = call.receiver_type_fqn ?? this.inferReceiverType(receiver, fromFile);
19084
19095
  if (receiverType) {
19085
19096
  const methodSymbol = this.symbolTable.findMethod(receiverType, call.method_name);
19086
19097
  if (methodSymbol) {
19098
+ const parent = methodSymbol.parentType ? this.symbolTable.getSymbol(methodSymbol.parentType) : undefined;
19099
+ if (parent && parent.kind === "interface") {
19100
+ const candidates2 = this.findPolymorphicCandidates(receiverType, call.method_name);
19101
+ if (candidates2.length > 0) {
19102
+ const primary = candidates2[0];
19103
+ return {
19104
+ call,
19105
+ sourceFile: fromFile,
19106
+ targetFile: primary.file,
19107
+ targetMethod: primary.fqn,
19108
+ targetClass: primary.parentType || receiverType,
19109
+ resolution: "polymorphic",
19110
+ candidates: candidates2.map((c) => c.fqn)
19111
+ };
19112
+ }
19113
+ }
19087
19114
  return {
19088
19115
  call,
19089
19116
  sourceFile: fromFile,
@@ -20096,7 +20123,8 @@ class CrossFilePass {
20096
20123
  });
20097
20124
  const ipPaths = [
20098
20125
  ...resolver.findInterproceduralTaintPaths(),
20099
- ...resolver.findFieldBindingTaintPaths()
20126
+ ...resolver.findFieldBindingTaintPaths(),
20127
+ ...findCrossInstanceAliasingPaths(projectGraph, sourceLines)
20100
20128
  ];
20101
20129
  for (let i2 = 0;i2 < ipPaths.length; i2++) {
20102
20130
  const p = ipPaths[i2];
@@ -20172,6 +20200,108 @@ class CrossFilePass {
20172
20200
  return { crossFileCalls, taintPaths, typeHierarchy };
20173
20201
  }
20174
20202
  }
20203
+ function findCrossInstanceAliasingPaths(projectGraph, _sourceLines) {
20204
+ const paths = [];
20205
+ const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
20206
+ const aliasWriteRe = /^\s*this\.([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?)(?:;\s*)?$/;
20207
+ const typeIndex = new Map;
20208
+ for (const filePath of projectGraph.filePaths) {
20209
+ const ir = projectGraph.getIR(filePath);
20210
+ if (!ir)
20211
+ continue;
20212
+ if (ir.meta.language !== "java")
20213
+ continue;
20214
+ for (const t of ir.types) {
20215
+ if (t.kind === "class")
20216
+ typeIndex.set(t.name, { file: filePath, type: t, ir });
20217
+ }
20218
+ }
20219
+ if (typeIndex.size === 0)
20220
+ return paths;
20221
+ for (const filePath of projectGraph.filePaths) {
20222
+ const ir = projectGraph.getIR(filePath);
20223
+ if (!ir)
20224
+ continue;
20225
+ if (ir.meta.language !== "java")
20226
+ continue;
20227
+ const lines = _sourceLines.get(filePath);
20228
+ if (!lines || lines.length === 0)
20229
+ continue;
20230
+ for (const type of ir.types) {
20231
+ if (type.kind !== "class")
20232
+ continue;
20233
+ const aliasFields = new Map;
20234
+ for (const f of type.fields) {
20235
+ if (!f.type)
20236
+ continue;
20237
+ const simple = f.type.replace(/<.*>/g, "").replace(/\[\]$/, "").trim();
20238
+ if (typeIndex.has(simple))
20239
+ aliasFields.set(f.name, simple);
20240
+ }
20241
+ if (aliasFields.size === 0)
20242
+ continue;
20243
+ for (const m of type.methods) {
20244
+ if (m.name === type.name)
20245
+ continue;
20246
+ const mStart = m.start_line;
20247
+ const mEnd = m.end_line;
20248
+ for (let i2 = mStart - 1;i2 < Math.min(mEnd, lines.length); i2++) {
20249
+ const trimmed = (lines[i2] ?? "").trim();
20250
+ if (!trimmed || trimmed.startsWith("//"))
20251
+ continue;
20252
+ const wm = trimmed.match(aliasWriteRe);
20253
+ if (!wm)
20254
+ continue;
20255
+ const aliasField = wm[1];
20256
+ const innerField = wm[2];
20257
+ const rhs = wm[3].trim().replace(/;\s*$/, "");
20258
+ const aliasType = aliasFields.get(aliasField);
20259
+ if (!aliasType)
20260
+ continue;
20261
+ const target = typeIndex.get(aliasType);
20262
+ if (!target)
20263
+ continue;
20264
+ if (!target.type.fields.some((f) => f.name === innerField))
20265
+ continue;
20266
+ if (!javaHttpPattern.test(rhs))
20267
+ continue;
20268
+ const innerRe = new RegExp(`\\b${innerField}\\b`);
20269
+ for (const tm of target.type.methods) {
20270
+ const sinksInTarget = target.ir.taint.sinks.filter((s) => s.line >= tm.start_line && s.line <= tm.end_line);
20271
+ for (const sink of sinksInTarget) {
20272
+ const callsAtSink = target.ir.calls.filter((c) => c.location.line === sink.line);
20273
+ let matched = false;
20274
+ for (const c of callsAtSink) {
20275
+ for (const a of c.arguments ?? []) {
20276
+ if (innerRe.test(a.expression ?? "") || a.variable === innerField) {
20277
+ matched = true;
20278
+ break;
20279
+ }
20280
+ }
20281
+ if (matched)
20282
+ break;
20283
+ }
20284
+ if (!matched)
20285
+ continue;
20286
+ paths.push({
20287
+ source: { file: filePath, line: i2 + 1, type: "http_param" },
20288
+ sink: { file: target.file, line: sink.line, type: sink.type, cwe: sink.cwe },
20289
+ hops: [
20290
+ { file: filePath, line: i2 + 1, method: m.name, kind: "source" },
20291
+ { file: filePath, line: i2 + 1, method: m.name, kind: "field_write" },
20292
+ { file: target.file, line: tm.start_line, method: tm.name, kind: "field_read" },
20293
+ { file: target.file, line: sink.line, method: tm.name, kind: "sink" }
20294
+ ],
20295
+ confidence: 0.65
20296
+ });
20297
+ }
20298
+ }
20299
+ }
20300
+ }
20301
+ }
20302
+ }
20303
+ return paths;
20304
+ }
20175
20305
 
20176
20306
  // ../circle-ir/dist/analysis/html/html-extractor.js
20177
20307
  var EVENT_HANDLER_ATTRS = new Set([
@@ -20878,6 +21008,8 @@ class LanguageSourcesPass {
20878
21008
  const additionalSanitizers = [];
20879
21009
  additionalSources.push(...findGetterSources(types, constProp.instanceFieldTaint, code));
20880
21010
  additionalSources.push(...findOopFieldReadSources(types, code, language));
21011
+ additionalSources.push(...findStaticFieldSources(types, code, language));
21012
+ additionalSources.push(...findSetterChainSources(types, code, language));
20881
21013
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
20882
21014
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
20883
21015
  for (const s of jsDOMSinks) {
@@ -21106,6 +21238,155 @@ function findOopFieldReadSources(types, sourceCode, language) {
21106
21238
  }
21107
21239
  return sources;
21108
21240
  }
21241
+ function findStaticFieldSources(types, sourceCode, language) {
21242
+ if (language !== "java")
21243
+ return [];
21244
+ const sources = [];
21245
+ const lines = sourceCode.split(`
21246
+ `);
21247
+ const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
21248
+ for (const type of types) {
21249
+ if (type.kind !== "class")
21250
+ continue;
21251
+ if (type.name === "<module>")
21252
+ continue;
21253
+ const staticFields = new Set;
21254
+ for (const f of type.fields) {
21255
+ if (f.modifiers.includes("static"))
21256
+ staticFields.add(f.name);
21257
+ }
21258
+ if (staticFields.size === 0)
21259
+ continue;
21260
+ const qualifiedAssignRe = new RegExp(`^\\s*${type.name}\\.([A-Za-z_]\\w*)\\s*=\\s*(.+?)(?:;\\s*)?$`);
21261
+ const bareAssignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?)(?:;\s*)?$/;
21262
+ for (const m of type.methods) {
21263
+ if (!m.modifiers.includes("static"))
21264
+ continue;
21265
+ const mStart = m.start_line;
21266
+ const mEnd = m.end_line;
21267
+ for (let i2 = mStart - 1;i2 < Math.min(mEnd, lines.length); i2++) {
21268
+ const line = lines[i2] ?? "";
21269
+ const trimmed = line.trim();
21270
+ if (!trimmed || trimmed.startsWith("//"))
21271
+ continue;
21272
+ let fieldName = null;
21273
+ let rhs = null;
21274
+ const qm = trimmed.match(qualifiedAssignRe);
21275
+ if (qm) {
21276
+ fieldName = qm[1];
21277
+ rhs = qm[2];
21278
+ } else {
21279
+ const bm = trimmed.match(bareAssignRe);
21280
+ if (bm) {
21281
+ fieldName = bm[1];
21282
+ rhs = bm[2];
21283
+ }
21284
+ }
21285
+ if (!fieldName || !rhs)
21286
+ continue;
21287
+ if (!staticFields.has(fieldName))
21288
+ continue;
21289
+ rhs = rhs.trim().replace(/;\s*$/, "");
21290
+ if (!javaHttpPattern.test(rhs))
21291
+ continue;
21292
+ sources.push({
21293
+ type: "http_param",
21294
+ location: `${type.name}.${fieldName} static field set in ${m.name}() — #78 round 2`,
21295
+ severity: "high",
21296
+ line: i2 + 1,
21297
+ confidence: 0.85,
21298
+ variable: fieldName
21299
+ });
21300
+ sources.push({
21301
+ type: "http_param",
21302
+ location: `${type.name}.${fieldName} static field (qualified read alias) — #78 round 2`,
21303
+ severity: "high",
21304
+ line: i2 + 1,
21305
+ confidence: 0.85,
21306
+ variable: `${type.name}.${fieldName}`
21307
+ });
21308
+ }
21309
+ }
21310
+ }
21311
+ return sources;
21312
+ }
21313
+ function findSetterChainSources(types, sourceCode, language) {
21314
+ if (language !== "java")
21315
+ return [];
21316
+ const sources = [];
21317
+ const lines = sourceCode.split(`
21318
+ `);
21319
+ const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
21320
+ for (const type of types) {
21321
+ if (type.kind !== "class")
21322
+ continue;
21323
+ if (type.name === "<module>")
21324
+ continue;
21325
+ const pairs = new Map;
21326
+ const setterRe = /this\.([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)\s*;?/;
21327
+ const getterRe = /return\s+this\.([A-Za-z_]\w*)\s*;?/;
21328
+ for (const m of type.methods) {
21329
+ if (m.name === type.name)
21330
+ continue;
21331
+ const mStart = m.start_line;
21332
+ const mEnd = m.end_line;
21333
+ const fullBody = lines.slice(mStart - 1, Math.min(mEnd, lines.length)).join(`
21334
+ `);
21335
+ const open = fullBody.indexOf("{");
21336
+ const close = fullBody.lastIndexOf("}");
21337
+ if (open < 0 || close < 0 || close <= open)
21338
+ continue;
21339
+ const inner = fullBody.slice(open + 1, close).replace(/\/\/[^\n]*/g, "").trim();
21340
+ if (!inner)
21341
+ continue;
21342
+ const sm = inner.match(setterRe);
21343
+ if (sm && m.parameters.length === 1 && sm[2] === m.parameters[0].name) {
21344
+ const remainder = inner.replace(sm[0], "").trim();
21345
+ if (!remainder) {
21346
+ const entry = pairs.get(sm[1]) ?? {};
21347
+ entry.setter = m.name;
21348
+ pairs.set(sm[1], entry);
21349
+ continue;
21350
+ }
21351
+ }
21352
+ const gm = inner.match(getterRe);
21353
+ if (gm && m.parameters.length === 0) {
21354
+ const remainder = inner.replace(gm[0], "").trim();
21355
+ if (!remainder) {
21356
+ const entry = pairs.get(gm[1]) ?? {};
21357
+ entry.getter = m.name;
21358
+ pairs.set(gm[1], entry);
21359
+ }
21360
+ }
21361
+ }
21362
+ for (const [, { setter, getter }] of pairs) {
21363
+ if (!setter || !getter)
21364
+ continue;
21365
+ const setterCallRe = new RegExp(`\\b([A-Za-z_]\\w*)\\.${setter}\\s*\\(\\s*([^)]+?)\\s*\\)\\s*;?`);
21366
+ for (let i2 = 0;i2 < lines.length; i2++) {
21367
+ const line = lines[i2] ?? "";
21368
+ const trimmed = line.trim();
21369
+ if (!trimmed || trimmed.startsWith("//"))
21370
+ continue;
21371
+ const cm = trimmed.match(setterCallRe);
21372
+ if (!cm)
21373
+ continue;
21374
+ const arg = cm[2];
21375
+ if (!javaHttpPattern.test(arg))
21376
+ continue;
21377
+ sources.push({
21378
+ type: "http_param",
21379
+ location: `${type.name}.${setter}(tainted) → ${type.name}.${getter}() chain — #78 round 2`,
21380
+ severity: "high",
21381
+ line: i2 + 1,
21382
+ confidence: 0.75,
21383
+ variable: getter
21384
+ });
21385
+ }
21386
+ }
21387
+ }
21388
+ return sources;
21389
+ }
21109
21390
  function findJavaScriptAssignmentSources(sourceCode, language) {
21110
21391
  if (!["javascript", "typescript"].includes(language))
21111
21392
  return [];
@@ -21852,6 +22133,32 @@ class SinkFilterPass {
21852
22133
  return true;
21853
22134
  });
21854
22135
  }
22136
+ if (["javascript", "typescript"].includes(language)) {
22137
+ const sourceLines = ctx.code.split(`
22138
+ `);
22139
+ const guardPatterns = /\b(?:includes|startsWith|endsWith|indexOf|test|match)\s*\(/;
22140
+ filtered = filtered.filter((sink) => {
22141
+ if (sink.type !== "open_redirect" && sink.type !== "crlf") {
22142
+ return true;
22143
+ }
22144
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
22145
+ const startLine = Math.max(0, sink.line - 7);
22146
+ for (let i2 = startLine;i2 < sink.line - 1; i2++) {
22147
+ const line = sourceLines[i2] ?? "";
22148
+ if (/\bif\s*\(/.test(line) && guardPatterns.test(line)) {
22149
+ return false;
22150
+ }
22151
+ }
22152
+ if (/\bencodeURIComponent\s*\(|\bencodeURI\s*\(/.test(sinkLineText)) {
22153
+ return false;
22154
+ }
22155
+ const setHeaderMatch = sinkLineText.match(/setHeader\s*\(\s*[^,]+,\s*(['"`])([^'"`]*)\1\s*\)/);
22156
+ if (setHeaderMatch) {
22157
+ return false;
22158
+ }
22159
+ return true;
22160
+ });
22161
+ }
21855
22162
  return { sources, sinks: filtered, sanitizers };
21856
22163
  }
21857
22164
  }
@@ -31047,7 +31354,7 @@ var colors = {
31047
31354
  };
31048
31355
 
31049
31356
  // src/version.ts
31050
- var version = "3.65.0";
31357
+ var version = "3.67.0";
31051
31358
 
31052
31359
  // src/formatters.ts
31053
31360
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.65.0",
3
+ "version": "3.67.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.65.0"
68
+ "circle-ir": "^3.67.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",