circle-ir 3.151.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.
- package/configs/sink-semantics.json +102 -0
- package/configs/sinks/command.yaml +0 -10
- package/configs/sinks/deserialization.yaml +54 -0
- package/configs/sinks/path.yaml +0 -94
- package/configs/sinks/sql.yaml +24 -24
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +14 -5
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/library-profile-sink-gate-pass.d.ts +89 -0
- package/dist/analysis/passes/library-profile-sink-gate-pass.d.ts.map +1 -0
- package/dist/analysis/passes/library-profile-sink-gate-pass.js +132 -0
- package/dist/analysis/passes/library-profile-sink-gate-pass.js.map +1 -0
- package/dist/analysis/require-entry-path.d.ts +91 -0
- package/dist/analysis/require-entry-path.d.ts.map +1 -0
- package/dist/analysis/require-entry-path.js +387 -0
- package/dist/analysis/require-entry-path.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +55 -1
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +23 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +331 -253
- package/dist/core/circle-ir-core.cjs +28 -6
- package/dist/core/circle-ir-core.js +28 -6
- package/dist/types/config.d.ts +11 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +25 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
31212
|
+
if (TIER_1_METHOD_ANNOTATIONS2.has(normalizeAnnotation(ann))) {
|
|
30959
31213
|
hasFrameworkSignal = true;
|
|
30960
31214
|
break;
|
|
30961
31215
|
}
|
|
@@ -30991,20 +31245,74 @@ var CliMainReflectionSuppressPass = class {
|
|
|
30991
31245
|
}
|
|
30992
31246
|
};
|
|
30993
31247
|
|
|
30994
|
-
// src/analysis/passes/
|
|
30995
|
-
var
|
|
30996
|
-
|
|
31248
|
+
// src/analysis/passes/library-profile-sink-gate-pass.ts
|
|
31249
|
+
var DROPPED_SINK_TYPES = /* @__PURE__ */ new Set([
|
|
31250
|
+
"log_injection"
|
|
31251
|
+
]);
|
|
31252
|
+
function isLibraryShape2(profile) {
|
|
31253
|
+
if (!profile || profile === "unknown") return false;
|
|
31254
|
+
return profile.startsWith("library/");
|
|
31255
|
+
}
|
|
31256
|
+
var LibraryProfileSinkGatePass = class {
|
|
31257
|
+
name = "library-profile-sink-gate";
|
|
30997
31258
|
category = "security";
|
|
30998
31259
|
run(ctx) {
|
|
30999
31260
|
const { graph } = ctx;
|
|
31000
|
-
const
|
|
31001
|
-
|
|
31002
|
-
|
|
31003
|
-
|
|
31004
|
-
|
|
31005
|
-
|
|
31006
|
-
|
|
31007
|
-
|
|
31261
|
+
const profile = graph.ir.meta.projectProfile;
|
|
31262
|
+
if (!isLibraryShape2(profile)) {
|
|
31263
|
+
return {
|
|
31264
|
+
profile,
|
|
31265
|
+
applied: false,
|
|
31266
|
+
dropped: 0,
|
|
31267
|
+
droppedByType: {}
|
|
31268
|
+
};
|
|
31269
|
+
}
|
|
31270
|
+
const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
|
|
31271
|
+
if (sinks.length === 0) {
|
|
31272
|
+
return {
|
|
31273
|
+
profile,
|
|
31274
|
+
applied: true,
|
|
31275
|
+
dropped: 0,
|
|
31276
|
+
droppedByType: {}
|
|
31277
|
+
};
|
|
31278
|
+
}
|
|
31279
|
+
const droppedByType = {};
|
|
31280
|
+
const kept = [];
|
|
31281
|
+
for (const sink of sinks) {
|
|
31282
|
+
if (DROPPED_SINK_TYPES.has(sink.type)) {
|
|
31283
|
+
droppedByType[sink.type] = (droppedByType[sink.type] ?? 0) + 1;
|
|
31284
|
+
continue;
|
|
31285
|
+
}
|
|
31286
|
+
kept.push(sink);
|
|
31287
|
+
}
|
|
31288
|
+
const dropped = sinks.length - kept.length;
|
|
31289
|
+
if (dropped > 0) {
|
|
31290
|
+
sinks.length = 0;
|
|
31291
|
+
sinks.push(...kept);
|
|
31292
|
+
}
|
|
31293
|
+
return {
|
|
31294
|
+
profile,
|
|
31295
|
+
applied: true,
|
|
31296
|
+
dropped,
|
|
31297
|
+
droppedByType
|
|
31298
|
+
};
|
|
31299
|
+
}
|
|
31300
|
+
};
|
|
31301
|
+
|
|
31302
|
+
// src/analysis/passes/taint-propagation-pass.ts
|
|
31303
|
+
var TaintPropagationPass = class {
|
|
31304
|
+
name = "taint-propagation";
|
|
31305
|
+
category = "security";
|
|
31306
|
+
run(ctx) {
|
|
31307
|
+
const { graph } = ctx;
|
|
31308
|
+
const { calls, types } = graph.ir;
|
|
31309
|
+
const constProp = ctx.getResult("constant-propagation");
|
|
31310
|
+
const sinkFilter = ctx.getResult("sink-filter");
|
|
31311
|
+
const { sources, sinks, sanitizers } = sinkFilter;
|
|
31312
|
+
if (sinks.length === 0) {
|
|
31313
|
+
return { flows: [] };
|
|
31314
|
+
}
|
|
31315
|
+
const canSynthesize = ctx.language === "python" && typeof ctx.code === "string";
|
|
31008
31316
|
if (sources.length === 0 && !canSynthesize) {
|
|
31009
31317
|
return { flows: [] };
|
|
31010
31318
|
}
|
|
@@ -31781,238 +32089,6 @@ function pickScopedSource(sources, sinkLine, methodName, types, taintedVar) {
|
|
|
31781
32089
|
return sources[0];
|
|
31782
32090
|
}
|
|
31783
32091
|
|
|
31784
|
-
// src/analysis/entry-point-detection.ts
|
|
31785
|
-
var TIER_1_METHOD_ANNOTATIONS2 = /* @__PURE__ */ new Set([
|
|
31786
|
-
// Spring MVC
|
|
31787
|
-
"RequestMapping",
|
|
31788
|
-
"GetMapping",
|
|
31789
|
-
"PostMapping",
|
|
31790
|
-
"PutMapping",
|
|
31791
|
-
"DeleteMapping",
|
|
31792
|
-
"PatchMapping",
|
|
31793
|
-
// Spring messaging / WebSocket
|
|
31794
|
-
"MessageMapping",
|
|
31795
|
-
"SubscribeMapping",
|
|
31796
|
-
// Spring messaging listeners
|
|
31797
|
-
"KafkaListener",
|
|
31798
|
-
"KafkaHandler",
|
|
31799
|
-
"RabbitListener",
|
|
31800
|
-
"RabbitHandler",
|
|
31801
|
-
"JmsListener",
|
|
31802
|
-
"StreamListener",
|
|
31803
|
-
// Spring Cloud AWS
|
|
31804
|
-
"SqsListener",
|
|
31805
|
-
"SqsHandler",
|
|
31806
|
-
// Spring application events (borderline — inclusive in ship 1)
|
|
31807
|
-
"EventListener",
|
|
31808
|
-
// CRON / scheduled
|
|
31809
|
-
"Scheduled",
|
|
31810
|
-
// JAX-RS
|
|
31811
|
-
"Path",
|
|
31812
|
-
"GET",
|
|
31813
|
-
"POST",
|
|
31814
|
-
"PUT",
|
|
31815
|
-
"DELETE",
|
|
31816
|
-
"PATCH",
|
|
31817
|
-
"HEAD",
|
|
31818
|
-
"OPTIONS",
|
|
31819
|
-
// Jenkins Stapler form-binding (#224 — CVE-2022-20617 docker-commons).
|
|
31820
|
-
// `@DataBoundConstructor` / `@DataBoundSetter` mark the trust boundary
|
|
31821
|
-
// between Jenkins UI (or `config.xml` unmarshal) and the plugin: the
|
|
31822
|
-
// Stapler runtime invokes the annotated constructor / setter with
|
|
31823
|
-
// user-supplied strings from a submitted form or a persisted job
|
|
31824
|
-
// config. Every parameter is a user-supplied taint source.
|
|
31825
|
-
"DataBoundConstructor",
|
|
31826
|
-
"DataBoundSetter"
|
|
31827
|
-
]);
|
|
31828
|
-
var TIER_1_CLASS_ANNOTATIONS2 = /* @__PURE__ */ new Set([
|
|
31829
|
-
// Spring MVC
|
|
31830
|
-
"RestController",
|
|
31831
|
-
"Controller",
|
|
31832
|
-
// Spring stereotype beans (#136 — see header)
|
|
31833
|
-
"Service",
|
|
31834
|
-
"Repository",
|
|
31835
|
-
"Component",
|
|
31836
|
-
// JAX-RS resource class
|
|
31837
|
-
"Path",
|
|
31838
|
-
// Servlet 3.0 annotation-based servlet
|
|
31839
|
-
"WebServlet",
|
|
31840
|
-
// JSR-356 WebSocket endpoint
|
|
31841
|
-
"ServerEndpoint",
|
|
31842
|
-
// Declarative HTTP client (borderline — inclusive in ship 1; contract-defined
|
|
31843
|
-
// parameter shapes are downstream-relevant trust boundaries)
|
|
31844
|
-
"FeignClient"
|
|
31845
|
-
]);
|
|
31846
|
-
var TIER_1_BY_SUPERTYPE = /* @__PURE__ */ new Map([
|
|
31847
|
-
// Servlet API
|
|
31848
|
-
["HttpServlet", /* @__PURE__ */ new Set(["doGet", "doPost", "doPut", "doDelete", "doHead", "doOptions", "doTrace", "service"])],
|
|
31849
|
-
["GenericServlet", /* @__PURE__ */ new Set(["service"])],
|
|
31850
|
-
["Filter", /* @__PURE__ */ new Set(["doFilter"])],
|
|
31851
|
-
// Spring web
|
|
31852
|
-
["HandlerInterceptor", /* @__PURE__ */ new Set(["preHandle", "postHandle", "afterCompletion"])],
|
|
31853
|
-
["AsyncHandlerInterceptor", /* @__PURE__ */ new Set(["preHandle", "postHandle", "afterCompletion", "afterConcurrentHandlingStarted"])],
|
|
31854
|
-
// Spring boot CLI entry points
|
|
31855
|
-
["CommandLineRunner", /* @__PURE__ */ new Set(["run"])],
|
|
31856
|
-
["ApplicationRunner", /* @__PURE__ */ new Set(["run"])],
|
|
31857
|
-
// Netty channel handlers (#154 — CVE-2022-26884 dolphinscheduler).
|
|
31858
|
-
// `SimpleChannelInboundHandler<T>.channelRead0(ctx, msg)` is the
|
|
31859
|
-
// standard typed read entry; `ChannelInboundHandler.channelRead(ctx,
|
|
31860
|
-
// Object)` is the untyped base. `ChannelInboundHandlerAdapter` and
|
|
31861
|
-
// `ChannelDuplexHandler` are the most-common adapter classes user
|
|
31862
|
-
// handlers extend. `NettyRequestProcessor` is the dolphinscheduler-
|
|
31863
|
-
// specific (and other Netty-RPC projects') wire-message processor
|
|
31864
|
-
// surface — `process(channel, command)` is the network entry point.
|
|
31865
|
-
["SimpleChannelInboundHandler", /* @__PURE__ */ new Set(["channelRead0", "messageReceived"])],
|
|
31866
|
-
["ChannelInboundHandler", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
|
|
31867
|
-
["ChannelInboundHandlerAdapter", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
|
|
31868
|
-
["ChannelDuplexHandler", /* @__PURE__ */ new Set(["channelRead", "channelReadComplete"])],
|
|
31869
|
-
["NettyRequestProcessor", /* @__PURE__ */ new Set(["process"])],
|
|
31870
|
-
// XStream deserialization converters (#224 — CVE-2020-26217,
|
|
31871
|
-
// CVE-2021-21345). The xstream deserializer invokes `unmarshal`
|
|
31872
|
-
// with attacker-controlled `HierarchicalStreamReader` state whenever
|
|
31873
|
-
// untrusted XML reaches `XStream.fromXML`. Each converter is a
|
|
31874
|
-
// deserialization gadget surface — its `unmarshal` parameters are
|
|
31875
|
-
// network-facing taint sources even though the enclosing class
|
|
31876
|
-
// carries no framework annotation. `marshal` is included for the
|
|
31877
|
-
// symmetric round-trip surface but is rarely exploitable on its
|
|
31878
|
-
// own; the fixup targets `unmarshal` primarily.
|
|
31879
|
-
//
|
|
31880
|
-
// `SingleValueConverter` is the string-form variant (fromString
|
|
31881
|
-
// parses an attacker string; toString is the sink half of the
|
|
31882
|
-
// round-trip). `ConverterMatcher` is the shared base and some
|
|
31883
|
-
// downstream projects subclass it directly.
|
|
31884
|
-
["Converter", /* @__PURE__ */ new Set(["marshal", "unmarshal"])],
|
|
31885
|
-
["SingleValueConverter", /* @__PURE__ */ new Set(["fromString", "toString"])],
|
|
31886
|
-
["ConverterMatcher", /* @__PURE__ */ new Set(["marshal", "unmarshal"])],
|
|
31887
|
-
// XStream abstract base classes — direct-parent `extends` match
|
|
31888
|
-
// covers the common shape where user converters subclass a base
|
|
31889
|
-
// rather than implementing `Converter` directly.
|
|
31890
|
-
["AbstractReflectionConverter", /* @__PURE__ */ new Set(["marshal", "unmarshal", "doMarshal", "doUnmarshal"])],
|
|
31891
|
-
["AbstractSingleValueConverter", /* @__PURE__ */ new Set(["fromString", "toString"])],
|
|
31892
|
-
["AbstractCollectionConverter", /* @__PURE__ */ new Set(["marshal", "unmarshal"])]
|
|
31893
|
-
]);
|
|
31894
|
-
var TIER_3_CLASS_SUFFIXES = [
|
|
31895
|
-
"Util",
|
|
31896
|
-
"Utils",
|
|
31897
|
-
"Helper",
|
|
31898
|
-
"Helpers"
|
|
31899
|
-
];
|
|
31900
|
-
var TIER_3_PACKAGE_FRAGMENTS = [
|
|
31901
|
-
".template.",
|
|
31902
|
-
".templates.",
|
|
31903
|
-
".engine.",
|
|
31904
|
-
".engines."
|
|
31905
|
-
];
|
|
31906
|
-
var TIER_3_JDK_FACADE_INTERFACES = /* @__PURE__ */ new Set([
|
|
31907
|
-
// Collection root + common containers
|
|
31908
|
-
"Collection",
|
|
31909
|
-
"List",
|
|
31910
|
-
"Set",
|
|
31911
|
-
"Map",
|
|
31912
|
-
"Queue",
|
|
31913
|
-
"Deque",
|
|
31914
|
-
"SortedSet",
|
|
31915
|
-
"SortedMap",
|
|
31916
|
-
"NavigableSet",
|
|
31917
|
-
"NavigableMap",
|
|
31918
|
-
// Iteration / ordering / equality contracts
|
|
31919
|
-
"Iterator",
|
|
31920
|
-
"Iterable",
|
|
31921
|
-
"ListIterator",
|
|
31922
|
-
"Comparator",
|
|
31923
|
-
"Comparable",
|
|
31924
|
-
// Serialization / cloning contracts
|
|
31925
|
-
"Serializable",
|
|
31926
|
-
"Externalizable",
|
|
31927
|
-
"Cloneable"
|
|
31928
|
-
]);
|
|
31929
|
-
function classNameLooksLikeUtility(name2) {
|
|
31930
|
-
if (!name2) return false;
|
|
31931
|
-
for (const suffix of TIER_3_CLASS_SUFFIXES) {
|
|
31932
|
-
if (name2.length > suffix.length && name2.endsWith(suffix)) return true;
|
|
31933
|
-
}
|
|
31934
|
-
return false;
|
|
31935
|
-
}
|
|
31936
|
-
function packageLooksLikeTemplateOrEngine(pkg) {
|
|
31937
|
-
if (!pkg) return false;
|
|
31938
|
-
const padded = `.${pkg}.`;
|
|
31939
|
-
for (const frag of TIER_3_PACKAGE_FRAGMENTS) {
|
|
31940
|
-
if (padded.includes(frag)) return true;
|
|
31941
|
-
}
|
|
31942
|
-
return false;
|
|
31943
|
-
}
|
|
31944
|
-
function implementsJdkFacade(t) {
|
|
31945
|
-
if (!t) return false;
|
|
31946
|
-
for (const impl of t.implements ?? []) {
|
|
31947
|
-
if (TIER_3_JDK_FACADE_INTERFACES.has(simpleTypeName(impl))) return true;
|
|
31948
|
-
}
|
|
31949
|
-
return false;
|
|
31950
|
-
}
|
|
31951
|
-
function classShapeIsLibraryFacade(enclosingType) {
|
|
31952
|
-
if (!enclosingType) return false;
|
|
31953
|
-
if (classNameLooksLikeUtility(enclosingType.name)) return true;
|
|
31954
|
-
if (packageLooksLikeTemplateOrEngine(enclosingType.package)) return true;
|
|
31955
|
-
if (implementsJdkFacade(enclosingType)) return true;
|
|
31956
|
-
return false;
|
|
31957
|
-
}
|
|
31958
|
-
function annotationsInclude(annotations, targets) {
|
|
31959
|
-
if (!annotations || annotations.length === 0) return false;
|
|
31960
|
-
for (const raw of annotations) {
|
|
31961
|
-
const simple = raw.replace(/^@/, "").replace(/[<(].*$/, "").trim();
|
|
31962
|
-
if (targets.has(simple)) return true;
|
|
31963
|
-
}
|
|
31964
|
-
return false;
|
|
31965
|
-
}
|
|
31966
|
-
function simpleTypeName(ref) {
|
|
31967
|
-
return ref.replace(/<.*$/, "").trim();
|
|
31968
|
-
}
|
|
31969
|
-
function looksLikeMainMethod(method) {
|
|
31970
|
-
if (method.name !== "main") return false;
|
|
31971
|
-
const params = method.parameters ?? [];
|
|
31972
|
-
if (params.length !== 1) return false;
|
|
31973
|
-
const t = (params[0].type ?? "").replace(/\s+/g, "");
|
|
31974
|
-
return t === "String[]" || t === "String..." || t === "java.lang.String[]";
|
|
31975
|
-
}
|
|
31976
|
-
function methodIsSupertypeLifecycleEntryPoint(method, enclosingType) {
|
|
31977
|
-
if (!method.name) return false;
|
|
31978
|
-
if (!enclosingType) return false;
|
|
31979
|
-
const candidates = [];
|
|
31980
|
-
if (enclosingType.extends) candidates.push(simpleTypeName(enclosingType.extends));
|
|
31981
|
-
for (const i2 of enclosingType.implements ?? []) candidates.push(simpleTypeName(i2));
|
|
31982
|
-
for (const supertype of candidates) {
|
|
31983
|
-
const lifecycleMethods = TIER_1_BY_SUPERTYPE.get(supertype);
|
|
31984
|
-
if (lifecycleMethods?.has(method.name)) return true;
|
|
31985
|
-
}
|
|
31986
|
-
return false;
|
|
31987
|
-
}
|
|
31988
|
-
function classifyEntryPointTier(method, enclosingType, ctx) {
|
|
31989
|
-
const language = (ctx.language ?? "").toLowerCase();
|
|
31990
|
-
if (language !== "java") return "TIER_UNKNOWN";
|
|
31991
|
-
if (!method) return "TIER_UNKNOWN";
|
|
31992
|
-
if (classShapeIsLibraryFacade(enclosingType)) {
|
|
31993
|
-
return "TIER_3_LIBRARY_API";
|
|
31994
|
-
}
|
|
31995
|
-
if (annotationsInclude(method.annotations, TIER_1_METHOD_ANNOTATIONS2)) {
|
|
31996
|
-
return "TIER_1_ENTRY_POINT";
|
|
31997
|
-
}
|
|
31998
|
-
if (enclosingType && annotationsInclude(enclosingType.annotations, TIER_1_CLASS_ANNOTATIONS2)) {
|
|
31999
|
-
return "TIER_1_ENTRY_POINT";
|
|
32000
|
-
}
|
|
32001
|
-
if (methodIsSupertypeLifecycleEntryPoint(method, enclosingType)) {
|
|
32002
|
-
return "TIER_1_ENTRY_POINT";
|
|
32003
|
-
}
|
|
32004
|
-
if (looksLikeMainMethod(method)) {
|
|
32005
|
-
return "TIER_1_ENTRY_POINT";
|
|
32006
|
-
}
|
|
32007
|
-
return "TIER_3_LIBRARY_API";
|
|
32008
|
-
}
|
|
32009
|
-
function shouldGateInterproceduralParam(sourceType, enclosingMethod, enclosingType, ctx) {
|
|
32010
|
-
if (sourceType !== "interprocedural_param") return false;
|
|
32011
|
-
if (!enclosingMethod) return false;
|
|
32012
|
-
const tier = classifyEntryPointTier(enclosingMethod, enclosingType, ctx);
|
|
32013
|
-
return tier === "TIER_3_LIBRARY_API";
|
|
32014
|
-
}
|
|
32015
|
-
|
|
32016
32092
|
// src/analysis/passes/interprocedural-pass.ts
|
|
32017
32093
|
var InterproceduralPass = class {
|
|
32018
32094
|
name = "interprocedural";
|
|
@@ -40687,6 +40763,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
40687
40763
|
if (!disabledPasses.has("sink-semantics")) pipeline.add(new SinkSemanticsPass());
|
|
40688
40764
|
if (!disabledPasses.has("cli-main-reflection-suppress"))
|
|
40689
40765
|
pipeline.add(new CliMainReflectionSuppressPass());
|
|
40766
|
+
if (!disabledPasses.has("library-profile-sink-gate"))
|
|
40767
|
+
pipeline.add(new LibraryProfileSinkGatePass());
|
|
40690
40768
|
pipeline.add(new TaintPropagationPass());
|
|
40691
40769
|
pipeline.add(new InterproceduralPass({
|
|
40692
40770
|
enableEntryPointGate: options.enableEntryPointGate ?? true
|