Skip to content

Commit 82c63be

Browse files
committed
fix: clear SpotBugs findings in generated and bespoke SDK code
Regenerate transport, auth, and base API classes so intentional find-sec-bugs findings (opt-in insecure TLS, user-supplied CA path, ASCII protocol-token case folding) carry justified @SuppressFBWarnings annotations. Make the bespoke Zitadel entry point final to clear CT_CONSTRUCTOR_THROW and narrow URLUtil.buildHostname's catch to the checked exceptions it actually throws.
1 parent f9f5737 commit 82c63be

6 files changed

Lines changed: 122 additions & 5 deletions

File tree

‎src/main/java/com/zitadel/DefaultApiClient.java‎

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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];

‎src/main/java/com/zitadel/TransportOptions.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ public Builder caCertPath(@Nullable String caCertPath) {
238238
* @return this builder
239239
* @throws IllegalArgumentException if the proxy URL is not a valid URI
240240
*/
241+
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
242+
value = "IMPROPER_UNICODE",
243+
justification =
244+
"Case-insensitive comparison of ASCII URI scheme tokens"
245+
+ " (http/https), not user identity strings; Unicode"
246+
+ " case-folding does not apply.")
241247
public Builder proxy(@Nullable String proxy) {
242248
if (proxy != null) {
243249
java.net.URI uri;

‎src/main/java/com/zitadel/Zitadel.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import java.util.function.Consumer;
1313

1414
@SuppressWarnings({"unused", "DeprecatedIsStillUsed"})
15-
public class Zitadel {
15+
public final class Zitadel {
1616
/**
1717
* The underlying, configured API client.
1818
*

‎src/main/java/com/zitadel/api/BaseApi.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ public BaseApi(ApiClient apiClient, Configuration config, @Nullable Authenticato
118118
* @throws ApiException if the API call fails
119119
*/
120120
@SuppressWarnings("unchecked")
121+
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
122+
value = "IMPROPER_UNICODE",
123+
justification =
124+
"Case-insensitive comparison of ASCII HTTP header/media-type tokens,"
125+
+ " not user identity strings; Unicode case-folding does not"
126+
+ " apply.")
121127
protected <T> ApiResult<T> invokeApiForResult(
122128
String method,
123129
String path,
@@ -440,6 +446,11 @@ private static boolean isValidCookieValue(String value) {
440446
* @param contentType the response Content-Type header value (possibly empty)
441447
* @return true when the body was kept as decoded text by the transport
442448
*/
449+
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
450+
value = "IMPROPER_UNICODE",
451+
justification =
452+
"Case folding of ASCII media-type tokens for content sniffing, not"
453+
+ " user identity strings; Unicode case-folding does not apply.")
443454
private static boolean isTextResponseContentType(String contentType) {
444455
if (contentType == null || contentType.isEmpty()) {
445456
return true;

‎src/main/java/com/zitadel/auth/BearerAuthenticator.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public String getHost() {
5252
}
5353

5454
@Override
55+
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
56+
value = "IMPROPER_UNICODE",
57+
justification =
58+
"Case-insensitive comparison of the ASCII \"Bearer \" auth-scheme"
59+
+ " prefix, not a user identity string; Unicode case-folding"
60+
+ " does not apply.")
5561
public Map<String, String> getAuthHeaders() {
5662
/* Dedupe "Bearer " prefix (case-insensitive ASCII): tokens read
5763
* from env files are commonly stored already-prefixed; emitting

‎src/main/java/com/zitadel/utils/URLUtil.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.zitadel.utils;
22

3+
import java.net.MalformedURLException;
34
import java.net.URI;
5+
import java.net.URISyntaxException;
46
import java.net.URL;
57

68
public class URLUtil {
@@ -17,7 +19,7 @@ public static URL buildHostname(String hostname) {
1719
}
1820

1921
return new URI(hostname).toURL();
20-
} catch (Exception e) {
22+
} catch (URISyntaxException | MalformedURLException e) {
2123
throw new RuntimeException(e);
2224
}
2325
}

0 commit comments

Comments
 (0)