@@ -68,15 +68,39 @@ public final class DefaultApiClient implements ApiClient {
6868
6969 private static final ObjectMapper MULTIPART_MAPPER = ObjectSerializer .createDefaultObjectMapper ();
7070
71+ /*
72+ * Intentional all-trusting trust manager. Installed ONLY when the caller
73+ * explicitly opts out of TLS verification via TransportOptions#verifySsl
74+ * (false) — the documented curl -k equivalent used against local/dev
75+ * Zitadel stacks with self-signed certificates. SpotBugs/find-sec-bugs
76+ * correctly flags this as a MITM risk in general, but here it is a
77+ * deliberate, opt-in feature mirrored across all 12 SDKs, so the warning
78+ * is suppressed on the empty trust-check methods rather than removed.
79+ */
7180 private static final X509TrustManager TRUST_ALL_MANAGER =
7281 new X509TrustManager () {
7382 @ Override
83+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
84+ value = "WEAK_TRUST_MANAGER" ,
85+ justification =
86+ "Opt-in insecure TLS (verifySsl=false): trust-all is a"
87+ + " documented curl -k equivalent for dev stacks." )
7488 public void checkClientTrusted (X509Certificate [] chain , String authType ) {}
7589
7690 @ Override
91+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
92+ value = "WEAK_TRUST_MANAGER" ,
93+ justification =
94+ "Opt-in insecure TLS (verifySsl=false): trust-all is a"
95+ + " documented curl -k equivalent for dev stacks." )
7796 public void checkServerTrusted (X509Certificate [] chain , String authType ) {}
7897
7998 @ Override
99+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
100+ value = "WEAK_TRUST_MANAGER" ,
101+ justification =
102+ "Opt-in insecure TLS (verifySsl=false): trust-all is a"
103+ + " documented curl -k equivalent for dev stacks." )
80104 public X509Certificate [] getAcceptedIssuers () {
81105 return new X509Certificate [0 ];
82106 }
@@ -225,6 +249,12 @@ private static HttpClient buildHttpClient(TransportOptions transportOptions) {
225249 * @return an {@link SSLContext} trusting only the given CA certificate
226250 * @throws ApiException if the certificate cannot be read or parsed
227251 */
252+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
253+ value = "PATH_TRAVERSAL_IN" ,
254+ justification =
255+ "The CA certificate path is supplied by the SDK caller via"
256+ + " TransportOptions#caCertPath as a deliberate TLS-pinning"
257+ + " opt-in, not by remote/untrusted input." )
228258 private static SSLContext buildPinnedSslContext (String caCertPath ) {
229259 try {
230260 CertificateFactory cf = CertificateFactory .getInstance ("X.509" );
@@ -352,6 +382,12 @@ private HttpResponse<byte[]> sendWithRetry(HttpRequest request)
352382 * and the first 3xx is surfaced to the caller as-is.
353383 */
354384 @ Override
385+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
386+ value = "IMPROPER_UNICODE" ,
387+ justification =
388+ "Case-insensitive comparisons here operate on ASCII HTTP tokens"
389+ + " (header names, methods, schemes), not user identity"
390+ + " strings, so Unicode case-folding ambiguities do not apply." )
355391 public ApiHttpResponse sendRequest (
356392 String method ,
357393 String url ,
@@ -498,15 +534,14 @@ public ApiHttpResponse sendRequest(
498534 Map <String , String > redirectHeaders = new HashMap <>(currentHeaders );
499535 if (!sameOrigin ) {
500536 redirectHeaders .keySet ().removeIf (
501- k -> sensitiveHeaders . contains ( k . toLowerCase ( Locale . ROOT ) ));
537+ k -> isSensitiveHeader ( sensitiveHeaders , k ));
502538 }
503539 if (nextBody == HttpRequest .BodyPublishers .noBody ()
504540 || ((statusCode == 303 || statusCode == 301 || statusCode == 302 )
505541 && !"GET" .equalsIgnoreCase (currentMethod )
506542 && !"HEAD" .equalsIgnoreCase (currentMethod ))) {
507543 redirectHeaders .keySet ().removeIf (
508- k -> "content-type" .equalsIgnoreCase (k )
509- || "content-length" .equalsIgnoreCase (k ));
544+ DefaultApiClient ::isEntityHeader );
510545 }
511546
512547 HttpRequest .Builder redirectBuilder = HttpRequest .newBuilder (redirectUri )
@@ -584,6 +619,38 @@ private static boolean isRedirect(int statusCode) {
584619 || statusCode == 307 || statusCode == 308 ;
585620 }
586621
622+ /**
623+ * Whether a header name is in the case-insensitive sensitive-header set
624+ * that must be stripped on a cross-origin redirect. Extracted from the
625+ * redirect {@code removeIf} lambda so the ASCII case folding sits in a
626+ * named, annotated method rather than a synthetic lambda.
627+ */
628+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
629+ value = "IMPROPER_UNICODE" ,
630+ justification =
631+ "Case folding of ASCII HTTP header names, not user identity"
632+ + " strings; Unicode case-folding does not apply." )
633+ private static boolean isSensitiveHeader (Set <String > sensitiveHeaders , String name ) {
634+ return sensitiveHeaders .contains (name .toLowerCase (Locale .ROOT ));
635+ }
636+
637+ /**
638+ * Whether a header name is an entity header ({@code Content-Type} /
639+ * {@code Content-Length}) that must be dropped when a redirect changes
640+ * the request to a bodyless GET. Extracted from the redirect
641+ * {@code removeIf} lambda so the ASCII case comparison sits in a named,
642+ * annotated method rather than a synthetic lambda.
643+ */
644+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
645+ value = "IMPROPER_UNICODE" ,
646+ justification =
647+ "Case-insensitive comparison of ASCII HTTP header names, not user"
648+ + " identity strings; Unicode case-folding does not apply." )
649+ private static boolean isEntityHeader (String name ) {
650+ return "content-type" .equalsIgnoreCase (name )
651+ || "content-length" .equalsIgnoreCase (name );
652+ }
653+
587654 /**
588655 * Normalize a URI port, returning the default-port value (443 for
589656 * https, 80 for http) when the URI does not specify an explicit port.
@@ -592,6 +659,11 @@ private static boolean isRedirect(int statusCode) {
592659 * and sensitive headers are not stripped on a redirect that only adds
593660 * the implicit default port.
594661 */
662+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
663+ value = "IMPROPER_UNICODE" ,
664+ justification =
665+ "Case-insensitive comparison of ASCII URI scheme tokens, not user"
666+ + " identity strings; Unicode case-folding does not apply." )
595667 private static int effectivePort (URI uri ) {
596668 int port = uri .getPort ();
597669 if (port != -1 ) {
@@ -610,6 +682,11 @@ private static int effectivePort(URI uri) {
610682 * are normalised to scheme defaults so {@code https://host/x} and
611683 * {@code https://host:443/x} compare equal.
612684 */
685+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
686+ value = "IMPROPER_UNICODE" ,
687+ justification =
688+ "Case-insensitive comparison of ASCII URI scheme tokens, not user"
689+ + " identity strings; Unicode case-folding does not apply." )
613690 private static boolean sameOrigin (URI originalUri , URI redirectUri ) {
614691 return redirectUri .getHost () != null
615692 && redirectUri .getScheme () != null
@@ -628,6 +705,11 @@ private static boolean sameOrigin(URI originalUri, URI redirectUri) {
628705 * HTTP, the response is 307 or 308, and there is a non-empty body to
629706 * leak.
630707 */
708+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
709+ value = "IMPROPER_UNICODE" ,
710+ justification =
711+ "Case-insensitive comparison of ASCII URI scheme tokens, not user"
712+ + " identity strings; Unicode case-folding does not apply." )
631713 static boolean shouldRefuseHttpsToHttpBodyReplay (
632714 URI originalUri , URI redirectUri , int statusCode , boolean hasBody ) {
633715 if (!hasBody ) {
@@ -668,6 +750,11 @@ private static Charset parseCharset(String contentType) {
668750 }
669751 }
670752
753+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
754+ value = "IMPROPER_UNICODE" ,
755+ justification =
756+ "Case folding of ASCII media-type tokens for content sniffing, not"
757+ + " user identity strings; Unicode case-folding does not apply." )
671758 private static boolean isTextContentType (String contentType ) {
672759 int semi = contentType .indexOf (';' );
673760 String mediaType = (semi >= 0 ? contentType .substring (0 , semi ) : contentType ).trim ().toLowerCase (Locale .ROOT );
@@ -695,6 +782,11 @@ private static boolean isTextContentType(String contentType) {
695782 * @param encoding the Content-Encoding header value
696783 * @return the decompressed body bytes
697784 */
785+ @ edu .umd .cs .findbugs .annotations .SuppressFBWarnings (
786+ value = "IMPROPER_UNICODE" ,
787+ justification =
788+ "Case folding of ASCII Content-Encoding tokens, not user identity"
789+ + " strings; Unicode case-folding does not apply." )
698790 private static byte [] decompressBody (byte [] data , String encoding ) throws IOException {
699791 if (data .length == 0 ) {
700792 return new byte [0 ];
0 commit comments