cognium-dev 3.65.0 → 3.66.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 +273 -3
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -19080,10 +19080,26 @@ class CrossFileResolver {
19080
19080
  }
19081
19081
  resolveWithReceiver(call, fromFile) {
19082
19082
  const receiver = call.receiver;
19083
- const receiverType = this.inferReceiverType(receiver, fromFile);
19083
+ const receiverType = call.receiver_type_fqn ?? this.inferReceiverType(receiver, fromFile);
19084
19084
  if (receiverType) {
19085
19085
  const methodSymbol = this.symbolTable.findMethod(receiverType, call.method_name);
19086
19086
  if (methodSymbol) {
19087
+ const parent = methodSymbol.parentType ? this.symbolTable.getSymbol(methodSymbol.parentType) : undefined;
19088
+ if (parent && parent.kind === "interface") {
19089
+ const candidates2 = this.findPolymorphicCandidates(receiverType, call.method_name);
19090
+ if (candidates2.length > 0) {
19091
+ const primary = candidates2[0];
19092
+ return {
19093
+ call,
19094
+ sourceFile: fromFile,
19095
+ targetFile: primary.file,
19096
+ targetMethod: primary.fqn,
19097
+ targetClass: primary.parentType || receiverType,
19098
+ resolution: "polymorphic",
19099
+ candidates: candidates2.map((c) => c.fqn)
19100
+ };
19101
+ }
19102
+ }
19087
19103
  return {
19088
19104
  call,
19089
19105
  sourceFile: fromFile,
@@ -20096,7 +20112,8 @@ class CrossFilePass {
20096
20112
  });
20097
20113
  const ipPaths = [
20098
20114
  ...resolver.findInterproceduralTaintPaths(),
20099
- ...resolver.findFieldBindingTaintPaths()
20115
+ ...resolver.findFieldBindingTaintPaths(),
20116
+ ...findCrossInstanceAliasingPaths(projectGraph, sourceLines)
20100
20117
  ];
20101
20118
  for (let i2 = 0;i2 < ipPaths.length; i2++) {
20102
20119
  const p = ipPaths[i2];
@@ -20172,6 +20189,108 @@ class CrossFilePass {
20172
20189
  return { crossFileCalls, taintPaths, typeHierarchy };
20173
20190
  }
20174
20191
  }
20192
+ function findCrossInstanceAliasingPaths(projectGraph, _sourceLines) {
20193
+ const paths = [];
20194
+ const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
20195
+ const aliasWriteRe = /^\s*this\.([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?)(?:;\s*)?$/;
20196
+ const typeIndex = new Map;
20197
+ for (const filePath of projectGraph.filePaths) {
20198
+ const ir = projectGraph.getIR(filePath);
20199
+ if (!ir)
20200
+ continue;
20201
+ if (ir.meta.language !== "java")
20202
+ continue;
20203
+ for (const t of ir.types) {
20204
+ if (t.kind === "class")
20205
+ typeIndex.set(t.name, { file: filePath, type: t, ir });
20206
+ }
20207
+ }
20208
+ if (typeIndex.size === 0)
20209
+ return paths;
20210
+ for (const filePath of projectGraph.filePaths) {
20211
+ const ir = projectGraph.getIR(filePath);
20212
+ if (!ir)
20213
+ continue;
20214
+ if (ir.meta.language !== "java")
20215
+ continue;
20216
+ const lines = _sourceLines.get(filePath);
20217
+ if (!lines || lines.length === 0)
20218
+ continue;
20219
+ for (const type of ir.types) {
20220
+ if (type.kind !== "class")
20221
+ continue;
20222
+ const aliasFields = new Map;
20223
+ for (const f of type.fields) {
20224
+ if (!f.type)
20225
+ continue;
20226
+ const simple = f.type.replace(/<.*>/g, "").replace(/\[\]$/, "").trim();
20227
+ if (typeIndex.has(simple))
20228
+ aliasFields.set(f.name, simple);
20229
+ }
20230
+ if (aliasFields.size === 0)
20231
+ continue;
20232
+ for (const m of type.methods) {
20233
+ if (m.name === type.name)
20234
+ continue;
20235
+ const mStart = m.start_line;
20236
+ const mEnd = m.end_line;
20237
+ for (let i2 = mStart - 1;i2 < Math.min(mEnd, lines.length); i2++) {
20238
+ const trimmed = (lines[i2] ?? "").trim();
20239
+ if (!trimmed || trimmed.startsWith("//"))
20240
+ continue;
20241
+ const wm = trimmed.match(aliasWriteRe);
20242
+ if (!wm)
20243
+ continue;
20244
+ const aliasField = wm[1];
20245
+ const innerField = wm[2];
20246
+ const rhs = wm[3].trim().replace(/;\s*$/, "");
20247
+ const aliasType = aliasFields.get(aliasField);
20248
+ if (!aliasType)
20249
+ continue;
20250
+ const target = typeIndex.get(aliasType);
20251
+ if (!target)
20252
+ continue;
20253
+ if (!target.type.fields.some((f) => f.name === innerField))
20254
+ continue;
20255
+ if (!javaHttpPattern.test(rhs))
20256
+ continue;
20257
+ const innerRe = new RegExp(`\\b${innerField}\\b`);
20258
+ for (const tm of target.type.methods) {
20259
+ const sinksInTarget = target.ir.taint.sinks.filter((s) => s.line >= tm.start_line && s.line <= tm.end_line);
20260
+ for (const sink of sinksInTarget) {
20261
+ const callsAtSink = target.ir.calls.filter((c) => c.location.line === sink.line);
20262
+ let matched = false;
20263
+ for (const c of callsAtSink) {
20264
+ for (const a of c.arguments ?? []) {
20265
+ if (innerRe.test(a.expression ?? "") || a.variable === innerField) {
20266
+ matched = true;
20267
+ break;
20268
+ }
20269
+ }
20270
+ if (matched)
20271
+ break;
20272
+ }
20273
+ if (!matched)
20274
+ continue;
20275
+ paths.push({
20276
+ source: { file: filePath, line: i2 + 1, type: "http_param" },
20277
+ sink: { file: target.file, line: sink.line, type: sink.type, cwe: sink.cwe },
20278
+ hops: [
20279
+ { file: filePath, line: i2 + 1, method: m.name, kind: "source" },
20280
+ { file: filePath, line: i2 + 1, method: m.name, kind: "field_write" },
20281
+ { file: target.file, line: tm.start_line, method: tm.name, kind: "field_read" },
20282
+ { file: target.file, line: sink.line, method: tm.name, kind: "sink" }
20283
+ ],
20284
+ confidence: 0.65
20285
+ });
20286
+ }
20287
+ }
20288
+ }
20289
+ }
20290
+ }
20291
+ }
20292
+ return paths;
20293
+ }
20175
20294
 
20176
20295
  // ../circle-ir/dist/analysis/html/html-extractor.js
20177
20296
  var EVENT_HANDLER_ATTRS = new Set([
@@ -20878,6 +20997,8 @@ class LanguageSourcesPass {
20878
20997
  const additionalSanitizers = [];
20879
20998
  additionalSources.push(...findGetterSources(types, constProp.instanceFieldTaint, code));
20880
20999
  additionalSources.push(...findOopFieldReadSources(types, code, language));
21000
+ additionalSources.push(...findStaticFieldSources(types, code, language));
21001
+ additionalSources.push(...findSetterChainSources(types, code, language));
20881
21002
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
20882
21003
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
20883
21004
  for (const s of jsDOMSinks) {
@@ -21106,6 +21227,155 @@ function findOopFieldReadSources(types, sourceCode, language) {
21106
21227
  }
21107
21228
  return sources;
21108
21229
  }
21230
+ function findStaticFieldSources(types, sourceCode, language) {
21231
+ if (language !== "java")
21232
+ return [];
21233
+ const sources = [];
21234
+ const lines = sourceCode.split(`
21235
+ `);
21236
+ const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
21237
+ for (const type of types) {
21238
+ if (type.kind !== "class")
21239
+ continue;
21240
+ if (type.name === "<module>")
21241
+ continue;
21242
+ const staticFields = new Set;
21243
+ for (const f of type.fields) {
21244
+ if (f.modifiers.includes("static"))
21245
+ staticFields.add(f.name);
21246
+ }
21247
+ if (staticFields.size === 0)
21248
+ continue;
21249
+ const qualifiedAssignRe = new RegExp(`^\\s*${type.name}\\.([A-Za-z_]\\w*)\\s*=\\s*(.+?)(?:;\\s*)?$`);
21250
+ const bareAssignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?)(?:;\s*)?$/;
21251
+ for (const m of type.methods) {
21252
+ if (!m.modifiers.includes("static"))
21253
+ continue;
21254
+ const mStart = m.start_line;
21255
+ const mEnd = m.end_line;
21256
+ for (let i2 = mStart - 1;i2 < Math.min(mEnd, lines.length); i2++) {
21257
+ const line = lines[i2] ?? "";
21258
+ const trimmed = line.trim();
21259
+ if (!trimmed || trimmed.startsWith("//"))
21260
+ continue;
21261
+ let fieldName = null;
21262
+ let rhs = null;
21263
+ const qm = trimmed.match(qualifiedAssignRe);
21264
+ if (qm) {
21265
+ fieldName = qm[1];
21266
+ rhs = qm[2];
21267
+ } else {
21268
+ const bm = trimmed.match(bareAssignRe);
21269
+ if (bm) {
21270
+ fieldName = bm[1];
21271
+ rhs = bm[2];
21272
+ }
21273
+ }
21274
+ if (!fieldName || !rhs)
21275
+ continue;
21276
+ if (!staticFields.has(fieldName))
21277
+ continue;
21278
+ rhs = rhs.trim().replace(/;\s*$/, "");
21279
+ if (!javaHttpPattern.test(rhs))
21280
+ continue;
21281
+ sources.push({
21282
+ type: "http_param",
21283
+ location: `${type.name}.${fieldName} static field set in ${m.name}() — #78 round 2`,
21284
+ severity: "high",
21285
+ line: i2 + 1,
21286
+ confidence: 0.85,
21287
+ variable: fieldName
21288
+ });
21289
+ sources.push({
21290
+ type: "http_param",
21291
+ location: `${type.name}.${fieldName} static field (qualified read alias) — #78 round 2`,
21292
+ severity: "high",
21293
+ line: i2 + 1,
21294
+ confidence: 0.85,
21295
+ variable: `${type.name}.${fieldName}`
21296
+ });
21297
+ }
21298
+ }
21299
+ }
21300
+ return sources;
21301
+ }
21302
+ function findSetterChainSources(types, sourceCode, language) {
21303
+ if (language !== "java")
21304
+ return [];
21305
+ const sources = [];
21306
+ const lines = sourceCode.split(`
21307
+ `);
21308
+ const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
21309
+ for (const type of types) {
21310
+ if (type.kind !== "class")
21311
+ continue;
21312
+ if (type.name === "<module>")
21313
+ continue;
21314
+ const pairs = new Map;
21315
+ const setterRe = /this\.([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)\s*;?/;
21316
+ const getterRe = /return\s+this\.([A-Za-z_]\w*)\s*;?/;
21317
+ for (const m of type.methods) {
21318
+ if (m.name === type.name)
21319
+ continue;
21320
+ const mStart = m.start_line;
21321
+ const mEnd = m.end_line;
21322
+ const fullBody = lines.slice(mStart - 1, Math.min(mEnd, lines.length)).join(`
21323
+ `);
21324
+ const open = fullBody.indexOf("{");
21325
+ const close = fullBody.lastIndexOf("}");
21326
+ if (open < 0 || close < 0 || close <= open)
21327
+ continue;
21328
+ const inner = fullBody.slice(open + 1, close).replace(/\/\/[^\n]*/g, "").trim();
21329
+ if (!inner)
21330
+ continue;
21331
+ const sm = inner.match(setterRe);
21332
+ if (sm && m.parameters.length === 1 && sm[2] === m.parameters[0].name) {
21333
+ const remainder = inner.replace(sm[0], "").trim();
21334
+ if (!remainder) {
21335
+ const entry = pairs.get(sm[1]) ?? {};
21336
+ entry.setter = m.name;
21337
+ pairs.set(sm[1], entry);
21338
+ continue;
21339
+ }
21340
+ }
21341
+ const gm = inner.match(getterRe);
21342
+ if (gm && m.parameters.length === 0) {
21343
+ const remainder = inner.replace(gm[0], "").trim();
21344
+ if (!remainder) {
21345
+ const entry = pairs.get(gm[1]) ?? {};
21346
+ entry.getter = m.name;
21347
+ pairs.set(gm[1], entry);
21348
+ }
21349
+ }
21350
+ }
21351
+ for (const [, { setter, getter }] of pairs) {
21352
+ if (!setter || !getter)
21353
+ continue;
21354
+ const setterCallRe = new RegExp(`\\b([A-Za-z_]\\w*)\\.${setter}\\s*\\(\\s*([^)]+?)\\s*\\)\\s*;?`);
21355
+ for (let i2 = 0;i2 < lines.length; i2++) {
21356
+ const line = lines[i2] ?? "";
21357
+ const trimmed = line.trim();
21358
+ if (!trimmed || trimmed.startsWith("//"))
21359
+ continue;
21360
+ const cm = trimmed.match(setterCallRe);
21361
+ if (!cm)
21362
+ continue;
21363
+ const arg = cm[2];
21364
+ if (!javaHttpPattern.test(arg))
21365
+ continue;
21366
+ sources.push({
21367
+ type: "http_param",
21368
+ location: `${type.name}.${setter}(tainted) → ${type.name}.${getter}() chain — #78 round 2`,
21369
+ severity: "high",
21370
+ line: i2 + 1,
21371
+ confidence: 0.75,
21372
+ variable: getter
21373
+ });
21374
+ }
21375
+ }
21376
+ }
21377
+ return sources;
21378
+ }
21109
21379
  function findJavaScriptAssignmentSources(sourceCode, language) {
21110
21380
  if (!["javascript", "typescript"].includes(language))
21111
21381
  return [];
@@ -31047,7 +31317,7 @@ var colors = {
31047
31317
  };
31048
31318
 
31049
31319
  // src/version.ts
31050
- var version = "3.65.0";
31320
+ var version = "3.66.0";
31051
31321
 
31052
31322
  // src/formatters.ts
31053
31323
  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.66.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.66.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",