cognium-dev 3.153.0 → 3.155.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 +262 -16
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -10623,9 +10623,6 @@ var DEFAULT_SINKS = [
10623
10623
  { method: "newBufferedWriter", class: "Files", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10624
10624
  { method: "copy", class: "Files", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
10625
10625
  { method: "move", class: "Files", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
10626
- { method: "exists", class: "Files", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
10627
- { method: "isDirectory", class: "Files", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
10628
- { method: "isRegularFile", class: "Files", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
10629
10626
  { method: "RandomAccessFile", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10630
10627
  { method: "resolveURI", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10631
10628
  { method: "resolve", class: "SourceResolver", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11662,6 +11659,7 @@ var DEFAULT_SANITIZERS = [
11662
11659
  { method: "parseDouble", class: "Double", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11663
11660
  { method: "parseShort", class: "Short", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11664
11661
  { method: "parseByte", class: "Byte", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11662
+ { method: "parseBoolean", class: "Boolean", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection", "xss", "crlf", "log_injection", "xpath_injection", "ldap_injection", "xxe"] },
11665
11663
  { method: "fromString", class: "UUID", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11666
11664
  { method: "BigInt", removes: ["sql_injection", "nosql_injection", "command_injection", "path_traversal", "code_injection"] },
11667
11665
  { method: "Atoi", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
@@ -13773,6 +13771,18 @@ class CodeGraph {
13773
13771
  }
13774
13772
  return this._chainsByFromDef;
13775
13773
  }
13774
+ _chainsByToDef = null;
13775
+ get chainsByToDef() {
13776
+ if (!this._chainsByToDef) {
13777
+ this._chainsByToDef = new Map;
13778
+ for (const chain of this.ir.dfg.chains ?? []) {
13779
+ const arr = this._chainsByToDef.get(chain.to_def) ?? [];
13780
+ arr.push(chain);
13781
+ this._chainsByToDef.set(chain.to_def, arr);
13782
+ }
13783
+ }
13784
+ return this._chainsByToDef;
13785
+ }
13776
13786
  _callsByLine = null;
13777
13787
  get callsByLine() {
13778
13788
  if (!this._callsByLine) {
@@ -15643,6 +15653,45 @@ class AnalysisPipeline {
15643
15653
  return { results, findings };
15644
15654
  }
15645
15655
  }
15656
+ // ../circle-ir/dist/analysis/dfg-walk.js
15657
+ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
15658
+ const maxHops = options.maxHops ?? 32;
15659
+ const visited = new Set;
15660
+ const lines = new Set;
15661
+ let hopCapReached = false;
15662
+ const startDef = defById.get(startDefId);
15663
+ if (!startDef) {
15664
+ return { visited, lines, hopCapReached: false };
15665
+ }
15666
+ visited.add(startDefId);
15667
+ lines.add(startDef.line);
15668
+ const queue = [startDefId];
15669
+ let hops = 0;
15670
+ while (queue.length > 0) {
15671
+ if (hops >= maxHops) {
15672
+ hopCapReached = true;
15673
+ break;
15674
+ }
15675
+ hops++;
15676
+ const currentId = queue.shift();
15677
+ const incoming = chainsByToDef.get(currentId);
15678
+ if (!incoming)
15679
+ continue;
15680
+ for (const chain of incoming) {
15681
+ const fromId = chain.from_def;
15682
+ if (visited.has(fromId))
15683
+ continue;
15684
+ visited.add(fromId);
15685
+ const fromDef = defById.get(fromId);
15686
+ if (fromDef) {
15687
+ lines.add(fromDef.line);
15688
+ }
15689
+ queue.push(fromId);
15690
+ }
15691
+ }
15692
+ return { visited, lines, hopCapReached };
15693
+ }
15694
+
15646
15695
  // ../circle-ir/dist/analysis/taint-propagation.js
15647
15696
  function buildSanitizersByLine(sanitizers) {
15648
15697
  const out2 = new Map;
@@ -15724,7 +15773,11 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
15724
15773
  if (allTaintedDefIds.has(use.def_id)) {
15725
15774
  const taintInfo = taintByDefId.get(use.def_id);
15726
15775
  if (taintInfo) {
15727
- const isSanitized = checkSanitized(taintInfo.line, sink.line, sink.type, sanitizersByLine);
15776
+ const isSanitized = checkSanitized(taintInfo.line, sink.line, sink.type, sanitizersByLine, {
15777
+ startDefId: use.def_id,
15778
+ chainsByToDef: graph.chainsByToDef,
15779
+ defById
15780
+ });
15728
15781
  if (!isSanitized.sanitized) {
15729
15782
  const source = sources.find((s) => s.line === taintInfo.sourceLine);
15730
15783
  if (source) {
@@ -15854,20 +15907,36 @@ var KNOWN_SINK_TYPES = new Set([
15854
15907
  "crlf",
15855
15908
  "mass_assignment"
15856
15909
  ]);
15857
- function checkSanitized(_fromLine, toLine, sinkType, sanitizersByLine) {
15858
- const sanitizersAtTarget = sanitizersByLine.get(toLine);
15859
- if (!sanitizersAtTarget || sanitizersAtTarget.length === 0) {
15860
- return { sanitized: false };
15861
- }
15910
+ function checkSanitized(_fromLine, toLine, sinkType, sanitizersByLine, ctx) {
15862
15911
  const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
15863
- for (const san of sanitizersAtTarget) {
15864
- if (isKnownSinkType) {
15865
- if (san.sanitizes.includes(sinkType)) {
15912
+ const sanitizersAtTarget = sanitizersByLine.get(toLine);
15913
+ if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
15914
+ for (const san of sanitizersAtTarget) {
15915
+ if (isKnownSinkType) {
15916
+ if (san.sanitizes.includes(sinkType)) {
15917
+ return { sanitized: true, sanitizer: san };
15918
+ }
15919
+ } else if (san.sanitizes.length > 0) {
15866
15920
  return { sanitized: true, sanitizer: san };
15867
15921
  }
15868
- } else {
15869
- if (san.sanitizes.length > 0) {
15870
- return { sanitized: true, sanitizer: san };
15922
+ }
15923
+ }
15924
+ if (ctx) {
15925
+ const walk = walkBackwardDefs(ctx.startDefId, ctx.chainsByToDef, ctx.defById, { maxHops: ctx.maxHops ?? 32 });
15926
+ for (const line of walk.lines) {
15927
+ if (line === toLine)
15928
+ continue;
15929
+ const sansAtLine = sanitizersByLine.get(line);
15930
+ if (!sansAtLine || sansAtLine.length === 0)
15931
+ continue;
15932
+ for (const san of sansAtLine) {
15933
+ if (isKnownSinkType) {
15934
+ if (san.sanitizes.includes(sinkType)) {
15935
+ return { sanitized: true, sanitizer: san };
15936
+ }
15937
+ } else if (san.sanitizes.length > 0) {
15938
+ return { sanitized: true, sanitizer: san };
15939
+ }
15871
15940
  }
15872
15941
  }
15873
15942
  }
@@ -24135,6 +24204,20 @@ function findJavaScriptDOMSinks(sourceCode, language) {
24135
24204
  method = "cssText";
24136
24205
  else if (line.includes("style.textContent"))
24137
24206
  method = "textContent";
24207
+ if (method === "document.write" || method === "document.writeln") {
24208
+ const argMatch = line.match(/document\.write(?:ln)?\s*\(\s*([^)]*)\)/);
24209
+ if (argMatch) {
24210
+ const arg = argMatch[1].trim();
24211
+ if (!arg.includes("+")) {
24212
+ const doubleQ = /^"(?:[^"\\]|\\.)*"$/;
24213
+ const singleQ = /^'(?:[^'\\]|\\.)*'$/;
24214
+ const backtick = /^`(?:[^`\\$]|\\.|\$(?!\{))*`$/;
24215
+ if (doubleQ.test(arg) || singleQ.test(arg) || backtick.test(arg)) {
24216
+ break;
24217
+ }
24218
+ }
24219
+ }
24220
+ }
24138
24221
  const alreadyExists = sinks.some((s) => s.line === lineNumber && s.cwe === cwe);
24139
24222
  if (!alreadyExists) {
24140
24223
  sinks.push({ type, cwe, severity, line: lineNumber, location: line.trim().substring(0, 80), method });
@@ -31144,6 +31227,137 @@ class LibraryProfileSinkGatePass {
31144
31227
  };
31145
31228
  }
31146
31229
  }
31230
+ var CWE22_SPECULATIVE_SOURCE_TYPES = new Set([
31231
+ "interprocedural_param",
31232
+ "constructor_field"
31233
+ ]);
31234
+
31235
+ class LibraryProfileCwe22PathGatePass {
31236
+ name = "library-profile-cwe22-path-gate";
31237
+ category = "security";
31238
+ run(ctx) {
31239
+ const { graph } = ctx;
31240
+ const profile = graph.ir.meta.projectProfile;
31241
+ if (!isLibraryShape2(profile)) {
31242
+ return {
31243
+ profile,
31244
+ applied: false,
31245
+ dropped: 0,
31246
+ droppedBySourceType: {}
31247
+ };
31248
+ }
31249
+ const flows = graph.ir.taint.flows;
31250
+ if (!flows || flows.length === 0) {
31251
+ return {
31252
+ profile,
31253
+ applied: true,
31254
+ dropped: 0,
31255
+ droppedBySourceType: {}
31256
+ };
31257
+ }
31258
+ const droppedBySourceType = {};
31259
+ const kept = [];
31260
+ for (const flow of flows) {
31261
+ if (flow.sink_type === "path_traversal" && CWE22_SPECULATIVE_SOURCE_TYPES.has(flow.source_type)) {
31262
+ droppedBySourceType[flow.source_type] = (droppedBySourceType[flow.source_type] ?? 0) + 1;
31263
+ continue;
31264
+ }
31265
+ kept.push(flow);
31266
+ }
31267
+ const dropped = flows.length - kept.length;
31268
+ if (dropped > 0) {
31269
+ flows.length = 0;
31270
+ flows.push(...kept);
31271
+ }
31272
+ return {
31273
+ profile,
31274
+ applied: true,
31275
+ dropped,
31276
+ droppedBySourceType
31277
+ };
31278
+ }
31279
+ }
31280
+
31281
+ // ../circle-ir/dist/analysis/passes/library-profile-xss-gate-pass.js
31282
+ var XSS_NON_HTML_OUTPUT_CLASSES = new Set([
31283
+ "StringBuilder",
31284
+ "StringBuffer",
31285
+ "CharArrayWriter",
31286
+ "ByteArrayOutputStream",
31287
+ "PrintStream",
31288
+ "System",
31289
+ "HttpRequest",
31290
+ "HttpRequestBuilder",
31291
+ "HttpResponse",
31292
+ "HttpSession",
31293
+ "ServletRequest",
31294
+ "HttpServletRequest",
31295
+ "RedisOutputStream",
31296
+ "SafeEncoder",
31297
+ "RESP2",
31298
+ "Protocol",
31299
+ "JSONUtil",
31300
+ "JSON",
31301
+ "ObjectMapper",
31302
+ "JsonReader",
31303
+ "Logger",
31304
+ "LoggerFactory",
31305
+ "Log",
31306
+ "Slf4jLogger",
31307
+ "RequestContext",
31308
+ "Context"
31309
+ ]);
31310
+ function isLibraryShape3(profile) {
31311
+ if (!profile || profile === "unknown")
31312
+ return false;
31313
+ return profile.startsWith("library/");
31314
+ }
31315
+
31316
+ class LibraryProfileXssGatePass {
31317
+ name = "library-profile-xss-gate";
31318
+ category = "security";
31319
+ run(ctx) {
31320
+ const { graph } = ctx;
31321
+ const profile = graph.ir.meta.projectProfile;
31322
+ if (!isLibraryShape3(profile)) {
31323
+ return {
31324
+ profile,
31325
+ applied: false,
31326
+ dropped: 0,
31327
+ droppedByClass: {}
31328
+ };
31329
+ }
31330
+ const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
31331
+ if (sinks.length === 0) {
31332
+ return {
31333
+ profile,
31334
+ applied: true,
31335
+ dropped: 0,
31336
+ droppedByClass: {}
31337
+ };
31338
+ }
31339
+ const droppedByClass = {};
31340
+ const kept = [];
31341
+ for (const sink of sinks) {
31342
+ if (sink.type === "xss" && sink.class && XSS_NON_HTML_OUTPUT_CLASSES.has(sink.class)) {
31343
+ droppedByClass[sink.class] = (droppedByClass[sink.class] ?? 0) + 1;
31344
+ continue;
31345
+ }
31346
+ kept.push(sink);
31347
+ }
31348
+ const dropped = sinks.length - kept.length;
31349
+ if (dropped > 0) {
31350
+ sinks.length = 0;
31351
+ sinks.push(...kept);
31352
+ }
31353
+ return {
31354
+ profile,
31355
+ applied: true,
31356
+ dropped,
31357
+ droppedByClass
31358
+ };
31359
+ }
31360
+ }
31147
31361
 
31148
31362
  // ../circle-ir/dist/analysis/passes/taint-propagation-pass.js
31149
31363
  class TaintPropagationPass {
@@ -38079,6 +38293,31 @@ class WeakHashPass {
38079
38293
  }
38080
38294
  }
38081
38295
 
38296
+ // ../circle-ir/dist/analysis/path-classification.js
38297
+ var TEST_PATH_PATTERNS = [
38298
+ /(^|\/)test\//i,
38299
+ /(^|\/)tests\//i,
38300
+ /(^|\/)testing\//i,
38301
+ /(^|\/)__tests__\//i,
38302
+ /(^|\/)spec\//i,
38303
+ /_test\.go$/i,
38304
+ /(^|\/)test_[^/]+\.py$/i,
38305
+ /_test\.py$/i,
38306
+ /\.test\.(?:tsx?|jsx?|mjs|cjs)$/i,
38307
+ /\.spec\.(?:tsx?|jsx?|mjs|cjs)$/i,
38308
+ /\.test\.(?:java|kt)$/i
38309
+ ];
38310
+ function isTestPath(filepath) {
38311
+ if (!filepath)
38312
+ return false;
38313
+ const normalized = filepath.replace(/\\/g, "/");
38314
+ for (const pattern of TEST_PATH_PATTERNS) {
38315
+ if (pattern.test(normalized))
38316
+ return true;
38317
+ }
38318
+ return false;
38319
+ }
38320
+
38082
38321
  // ../circle-ir/dist/analysis/passes/weak-crypto-pass.js
38083
38322
  var WEAK_CIPHER_BASES = new Set([
38084
38323
  "des",
@@ -38294,6 +38533,9 @@ class WeakCryptoPass {
38294
38533
  if (isProtocolMandatedCryptoFile(file, code)) {
38295
38534
  return { findings: [] };
38296
38535
  }
38536
+ if (isTestPath(file)) {
38537
+ return { findings: [] };
38538
+ }
38297
38539
  const findings = [];
38298
38540
  const constProp = ctx.hasResult("constant-propagation") ? ctx.getResult("constant-propagation") : null;
38299
38541
  const literalBindings = scanLiteralBindings(code, language);
@@ -42059,10 +42301,14 @@ async function analyze(code, filePath, language, options = {}) {
42059
42301
  pipeline.add(new CliMainReflectionSuppressPass);
42060
42302
  if (!disabledPasses.has("library-profile-sink-gate"))
42061
42303
  pipeline.add(new LibraryProfileSinkGatePass);
42304
+ if (!disabledPasses.has("library-profile-xss-gate"))
42305
+ pipeline.add(new LibraryProfileXssGatePass);
42062
42306
  pipeline.add(new TaintPropagationPass);
42063
42307
  pipeline.add(new InterproceduralPass({
42064
42308
  enableEntryPointGate: options.enableEntryPointGate ?? true
42065
42309
  }));
42310
+ if (!disabledPasses.has("library-profile-cwe22-path-gate"))
42311
+ pipeline.add(new LibraryProfileCwe22PathGatePass);
42066
42312
  if (!disabledPasses.has("scan-secrets"))
42067
42313
  pipeline.add(new ScanSecretsPass);
42068
42314
  if (!disabledPasses.has("dead-code"))
@@ -42993,7 +43239,7 @@ var colors = {
42993
43239
  };
42994
43240
 
42995
43241
  // src/version.ts
42996
- var version = "3.153.0";
43242
+ var version = "3.155.0";
42997
43243
 
42998
43244
  // src/formatters.ts
42999
43245
  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.153.0",
3
+ "version": "3.155.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.153.0"
69
+ "circle-ir": "^3.155.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",