cognium-dev 3.148.0 → 3.151.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 +379 -6
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -28873,6 +28873,61 @@ class SourceSemanticsPass {
28873
28873
  }
28874
28874
  }
28875
28875
 
28876
+ // ../circle-ir/dist/analysis/passes/library-profile-source-gate-pass.js
28877
+ var SPECULATIVE_SOURCE_TYPES = new Set([
28878
+ "interprocedural_param",
28879
+ "constructor_field"
28880
+ ]);
28881
+ function isLibraryShape(profile) {
28882
+ if (!profile || profile === "unknown")
28883
+ return false;
28884
+ return profile.startsWith("library/");
28885
+ }
28886
+
28887
+ class LibraryProfileSourceGatePass {
28888
+ name = "library-profile-source-gate";
28889
+ category = "security";
28890
+ run(ctx) {
28891
+ const { graph } = ctx;
28892
+ const profile = graph.ir.meta.projectProfile;
28893
+ if (!isLibraryShape(profile)) {
28894
+ return {
28895
+ profile,
28896
+ applied: false,
28897
+ dropped: 0,
28898
+ droppedByType: {}
28899
+ };
28900
+ }
28901
+ const sources = graph.ir.taint.sources;
28902
+ if (sources.length === 0) {
28903
+ return {
28904
+ profile,
28905
+ applied: true,
28906
+ dropped: 0,
28907
+ droppedByType: {}
28908
+ };
28909
+ }
28910
+ const droppedByType = {};
28911
+ const kept = [];
28912
+ for (const src of sources) {
28913
+ if (SPECULATIVE_SOURCE_TYPES.has(src.type)) {
28914
+ droppedByType[src.type] = (droppedByType[src.type] ?? 0) + 1;
28915
+ continue;
28916
+ }
28917
+ kept.push(src);
28918
+ }
28919
+ const dropped = sources.length - kept.length;
28920
+ sources.length = 0;
28921
+ sources.push(...kept);
28922
+ return {
28923
+ profile,
28924
+ applied: true,
28925
+ dropped,
28926
+ droppedByType
28927
+ };
28928
+ }
28929
+ }
28930
+
28876
28931
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
28877
28932
  var JS_XSS_SANITIZERS = [
28878
28933
  /\bDOMPurify\.sanitize\s*\(/,
@@ -30343,7 +30398,7 @@ class SinkSemanticsPass {
30343
30398
  return { droppedCount: 0, registrySize: 0 };
30344
30399
  }
30345
30400
  const registry = buildRegistry(entries);
30346
- const sinks = graph.ir.taint.sinks;
30401
+ const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
30347
30402
  let droppedCount = 0;
30348
30403
  const kept = sinks.filter((sink) => {
30349
30404
  if (!sink.class || !sink.method)
@@ -30366,6 +30421,194 @@ class SinkSemanticsPass {
30366
30421
  }
30367
30422
  }
30368
30423
 
30424
+ // ../circle-ir/dist/analysis/passes/cli-main-reflection-suppress-pass.js
30425
+ var REFLECTION_SINK_METHODS = new Set([
30426
+ "forName",
30427
+ "newInstance",
30428
+ "invoke",
30429
+ "getMethod",
30430
+ "getDeclaredMethod",
30431
+ "getConstructor",
30432
+ "getDeclaredConstructor",
30433
+ "loadClass",
30434
+ "defineClass"
30435
+ ]);
30436
+ var TIER_1_CLASS_ANNOTATIONS = new Set([
30437
+ "RestController",
30438
+ "Controller",
30439
+ "Service",
30440
+ "Repository",
30441
+ "Component",
30442
+ "Path",
30443
+ "WebServlet",
30444
+ "ServerEndpoint",
30445
+ "FeignClient"
30446
+ ]);
30447
+ var TIER_1_METHOD_ANNOTATIONS = new Set([
30448
+ "RequestMapping",
30449
+ "GetMapping",
30450
+ "PostMapping",
30451
+ "PutMapping",
30452
+ "DeleteMapping",
30453
+ "PatchMapping",
30454
+ "MessageMapping",
30455
+ "SubscribeMapping",
30456
+ "KafkaListener",
30457
+ "KafkaHandler",
30458
+ "RabbitListener",
30459
+ "RabbitHandler",
30460
+ "JmsListener",
30461
+ "StreamListener",
30462
+ "SqsListener",
30463
+ "SqsHandler",
30464
+ "EventListener",
30465
+ "Scheduled",
30466
+ "Path",
30467
+ "GET",
30468
+ "POST",
30469
+ "PUT",
30470
+ "DELETE",
30471
+ "PATCH",
30472
+ "HEAD",
30473
+ "OPTIONS",
30474
+ "DataBoundConstructor",
30475
+ "DataBoundSetter"
30476
+ ]);
30477
+ var TIER_1_SUPERTYPES = new Set([
30478
+ "HttpServlet",
30479
+ "GenericServlet",
30480
+ "Filter",
30481
+ "HandlerInterceptor",
30482
+ "AsyncHandlerInterceptor",
30483
+ "CommandLineRunner",
30484
+ "ApplicationRunner",
30485
+ "SimpleChannelInboundHandler",
30486
+ "ChannelInboundHandler",
30487
+ "ChannelInboundHandlerAdapter",
30488
+ "ChannelDuplexHandler",
30489
+ "NettyRequestProcessor",
30490
+ "Converter",
30491
+ "SingleValueConverter",
30492
+ "ConverterMatcher",
30493
+ "AbstractReflectionConverter",
30494
+ "AbstractSingleValueConverter",
30495
+ "AbstractCollectionConverter"
30496
+ ]);
30497
+ function normalizeAnnotation(raw) {
30498
+ let s = raw.trim();
30499
+ if (s.startsWith("@"))
30500
+ s = s.slice(1);
30501
+ const parenIdx = s.indexOf("(");
30502
+ if (parenIdx >= 0)
30503
+ s = s.slice(0, parenIdx);
30504
+ const genericIdx = s.indexOf("<");
30505
+ if (genericIdx >= 0)
30506
+ s = s.slice(0, genericIdx);
30507
+ const dotIdx = s.lastIndexOf(".");
30508
+ if (dotIdx >= 0)
30509
+ s = s.slice(dotIdx + 1);
30510
+ return s.trim();
30511
+ }
30512
+ function normalizeSupertype(raw) {
30513
+ let s = raw.trim();
30514
+ const genericIdx = s.indexOf("<");
30515
+ if (genericIdx >= 0)
30516
+ s = s.slice(0, genericIdx);
30517
+ const dotIdx = s.lastIndexOf(".");
30518
+ if (dotIdx >= 0)
30519
+ s = s.slice(dotIdx + 1);
30520
+ return s.trim();
30521
+ }
30522
+ function isMainMethod(name2, paramTypes) {
30523
+ if (name2 !== "main")
30524
+ return false;
30525
+ if (paramTypes.length !== 1)
30526
+ return false;
30527
+ const t = paramTypes[0];
30528
+ if (!t)
30529
+ return false;
30530
+ const bare = t.replace(/\s+/g, "");
30531
+ return bare === "String[]" || bare === "java.lang.String[]";
30532
+ }
30533
+
30534
+ class CliMainReflectionSuppressPass {
30535
+ name = "cli-main-reflection-suppress";
30536
+ category = "security";
30537
+ run(ctx) {
30538
+ const { graph, language } = ctx;
30539
+ if (language !== "java") {
30540
+ return { cliMainSignal: false, droppedCount: 0 };
30541
+ }
30542
+ const types = graph.ir.types;
30543
+ if (!types || types.length === 0) {
30544
+ return { cliMainSignal: false, droppedCount: 0 };
30545
+ }
30546
+ let hasMain = false;
30547
+ let hasFrameworkSignal = false;
30548
+ for (const type of types) {
30549
+ for (const ann of type.annotations) {
30550
+ if (TIER_1_CLASS_ANNOTATIONS.has(normalizeAnnotation(ann))) {
30551
+ hasFrameworkSignal = true;
30552
+ break;
30553
+ }
30554
+ }
30555
+ if (hasFrameworkSignal)
30556
+ break;
30557
+ if (type.extends && TIER_1_SUPERTYPES.has(normalizeSupertype(type.extends))) {
30558
+ hasFrameworkSignal = true;
30559
+ break;
30560
+ }
30561
+ for (const impl of type.implements) {
30562
+ if (TIER_1_SUPERTYPES.has(normalizeSupertype(impl))) {
30563
+ hasFrameworkSignal = true;
30564
+ break;
30565
+ }
30566
+ }
30567
+ if (hasFrameworkSignal)
30568
+ break;
30569
+ for (const method of type.methods) {
30570
+ for (const ann of method.annotations) {
30571
+ if (TIER_1_METHOD_ANNOTATIONS.has(normalizeAnnotation(ann))) {
30572
+ hasFrameworkSignal = true;
30573
+ break;
30574
+ }
30575
+ }
30576
+ if (hasFrameworkSignal)
30577
+ break;
30578
+ if (!hasMain) {
30579
+ const paramTypes = method.parameters.map((p) => p.type);
30580
+ if (isMainMethod(method.name, paramTypes)) {
30581
+ hasMain = true;
30582
+ }
30583
+ }
30584
+ }
30585
+ if (hasFrameworkSignal)
30586
+ break;
30587
+ }
30588
+ const cliMainSignal = hasMain && !hasFrameworkSignal;
30589
+ if (!cliMainSignal) {
30590
+ return { cliMainSignal: false, droppedCount: 0 };
30591
+ }
30592
+ const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
30593
+ let droppedCount = 0;
30594
+ const kept = sinks.filter((sink) => {
30595
+ if (sink.type !== "code_injection")
30596
+ return true;
30597
+ if (!sink.method)
30598
+ return true;
30599
+ if (!REFLECTION_SINK_METHODS.has(sink.method))
30600
+ return true;
30601
+ droppedCount++;
30602
+ return false;
30603
+ });
30604
+ if (droppedCount > 0) {
30605
+ sinks.length = 0;
30606
+ sinks.push(...kept);
30607
+ }
30608
+ return { cliMainSignal: true, droppedCount };
30609
+ }
30610
+ }
30611
+
30369
30612
  // ../circle-ir/dist/analysis/passes/taint-propagation-pass.js
30370
30613
  class TaintPropagationPass {
30371
30614
  name = "taint-propagation";
@@ -31733,7 +31976,7 @@ function findTaintBridges2(result) {
31733
31976
  }
31734
31977
 
31735
31978
  // ../circle-ir/dist/analysis/entry-point-detection.js
31736
- var TIER_1_METHOD_ANNOTATIONS = new Set([
31979
+ var TIER_1_METHOD_ANNOTATIONS2 = new Set([
31737
31980
  "RequestMapping",
31738
31981
  "GetMapping",
31739
31982
  "PostMapping",
@@ -31763,7 +32006,7 @@ var TIER_1_METHOD_ANNOTATIONS = new Set([
31763
32006
  "DataBoundConstructor",
31764
32007
  "DataBoundSetter"
31765
32008
  ]);
31766
- var TIER_1_CLASS_ANNOTATIONS = new Set([
32009
+ var TIER_1_CLASS_ANNOTATIONS2 = new Set([
31767
32010
  "RestController",
31768
32011
  "Controller",
31769
32012
  "Service",
@@ -31913,10 +32156,10 @@ function classifyEntryPointTier(method, enclosingType, ctx) {
31913
32156
  if (classShapeIsLibraryFacade(enclosingType)) {
31914
32157
  return "TIER_3_LIBRARY_API";
31915
32158
  }
31916
- if (annotationsInclude(method.annotations, TIER_1_METHOD_ANNOTATIONS)) {
32159
+ if (annotationsInclude(method.annotations, TIER_1_METHOD_ANNOTATIONS2)) {
31917
32160
  return "TIER_1_ENTRY_POINT";
31918
32161
  }
31919
- if (enclosingType && annotationsInclude(enclosingType.annotations, TIER_1_CLASS_ANNOTATIONS)) {
32162
+ if (enclosingType && annotationsInclude(enclosingType.annotations, TIER_1_CLASS_ANNOTATIONS2)) {
31920
32163
  return "TIER_1_ENTRY_POINT";
31921
32164
  }
31922
32165
  if (methodIsSupertypeLifecycleEntryPoint(method, enclosingType)) {
@@ -32899,6 +33142,37 @@ var CLOSE_METHODS = new Set([
32899
33142
  "shutdownNow",
32900
33143
  "terminate"
32901
33144
  ]);
33145
+ var WRAPPER_CTORS = new Set([
33146
+ "BufferedInputStream",
33147
+ "BufferedOutputStream",
33148
+ "BufferedReader",
33149
+ "BufferedWriter",
33150
+ "InputStreamReader",
33151
+ "OutputStreamWriter",
33152
+ "DataInputStream",
33153
+ "DataOutputStream",
33154
+ "PrintStream",
33155
+ "PrintWriter",
33156
+ "LineNumberReader",
33157
+ "PushbackInputStream",
33158
+ "PushbackReader",
33159
+ "SequenceInputStream",
33160
+ "GZIPInputStream",
33161
+ "GZIPOutputStream",
33162
+ "ZipInputStream",
33163
+ "ZipOutputStream",
33164
+ "InflaterInputStream",
33165
+ "DeflaterOutputStream",
33166
+ "CheckedInputStream",
33167
+ "CheckedOutputStream"
33168
+ ]);
33169
+ var WORKER_METHODS = new Set([
33170
+ "run",
33171
+ "call",
33172
+ "accept",
33173
+ "get",
33174
+ "apply"
33175
+ ]);
32902
33176
 
32903
33177
  class ResourceLeakPass {
32904
33178
  name = "resource-leak";
@@ -32935,6 +33209,12 @@ class ResourceLeakPass {
32935
33209
  if (this.isFactoryMethod(methodInfo.method)) {
32936
33210
  continue;
32937
33211
  }
33212
+ if (this.isWrappedByCloseableCtor(graph.ir.calls, resourceVar, openLine, methodEnd)) {
33213
+ continue;
33214
+ }
33215
+ if (this.isClosedInNestedWorker(graph, codeLines, resourceVar, methodInfo, openLine, methodEnd)) {
33216
+ continue;
33217
+ }
32938
33218
  const closeCall = graph.ir.calls.find((c) => CLOSE_METHODS.has(c.method_name) && c.receiver === resourceVar && c.location.line > openLine && c.location.line <= methodEnd);
32939
33219
  const snippet = (codeLines[openLine - 1] ?? "").trim();
32940
33220
  if (!closeCall) {
@@ -33031,6 +33311,57 @@ class ResourceLeakPass {
33031
33311
  return false;
33032
33312
  return FACTORY_METHOD_NAME_RE.test(method.name);
33033
33313
  }
33314
+ isWrappedByCloseableCtor(calls, variable, fromLine, toLine) {
33315
+ for (const call of calls) {
33316
+ if (!WRAPPER_CTORS.has(call.method_name))
33317
+ continue;
33318
+ if (call.location.line < fromLine || call.location.line > toLine)
33319
+ continue;
33320
+ for (const arg of call.arguments) {
33321
+ if (arg.variable === variable)
33322
+ return true;
33323
+ }
33324
+ }
33325
+ return false;
33326
+ }
33327
+ isClosedInNestedWorker(graph, lines, variable, methodInfo, fromLine, toLine) {
33328
+ const fieldNames = new Set(methodInfo.type.fields.map((f) => f.name));
33329
+ let candidateField = null;
33330
+ if (fieldNames.has(variable)) {
33331
+ candidateField = variable;
33332
+ } else {
33333
+ const thisAssignRe = new RegExp(`(?:\\bthis\\s*\\.\\s*)?(\\w+)\\s*=\\s*${escapeRegex2(variable)}\\b`);
33334
+ for (let l = fromLine;l <= toLine && l <= lines.length; l++) {
33335
+ const m = thisAssignRe.exec(lines[l - 1] ?? "");
33336
+ if (m && fieldNames.has(m[1])) {
33337
+ candidateField = m[1];
33338
+ break;
33339
+ }
33340
+ }
33341
+ }
33342
+ if (!candidateField)
33343
+ return false;
33344
+ for (const call of graph.ir.calls) {
33345
+ if (call.receiver !== candidateField)
33346
+ continue;
33347
+ if (!CLOSE_METHODS.has(call.method_name))
33348
+ continue;
33349
+ const closeLine = call.location.line;
33350
+ if (closeLine < fromLine || closeLine > toLine)
33351
+ continue;
33352
+ const enclosing = graph.methodAtLine(closeLine);
33353
+ if (!enclosing)
33354
+ continue;
33355
+ if (enclosing.method === methodInfo.method)
33356
+ continue;
33357
+ if (!WORKER_METHODS.has(enclosing.method.name))
33358
+ continue;
33359
+ if (enclosing.method.start_line > fromLine && enclosing.method.start_line <= toLine) {
33360
+ return true;
33361
+ }
33362
+ }
33363
+ return false;
33364
+ }
33034
33365
  }
33035
33366
 
33036
33367
  // ../circle-ir/dist/graph/scope-graph.js
@@ -41295,6 +41626,38 @@ function makeProfileResolver(p) {
41295
41626
  return () => p;
41296
41627
  return (file) => p.get(file) ?? "unknown";
41297
41628
  }
41629
+ function computeProjectProfileSummary(fileAnalyses) {
41630
+ const byShape = {
41631
+ library: 0,
41632
+ application: 0,
41633
+ cli: 0,
41634
+ server: 0,
41635
+ plugin: 0,
41636
+ unknown: 0
41637
+ };
41638
+ const byEnv = {
41639
+ production: 0,
41640
+ dev: 0,
41641
+ sample: 0,
41642
+ benchmark: 0,
41643
+ test: 0,
41644
+ unknown: 0
41645
+ };
41646
+ for (const { analysis } of fileAnalyses) {
41647
+ const profile = analysis.meta.projectProfile ?? "unknown";
41648
+ if (profile === "unknown") {
41649
+ byShape.unknown += 1;
41650
+ byEnv.unknown += 1;
41651
+ continue;
41652
+ }
41653
+ const slash = profile.indexOf("/");
41654
+ const shape = profile.slice(0, slash);
41655
+ const env = profile.slice(slash + 1);
41656
+ byShape[shape] += 1;
41657
+ byEnv[env] += 1;
41658
+ }
41659
+ return { byShape, byEnv, totalFiles: fileAnalyses.length };
41660
+ }
41298
41661
  async function analyze(code, filePath, language, options = {}) {
41299
41662
  if (!initialized) {
41300
41663
  await initAnalyzer(options);
@@ -41324,6 +41687,9 @@ async function analyze(code, filePath, language, options = {}) {
41324
41687
  }
41325
41688
  const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
41326
41689
  const meta = extractMeta(code, tree, filePath, language);
41690
+ if (options.projectProfile !== undefined) {
41691
+ meta.projectProfile = makeProfileResolver(options.projectProfile)(filePath);
41692
+ }
41327
41693
  const types = extractTypes(tree, nodeCache, language);
41328
41694
  const calls = extractCalls(tree, nodeCache, language);
41329
41695
  const imports = extractImports(tree, language);
@@ -41352,9 +41718,13 @@ async function analyze(code, filePath, language, options = {}) {
41352
41718
  pipeline.add(new LanguageSourcesPass);
41353
41719
  if (!disabledPasses.has("source-semantics"))
41354
41720
  pipeline.add(new SourceSemanticsPass);
41721
+ if (!disabledPasses.has("library-profile-source-gate"))
41722
+ pipeline.add(new LibraryProfileSourceGatePass);
41355
41723
  pipeline.add(new SinkFilterPass);
41356
41724
  if (!disabledPasses.has("sink-semantics"))
41357
41725
  pipeline.add(new SinkSemanticsPass);
41726
+ if (!disabledPasses.has("cli-main-reflection-suppress"))
41727
+ pipeline.add(new CliMainReflectionSuppressPass);
41358
41728
  pipeline.add(new TaintPropagationPass);
41359
41729
  pipeline.add(new InterproceduralPass({
41360
41730
  enableEntryPointGate: options.enableEntryPointGate ?? true
@@ -41641,6 +42011,9 @@ async function analyzeProject(files, options = {}) {
41641
42011
  total_loc: totalLoc,
41642
42012
  analyzed_at: new Date().toISOString()
41643
42013
  };
42014
+ if (options.projectProfile !== undefined) {
42015
+ meta.projectProfileSummary = computeProjectProfileSummary(fileAnalyses);
42016
+ }
41644
42017
  const projectAnalysis = {
41645
42018
  meta,
41646
42019
  files: fileAnalyses,
@@ -42282,7 +42655,7 @@ var colors = {
42282
42655
  };
42283
42656
 
42284
42657
  // src/version.ts
42285
- var version = "3.148.0";
42658
+ var version = "3.151.0";
42286
42659
 
42287
42660
  // src/formatters.ts
42288
42661
  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.148.0",
3
+ "version": "3.151.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.148.0"
69
+ "circle-ir": "^3.151.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",