circle-ir 3.152.0 → 3.153.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.
@@ -11286,11 +11286,11 @@ var DEFAULT_SINKS = [
11286
11286
  { method: "FileOutputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11287
11287
  { method: "FileReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11288
11288
  { method: "FileWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11289
- // ClassLoader/Class resource loading (can be abused for path traversal)
11290
- { method: "getResource", class: "ClassLoader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11291
- { method: "getResourceAsStream", class: "ClassLoader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11292
- { method: "getResource", class: "Class", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11293
- { method: "getResourceAsStream", class: "Class", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11289
+ // NOTE: ClassLoader.getResource / Class.getResource were removed in
11290
+ // 3.153.0 (#233). Classpath resource resolution cannot escape the
11291
+ // classpath root via `../` (JAR entries are opaque). If reintroduced,
11292
+ // the correct CWE is CWE-829 (untrusted-classpath-resource), not
11293
+ // CWE-22 path traversal.
11294
11294
  // Paths.get can be used for path traversal
11295
11295
  { method: "get", class: "Paths", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11296
11296
  { method: "of", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11793,6 +11793,15 @@ var DEFAULT_SINKS = [
11793
11793
  { method: "parseObject", class: "JSON", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11794
11794
  { method: "parseObject", class: "JSONObject", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11795
11795
  { method: "fromJson", class: "Gson", type: "deserialization", cwe: "CWE-502", severity: "medium", arg_positions: [0], safe_if_class_literal_at: 1 },
11796
+ // Jackson ObjectReader — pre-configured reader; typed 2-arg overload is safe
11797
+ // when arg[1] is a class literal or TypeReference<>()/TypeToken<>() (handled
11798
+ // by argIsClassLiteral extension). Added 3.153.0 (cognium-dev #233).
11799
+ { method: "readValue", class: "ObjectReader", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11800
+ // Jackson ObjectMapper.convertValue — same class-literal shape as readValue.
11801
+ { method: "convertValue", class: "ObjectMapper", type: "deserialization", cwe: "CWE-502", severity: "medium", arg_positions: [0], safe_if_class_literal_at: 1 },
11802
+ // Kryo.readObject(input, User.class) — typed form is safe; polymorphic form
11803
+ // (Kryo.readClassAndObject or non-literal type) remains a sink.
11804
+ { method: "readObject", class: "Kryo", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11796
11805
  // XMLDecoder
11797
11806
  { method: "readObject", class: "XMLDecoder", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
11798
11807
  // Java serialization constructors
@@ -13652,7 +13661,17 @@ function argIsClassLiteral(call, position) {
13652
13661
  if (!arg) return false;
13653
13662
  const expr = (arg.literal ?? arg.expression ?? "").trim();
13654
13663
  if (!expr) return false;
13655
- return CLASS_LITERAL_RE.test(expr);
13664
+ if (CLASS_LITERAL_RE.test(expr)) return true;
13665
+ return TYPE_TOKEN_RE.test(expr);
13666
+ }
13667
+ var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
13668
+ function argIsStringLiteral(call, position) {
13669
+ const arg = call.arguments.find((a) => a.position === position);
13670
+ if (!arg) return false;
13671
+ if (arg.literal !== void 0 && arg.literal !== null && arg.literal !== "") return true;
13672
+ const expr = (arg.expression ?? "").trim();
13673
+ if (!expr) return false;
13674
+ return expr.startsWith('"') && expr.endsWith('"') && expr.length >= 2 || expr.startsWith("'") && expr.endsWith("'") && expr.length >= 2 || expr.startsWith('"""') && expr.endsWith('"""');
13656
13675
  }
13657
13676
  var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
13658
13677
  // java.lang.*
@@ -13827,6 +13846,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
13827
13846
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
13828
13847
  continue;
13829
13848
  }
13849
+ if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
13850
+ continue;
13851
+ }
13830
13852
  if (pattern.type === "command_injection") {
13831
13853
  if (call.is_constructor) {
13832
13854
  if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
@@ -19553,6 +19575,238 @@ function applyLibraryApiSurfaceDowngrade(findings) {
19553
19575
  });
19554
19576
  }
19555
19577
 
19578
+ // src/analysis/entry-point-detection.ts
19579
+ var TIER_1_METHOD_ANNOTATIONS = /* @__PURE__ */ new Set([
19580
+ // Spring MVC
19581
+ "RequestMapping",
19582
+ "GetMapping",
19583
+ "PostMapping",
19584
+ "PutMapping",
19585
+ "DeleteMapping",
19586
+ "PatchMapping",
19587
+ // Spring messaging / WebSocket
19588
+ "MessageMapping",
19589
+ "SubscribeMapping",
19590
+ // Spring messaging listeners
19591
+ "KafkaListener",
19592
+ "KafkaHandler",
19593
+ "RabbitListener",
19594
+ "RabbitHandler",
19595
+ "JmsListener",
19596
+ "StreamListener",
19597
+ // Spring Cloud AWS
19598
+ "SqsListener",
19599
+ "SqsHandler",
19600
+ // Spring application events (borderline — inclusive in ship 1)
19601
+ "EventListener",
19602
+ // CRON / scheduled
19603
+ "Scheduled",
19604
+ // JAX-RS
19605
+ "Path",
19606
+ "GET",
19607
+ "POST",
19608
+ "PUT",
19609
+ "DELETE",
19610
+ "PATCH",
19611
+ "HEAD",
19612
+ "OPTIONS",
19613
+ // Jenkins Stapler form-binding (#224 — CVE-2022-20617 docker-commons).
19614
+ // `@DataBoundConstructor` / `@DataBoundSetter` mark the trust boundary
19615
+ // between Jenkins UI (or `config.xml` unmarshal) and the plugin: the
19616
+ // Stapler runtime invokes the annotated constructor / setter with
19617
+ // user-supplied strings from a submitted form or a persisted job
19618
+ // config. Every parameter is a user-supplied taint source.
19619
+ "DataBoundConstructor",
19620
+ "DataBoundSetter"
19621
+ ]);
19622
+ var TIER_1_CLASS_ANNOTATIONS = /* @__PURE__ */ new Set([
19623
+ // Spring MVC
19624
+ "RestController",
19625
+ "Controller",
19626
+ // Spring stereotype beans (#136 — see header)
19627
+ "Service",
19628
+ "Repository",
19629
+ "Component",
19630
+ // JAX-RS resource class
19631
+ "Path",
19632
+ // Servlet 3.0 annotation-based servlet
19633
+ "WebServlet",
19634
+ // JSR-356 WebSocket endpoint
19635
+ "ServerEndpoint",
19636
+ // Declarative HTTP client (borderline — inclusive in ship 1; contract-defined
19637
+ // parameter shapes are downstream-relevant trust boundaries)
19638
+ "FeignClient"
19639
+ ]);
19640
+ var TIER_1_BY_SUPERTYPE = /* @__PURE__ */ new Map([
19641
+ // Servlet API
19642
+ ["HttpServlet", /* @__PURE__ */ new Set(["doGet", "doPost", "doPut", "doDelete", "doHead", "doOptions", "doTrace", "service"])],
19643
+ ["GenericServlet", /* @__PURE__ */ new Set(["service"])],
19644
+ ["Filter", /* @__PURE__ */ new Set(["doFilter"])],
19645
+ // Spring web
19646
+ ["HandlerInterceptor", /* @__PURE__ */ new Set(["preHandle", "postHandle", "afterCompletion"])],
19647
+ ["AsyncHandlerInterceptor", /* @__PURE__ */ new Set(["preHandle", "postHandle", "afterCompletion", "afterConcurrentHandlingStarted"])],
19648
+ // Spring boot CLI entry points
19649
+ ["CommandLineRunner", /* @__PURE__ */ new Set(["run"])],
19650
+ ["ApplicationRunner", /* @__PURE__ */ new Set(["run"])],
19651
+ // Netty channel handlers (#154 — CVE-2022-26884 dolphinscheduler).
19652
+ // `SimpleChannelInboundHandler<T>.channelRead0(ctx, msg)` is the
19653
+ // standard typed read entry; `ChannelInboundHandler.channelRead(ctx,
19654
+ // Object)` is the untyped base. `ChannelInboundHandlerAdapter` and
19655
+ // `ChannelDuplexHandler` are the most-common adapter classes user
19656
+ // handlers extend. `NettyRequestProcessor` is the dolphinscheduler-
19657
+ // specific (and other Netty-RPC projects') wire-message processor
19658
+ // surface — `process(channel, command)` is the network entry point.
19659
+ ["SimpleChannelInboundHandler", /* @__PURE__ */ new Set(["channelRead0", "messageReceived"])],
19660
+ ["ChannelInboundHandler", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
19661
+ ["ChannelInboundHandlerAdapter", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
19662
+ ["ChannelDuplexHandler", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
19663
+ ["NettyRequestProcessor", /* @__PURE__ */ new Set(["process"])],
19664
+ // XStream deserialization converters (#224 — CVE-2020-26217,
19665
+ // CVE-2021-21345). The xstream deserializer invokes `unmarshal`
19666
+ // with attacker-controlled `HierarchicalStreamReader` state whenever
19667
+ // untrusted XML reaches `XStream.fromXML`. Each converter is a
19668
+ // deserialization gadget surface — its `unmarshal` parameters are
19669
+ // network-facing taint sources even though the enclosing class
19670
+ // carries no framework annotation. `marshal` is included for the
19671
+ // symmetric round-trip surface but is rarely exploitable on its
19672
+ // own; the fixup targets `unmarshal` primarily.
19673
+ //
19674
+ // `SingleValueConverter` is the string-form variant (fromString
19675
+ // parses an attacker string; toString is the sink half of the
19676
+ // round-trip). `ConverterMatcher` is the shared base and some
19677
+ // downstream projects subclass it directly.
19678
+ ["Converter", /* @__PURE__ */ new Set(["marshal", "unmarshal"])],
19679
+ ["SingleValueConverter", /* @__PURE__ */ new Set(["fromString", "toString"])],
19680
+ ["ConverterMatcher", /* @__PURE__ */ new Set(["marshal", "unmarshal"])],
19681
+ // XStream abstract base classes — direct-parent `extends` match
19682
+ // covers the common shape where user converters subclass a base
19683
+ // rather than implementing `Converter` directly.
19684
+ ["AbstractReflectionConverter", /* @__PURE__ */ new Set(["marshal", "unmarshal", "doMarshal", "doUnmarshal"])],
19685
+ ["AbstractSingleValueConverter", /* @__PURE__ */ new Set(["fromString", "toString"])],
19686
+ ["AbstractCollectionConverter", /* @__PURE__ */ new Set(["marshal", "unmarshal"])]
19687
+ ]);
19688
+ var TIER_3_CLASS_SUFFIXES = [
19689
+ "Util",
19690
+ "Utils",
19691
+ "Helper",
19692
+ "Helpers"
19693
+ ];
19694
+ var TIER_3_PACKAGE_FRAGMENTS = [
19695
+ ".template.",
19696
+ ".templates.",
19697
+ ".engine.",
19698
+ ".engines."
19699
+ ];
19700
+ var TIER_3_JDK_FACADE_INTERFACES = /* @__PURE__ */ new Set([
19701
+ // Collection root + common containers
19702
+ "Collection",
19703
+ "List",
19704
+ "Set",
19705
+ "Map",
19706
+ "Queue",
19707
+ "Deque",
19708
+ "SortedSet",
19709
+ "SortedMap",
19710
+ "NavigableSet",
19711
+ "NavigableMap",
19712
+ // Iteration / ordering / equality contracts
19713
+ "Iterator",
19714
+ "Iterable",
19715
+ "ListIterator",
19716
+ "Comparator",
19717
+ "Comparable",
19718
+ // Serialization / cloning contracts
19719
+ "Serializable",
19720
+ "Externalizable",
19721
+ "Cloneable"
19722
+ ]);
19723
+ function classNameLooksLikeUtility(name2) {
19724
+ if (!name2) return false;
19725
+ for (const suffix of TIER_3_CLASS_SUFFIXES) {
19726
+ if (name2.length > suffix.length && name2.endsWith(suffix)) return true;
19727
+ }
19728
+ return false;
19729
+ }
19730
+ function packageLooksLikeTemplateOrEngine(pkg) {
19731
+ if (!pkg) return false;
19732
+ const padded = `.${pkg}.`;
19733
+ for (const frag of TIER_3_PACKAGE_FRAGMENTS) {
19734
+ if (padded.includes(frag)) return true;
19735
+ }
19736
+ return false;
19737
+ }
19738
+ function implementsJdkFacade(t) {
19739
+ if (!t) return false;
19740
+ for (const impl of t.implements ?? []) {
19741
+ if (TIER_3_JDK_FACADE_INTERFACES.has(simpleTypeName(impl))) return true;
19742
+ }
19743
+ return false;
19744
+ }
19745
+ function classShapeIsLibraryFacade(enclosingType) {
19746
+ if (!enclosingType) return false;
19747
+ if (classNameLooksLikeUtility(enclosingType.name)) return true;
19748
+ if (packageLooksLikeTemplateOrEngine(enclosingType.package)) return true;
19749
+ if (implementsJdkFacade(enclosingType)) return true;
19750
+ return false;
19751
+ }
19752
+ function annotationsInclude(annotations, targets) {
19753
+ if (!annotations || annotations.length === 0) return false;
19754
+ for (const raw of annotations) {
19755
+ const simple = raw.replace(/^@/, "").replace(/[<(].*$/, "").trim();
19756
+ if (targets.has(simple)) return true;
19757
+ }
19758
+ return false;
19759
+ }
19760
+ function simpleTypeName(ref) {
19761
+ return ref.replace(/<.*$/, "").trim();
19762
+ }
19763
+ function looksLikeMainMethod(method) {
19764
+ if (method.name !== "main") return false;
19765
+ const params = method.parameters ?? [];
19766
+ if (params.length !== 1) return false;
19767
+ const t = (params[0].type ?? "").replace(/\s+/g, "");
19768
+ return t === "String[]" || t === "String..." || t === "java.lang.String[]";
19769
+ }
19770
+ function methodIsSupertypeLifecycleEntryPoint(method, enclosingType) {
19771
+ if (!method.name) return false;
19772
+ if (!enclosingType) return false;
19773
+ const candidates = [];
19774
+ if (enclosingType.extends) candidates.push(simpleTypeName(enclosingType.extends));
19775
+ for (const i2 of enclosingType.implements ?? []) candidates.push(simpleTypeName(i2));
19776
+ for (const supertype of candidates) {
19777
+ const lifecycleMethods = TIER_1_BY_SUPERTYPE.get(supertype);
19778
+ if (lifecycleMethods?.has(method.name)) return true;
19779
+ }
19780
+ return false;
19781
+ }
19782
+ function classifyEntryPointTier(method, enclosingType, ctx) {
19783
+ const language = (ctx.language ?? "").toLowerCase();
19784
+ if (language !== "java") return "TIER_UNKNOWN";
19785
+ if (!method) return "TIER_UNKNOWN";
19786
+ if (classShapeIsLibraryFacade(enclosingType)) {
19787
+ return "TIER_3_LIBRARY_API";
19788
+ }
19789
+ if (annotationsInclude(method.annotations, TIER_1_METHOD_ANNOTATIONS)) {
19790
+ return "TIER_1_ENTRY_POINT";
19791
+ }
19792
+ if (enclosingType && annotationsInclude(enclosingType.annotations, TIER_1_CLASS_ANNOTATIONS)) {
19793
+ return "TIER_1_ENTRY_POINT";
19794
+ }
19795
+ if (methodIsSupertypeLifecycleEntryPoint(method, enclosingType)) {
19796
+ return "TIER_1_ENTRY_POINT";
19797
+ }
19798
+ if (looksLikeMainMethod(method)) {
19799
+ return "TIER_1_ENTRY_POINT";
19800
+ }
19801
+ return "TIER_3_LIBRARY_API";
19802
+ }
19803
+ function shouldGateInterproceduralParam(sourceType, enclosingMethod, enclosingType, ctx) {
19804
+ if (sourceType !== "interprocedural_param") return false;
19805
+ if (!enclosingMethod) return false;
19806
+ const tier = classifyEntryPointTier(enclosingMethod, enclosingType, ctx);
19807
+ return tier === "TIER_3_LIBRARY_API";
19808
+ }
19809
+
19556
19810
  // src/analysis/project-profile-transform.ts
19557
19811
  var DOWNGRADE_ELIGIBLE_RULE_IDS = /* @__PURE__ */ new Set([
19558
19812
  "code_injection",
@@ -30818,7 +31072,7 @@ var REFLECTION_SINK_METHODS = /* @__PURE__ */ new Set([
30818
31072
  "loadClass",
30819
31073
  "defineClass"
30820
31074
  ]);
30821
- var TIER_1_CLASS_ANNOTATIONS = /* @__PURE__ */ new Set([
31075
+ var TIER_1_CLASS_ANNOTATIONS2 = /* @__PURE__ */ new Set([
30822
31076
  // Spring MVC
30823
31077
  "RestController",
30824
31078
  "Controller",
@@ -30835,7 +31089,7 @@ var TIER_1_CLASS_ANNOTATIONS = /* @__PURE__ */ new Set([
30835
31089
  // Declarative HTTP client
30836
31090
  "FeignClient"
30837
31091
  ]);
30838
- var TIER_1_METHOD_ANNOTATIONS = /* @__PURE__ */ new Set([
31092
+ var TIER_1_METHOD_ANNOTATIONS2 = /* @__PURE__ */ new Set([
30839
31093
  // Spring MVC
30840
31094
  "RequestMapping",
30841
31095
  "GetMapping",
@@ -30936,7 +31190,7 @@ var CliMainReflectionSuppressPass = class {
30936
31190
  let hasFrameworkSignal = false;
30937
31191
  for (const type of types) {
30938
31192
  for (const ann of type.annotations) {
30939
- if (TIER_1_CLASS_ANNOTATIONS.has(normalizeAnnotation(ann))) {
31193
+ if (TIER_1_CLASS_ANNOTATIONS2.has(normalizeAnnotation(ann))) {
30940
31194
  hasFrameworkSignal = true;
30941
31195
  break;
30942
31196
  }
@@ -30955,7 +31209,7 @@ var CliMainReflectionSuppressPass = class {
30955
31209
  if (hasFrameworkSignal) break;
30956
31210
  for (const method of type.methods) {
30957
31211
  for (const ann of method.annotations) {
30958
- if (TIER_1_METHOD_ANNOTATIONS.has(normalizeAnnotation(ann))) {
31212
+ if (TIER_1_METHOD_ANNOTATIONS2.has(normalizeAnnotation(ann))) {
30959
31213
  hasFrameworkSignal = true;
30960
31214
  break;
30961
31215
  }
@@ -31835,238 +32089,6 @@ function pickScopedSource(sources, sinkLine, methodName, types, taintedVar) {
31835
32089
  return sources[0];
31836
32090
  }
31837
32091
 
31838
- // src/analysis/entry-point-detection.ts
31839
- var TIER_1_METHOD_ANNOTATIONS2 = /* @__PURE__ */ new Set([
31840
- // Spring MVC
31841
- "RequestMapping",
31842
- "GetMapping",
31843
- "PostMapping",
31844
- "PutMapping",
31845
- "DeleteMapping",
31846
- "PatchMapping",
31847
- // Spring messaging / WebSocket
31848
- "MessageMapping",
31849
- "SubscribeMapping",
31850
- // Spring messaging listeners
31851
- "KafkaListener",
31852
- "KafkaHandler",
31853
- "RabbitListener",
31854
- "RabbitHandler",
31855
- "JmsListener",
31856
- "StreamListener",
31857
- // Spring Cloud AWS
31858
- "SqsListener",
31859
- "SqsHandler",
31860
- // Spring application events (borderline — inclusive in ship 1)
31861
- "EventListener",
31862
- // CRON / scheduled
31863
- "Scheduled",
31864
- // JAX-RS
31865
- "Path",
31866
- "GET",
31867
- "POST",
31868
- "PUT",
31869
- "DELETE",
31870
- "PATCH",
31871
- "HEAD",
31872
- "OPTIONS",
31873
- // Jenkins Stapler form-binding (#224 — CVE-2022-20617 docker-commons).
31874
- // `@DataBoundConstructor` / `@DataBoundSetter` mark the trust boundary
31875
- // between Jenkins UI (or `config.xml` unmarshal) and the plugin: the
31876
- // Stapler runtime invokes the annotated constructor / setter with
31877
- // user-supplied strings from a submitted form or a persisted job
31878
- // config. Every parameter is a user-supplied taint source.
31879
- "DataBoundConstructor",
31880
- "DataBoundSetter"
31881
- ]);
31882
- var TIER_1_CLASS_ANNOTATIONS2 = /* @__PURE__ */ new Set([
31883
- // Spring MVC
31884
- "RestController",
31885
- "Controller",
31886
- // Spring stereotype beans (#136 — see header)
31887
- "Service",
31888
- "Repository",
31889
- "Component",
31890
- // JAX-RS resource class
31891
- "Path",
31892
- // Servlet 3.0 annotation-based servlet
31893
- "WebServlet",
31894
- // JSR-356 WebSocket endpoint
31895
- "ServerEndpoint",
31896
- // Declarative HTTP client (borderline — inclusive in ship 1; contract-defined
31897
- // parameter shapes are downstream-relevant trust boundaries)
31898
- "FeignClient"
31899
- ]);
31900
- var TIER_1_BY_SUPERTYPE = /* @__PURE__ */ new Map([
31901
- // Servlet API
31902
- ["HttpServlet", /* @__PURE__ */ new Set(["doGet", "doPost", "doPut", "doDelete", "doHead", "doOptions", "doTrace", "service"])],
31903
- ["GenericServlet", /* @__PURE__ */ new Set(["service"])],
31904
- ["Filter", /* @__PURE__ */ new Set(["doFilter"])],
31905
- // Spring web
31906
- ["HandlerInterceptor", /* @__PURE__ */ new Set(["preHandle", "postHandle", "afterCompletion"])],
31907
- ["AsyncHandlerInterceptor", /* @__PURE__ */ new Set(["preHandle", "postHandle", "afterCompletion", "afterConcurrentHandlingStarted"])],
31908
- // Spring boot CLI entry points
31909
- ["CommandLineRunner", /* @__PURE__ */ new Set(["run"])],
31910
- ["ApplicationRunner", /* @__PURE__ */ new Set(["run"])],
31911
- // Netty channel handlers (#154 — CVE-2022-26884 dolphinscheduler).
31912
- // `SimpleChannelInboundHandler<T>.channelRead0(ctx, msg)` is the
31913
- // standard typed read entry; `ChannelInboundHandler.channelRead(ctx,
31914
- // Object)` is the untyped base. `ChannelInboundHandlerAdapter` and
31915
- // `ChannelDuplexHandler` are the most-common adapter classes user
31916
- // handlers extend. `NettyRequestProcessor` is the dolphinscheduler-
31917
- // specific (and other Netty-RPC projects') wire-message processor
31918
- // surface — `process(channel, command)` is the network entry point.
31919
- ["SimpleChannelInboundHandler", /* @__PURE__ */ new Set(["channelRead0", "messageReceived"])],
31920
- ["ChannelInboundHandler", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
31921
- ["ChannelInboundHandlerAdapter", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
31922
- ["ChannelDuplexHandler", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
31923
- ["NettyRequestProcessor", /* @__PURE__ */ new Set(["process"])],
31924
- // XStream deserialization converters (#224 — CVE-2020-26217,
31925
- // CVE-2021-21345). The xstream deserializer invokes `unmarshal`
31926
- // with attacker-controlled `HierarchicalStreamReader` state whenever
31927
- // untrusted XML reaches `XStream.fromXML`. Each converter is a
31928
- // deserialization gadget surface — its `unmarshal` parameters are
31929
- // network-facing taint sources even though the enclosing class
31930
- // carries no framework annotation. `marshal` is included for the
31931
- // symmetric round-trip surface but is rarely exploitable on its
31932
- // own; the fixup targets `unmarshal` primarily.
31933
- //
31934
- // `SingleValueConverter` is the string-form variant (fromString
31935
- // parses an attacker string; toString is the sink half of the
31936
- // round-trip). `ConverterMatcher` is the shared base and some
31937
- // downstream projects subclass it directly.
31938
- ["Converter", /* @__PURE__ */ new Set(["marshal", "unmarshal"])],
31939
- ["SingleValueConverter", /* @__PURE__ */ new Set(["fromString", "toString"])],
31940
- ["ConverterMatcher", /* @__PURE__ */ new Set(["marshal", "unmarshal"])],
31941
- // XStream abstract base classes — direct-parent `extends` match
31942
- // covers the common shape where user converters subclass a base
31943
- // rather than implementing `Converter` directly.
31944
- ["AbstractReflectionConverter", /* @__PURE__ */ new Set(["marshal", "unmarshal", "doMarshal", "doUnmarshal"])],
31945
- ["AbstractSingleValueConverter", /* @__PURE__ */ new Set(["fromString", "toString"])],
31946
- ["AbstractCollectionConverter", /* @__PURE__ */ new Set(["marshal", "unmarshal"])]
31947
- ]);
31948
- var TIER_3_CLASS_SUFFIXES = [
31949
- "Util",
31950
- "Utils",
31951
- "Helper",
31952
- "Helpers"
31953
- ];
31954
- var TIER_3_PACKAGE_FRAGMENTS = [
31955
- ".template.",
31956
- ".templates.",
31957
- ".engine.",
31958
- ".engines."
31959
- ];
31960
- var TIER_3_JDK_FACADE_INTERFACES = /* @__PURE__ */ new Set([
31961
- // Collection root + common containers
31962
- "Collection",
31963
- "List",
31964
- "Set",
31965
- "Map",
31966
- "Queue",
31967
- "Deque",
31968
- "SortedSet",
31969
- "SortedMap",
31970
- "NavigableSet",
31971
- "NavigableMap",
31972
- // Iteration / ordering / equality contracts
31973
- "Iterator",
31974
- "Iterable",
31975
- "ListIterator",
31976
- "Comparator",
31977
- "Comparable",
31978
- // Serialization / cloning contracts
31979
- "Serializable",
31980
- "Externalizable",
31981
- "Cloneable"
31982
- ]);
31983
- function classNameLooksLikeUtility(name2) {
31984
- if (!name2) return false;
31985
- for (const suffix of TIER_3_CLASS_SUFFIXES) {
31986
- if (name2.length > suffix.length && name2.endsWith(suffix)) return true;
31987
- }
31988
- return false;
31989
- }
31990
- function packageLooksLikeTemplateOrEngine(pkg) {
31991
- if (!pkg) return false;
31992
- const padded = `.${pkg}.`;
31993
- for (const frag of TIER_3_PACKAGE_FRAGMENTS) {
31994
- if (padded.includes(frag)) return true;
31995
- }
31996
- return false;
31997
- }
31998
- function implementsJdkFacade(t) {
31999
- if (!t) return false;
32000
- for (const impl of t.implements ?? []) {
32001
- if (TIER_3_JDK_FACADE_INTERFACES.has(simpleTypeName(impl))) return true;
32002
- }
32003
- return false;
32004
- }
32005
- function classShapeIsLibraryFacade(enclosingType) {
32006
- if (!enclosingType) return false;
32007
- if (classNameLooksLikeUtility(enclosingType.name)) return true;
32008
- if (packageLooksLikeTemplateOrEngine(enclosingType.package)) return true;
32009
- if (implementsJdkFacade(enclosingType)) return true;
32010
- return false;
32011
- }
32012
- function annotationsInclude(annotations, targets) {
32013
- if (!annotations || annotations.length === 0) return false;
32014
- for (const raw of annotations) {
32015
- const simple = raw.replace(/^@/, "").replace(/[<(].*$/, "").trim();
32016
- if (targets.has(simple)) return true;
32017
- }
32018
- return false;
32019
- }
32020
- function simpleTypeName(ref) {
32021
- return ref.replace(/<.*$/, "").trim();
32022
- }
32023
- function looksLikeMainMethod(method) {
32024
- if (method.name !== "main") return false;
32025
- const params = method.parameters ?? [];
32026
- if (params.length !== 1) return false;
32027
- const t = (params[0].type ?? "").replace(/\s+/g, "");
32028
- return t === "String[]" || t === "String..." || t === "java.lang.String[]";
32029
- }
32030
- function methodIsSupertypeLifecycleEntryPoint(method, enclosingType) {
32031
- if (!method.name) return false;
32032
- if (!enclosingType) return false;
32033
- const candidates = [];
32034
- if (enclosingType.extends) candidates.push(simpleTypeName(enclosingType.extends));
32035
- for (const i2 of enclosingType.implements ?? []) candidates.push(simpleTypeName(i2));
32036
- for (const supertype of candidates) {
32037
- const lifecycleMethods = TIER_1_BY_SUPERTYPE.get(supertype);
32038
- if (lifecycleMethods?.has(method.name)) return true;
32039
- }
32040
- return false;
32041
- }
32042
- function classifyEntryPointTier(method, enclosingType, ctx) {
32043
- const language = (ctx.language ?? "").toLowerCase();
32044
- if (language !== "java") return "TIER_UNKNOWN";
32045
- if (!method) return "TIER_UNKNOWN";
32046
- if (classShapeIsLibraryFacade(enclosingType)) {
32047
- return "TIER_3_LIBRARY_API";
32048
- }
32049
- if (annotationsInclude(method.annotations, TIER_1_METHOD_ANNOTATIONS2)) {
32050
- return "TIER_1_ENTRY_POINT";
32051
- }
32052
- if (enclosingType && annotationsInclude(enclosingType.annotations, TIER_1_CLASS_ANNOTATIONS2)) {
32053
- return "TIER_1_ENTRY_POINT";
32054
- }
32055
- if (methodIsSupertypeLifecycleEntryPoint(method, enclosingType)) {
32056
- return "TIER_1_ENTRY_POINT";
32057
- }
32058
- if (looksLikeMainMethod(method)) {
32059
- return "TIER_1_ENTRY_POINT";
32060
- }
32061
- return "TIER_3_LIBRARY_API";
32062
- }
32063
- function shouldGateInterproceduralParam(sourceType, enclosingMethod, enclosingType, ctx) {
32064
- if (sourceType !== "interprocedural_param") return false;
32065
- if (!enclosingMethod) return false;
32066
- const tier = classifyEntryPointTier(enclosingMethod, enclosingType, ctx);
32067
- return tier === "TIER_3_LIBRARY_API";
32068
- }
32069
-
32070
32092
  // src/analysis/passes/interprocedural-pass.ts
32071
32093
  var InterproceduralPass = class {
32072
32094
  name = "interprocedural";
@@ -10681,11 +10681,11 @@ var DEFAULT_SINKS = [
10681
10681
  { method: "FileOutputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10682
10682
  { method: "FileReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10683
10683
  { method: "FileWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10684
- // ClassLoader/Class resource loading (can be abused for path traversal)
10685
- { method: "getResource", class: "ClassLoader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10686
- { method: "getResourceAsStream", class: "ClassLoader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10687
- { method: "getResource", class: "Class", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10688
- { method: "getResourceAsStream", class: "Class", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10684
+ // NOTE: ClassLoader.getResource / Class.getResource were removed in
10685
+ // 3.153.0 (#233). Classpath resource resolution cannot escape the
10686
+ // classpath root via `../` (JAR entries are opaque). If reintroduced,
10687
+ // the correct CWE is CWE-829 (untrusted-classpath-resource), not
10688
+ // CWE-22 path traversal.
10689
10689
  // Paths.get can be used for path traversal
10690
10690
  { method: "get", class: "Paths", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10691
10691
  { method: "of", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11188,6 +11188,15 @@ var DEFAULT_SINKS = [
11188
11188
  { method: "parseObject", class: "JSON", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11189
11189
  { method: "parseObject", class: "JSONObject", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11190
11190
  { method: "fromJson", class: "Gson", type: "deserialization", cwe: "CWE-502", severity: "medium", arg_positions: [0], safe_if_class_literal_at: 1 },
11191
+ // Jackson ObjectReader — pre-configured reader; typed 2-arg overload is safe
11192
+ // when arg[1] is a class literal or TypeReference<>()/TypeToken<>() (handled
11193
+ // by argIsClassLiteral extension). Added 3.153.0 (cognium-dev #233).
11194
+ { method: "readValue", class: "ObjectReader", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11195
+ // Jackson ObjectMapper.convertValue — same class-literal shape as readValue.
11196
+ { method: "convertValue", class: "ObjectMapper", type: "deserialization", cwe: "CWE-502", severity: "medium", arg_positions: [0], safe_if_class_literal_at: 1 },
11197
+ // Kryo.readObject(input, User.class) — typed form is safe; polymorphic form
11198
+ // (Kryo.readClassAndObject or non-literal type) remains a sink.
11199
+ { method: "readObject", class: "Kryo", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], safe_if_class_literal_at: 1 },
11191
11200
  // XMLDecoder
11192
11201
  { method: "readObject", class: "XMLDecoder", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
11193
11202
  // Java serialization constructors
@@ -12960,7 +12969,17 @@ function argIsClassLiteral(call, position) {
12960
12969
  if (!arg) return false;
12961
12970
  const expr = (arg.literal ?? arg.expression ?? "").trim();
12962
12971
  if (!expr) return false;
12963
- return CLASS_LITERAL_RE.test(expr);
12972
+ if (CLASS_LITERAL_RE.test(expr)) return true;
12973
+ return TYPE_TOKEN_RE.test(expr);
12974
+ }
12975
+ var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
12976
+ function argIsStringLiteral(call, position) {
12977
+ const arg = call.arguments.find((a) => a.position === position);
12978
+ if (!arg) return false;
12979
+ if (arg.literal !== void 0 && arg.literal !== null && arg.literal !== "") return true;
12980
+ const expr = (arg.expression ?? "").trim();
12981
+ if (!expr) return false;
12982
+ return expr.startsWith('"') && expr.endsWith('"') && expr.length >= 2 || expr.startsWith("'") && expr.endsWith("'") && expr.length >= 2 || expr.startsWith('"""') && expr.endsWith('"""');
12964
12983
  }
12965
12984
  var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
12966
12985
  // java.lang.*
@@ -13135,6 +13154,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
13135
13154
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
13136
13155
  continue;
13137
13156
  }
13157
+ if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
13158
+ continue;
13159
+ }
13138
13160
  if (pattern.type === "command_injection") {
13139
13161
  if (call.is_constructor) {
13140
13162
  if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {