Api Key Exposure in Spring Boot with Mutual Tls
Api Key Exposure in Spring Boot with Mutual Tls — how this specific combination creates or exposes the vulnerability
Mutual Transport Layer Security (mTLS) in Spring Boot requires both the client and the server to present valid X.509 certificates during the TLS handshake. While mTLS strongly authenticates the client, it does not automatically protect sensitive values that travel inside HTTP requests. An API key exposed in headers, query parameters, or request bodies can still be visible to parties who successfully complete the TLS handshake, such as trusted internal services, compromised clients, or intermediaries in a chain of trust.
In Spring Boot, developers commonly configure mTLS via server.ssl.key-store and server.ssl.trust-store properties, enabling request client certificate validation with setNeedClientAuth(true). This ensures only clients with a trusted certificate can establish a connection, but it does not strip or hide credentials from the application protocol layer. As a result, an API key passed in an Authorization: ApiKey header, a custom header like X-API-Key, or a query parameter can be extracted from the request by application code, by logging mechanisms, or by observability tools that are part of the trusted environment.
Spring Boot applications that also consume OpenAPI specifications can inadvertently document or expose API key locations in generated docs. If an OpenAPI spec describes an api_key security scheme in a securitySchemes object and the implementation uses that key from an unencrypted source (e.g., a request header without additional transport protection beyond mTLS), scanning and runtime analysis can detect this pattern. The scanner performs unauthenticated checks against the live endpoint and can observe whether API key values are returned in responses, reflected in error messages, or logged in server-side traces, even when mTLS is in place.
Another risk arises when mTLS is used for client authentication but the application treats the authenticated principal as implicitly trustworthy. For example, a Spring Security configuration that only verifies the certificate subject distinguished name (DN) may allow any client with a valid certificate to call sensitive endpoints that return data protected by an API key. This can lead to vertical or horizontal privilege escalation if the API key is embedded in server responses or used as a bearer token for downstream services. The scanner’s checks include BOLA/IDOR and Property Authorization tests that can surface cases where an authenticated client can access another client’s data via an API key–protected endpoint.
SSRF and Unsafe Consumption findings are also relevant. An API key may be forwarded to external services from within the Spring Boot app, and if the destination URL is supplied by an attacker, the key can be exfiltrated through SSRF callbacks or logged in external logs. The scanner’s SSRF and Unsafe Consumption checks examine whether outbound calls incorporate secrets from request-controlled inputs and whether responses contain credentials, regardless of the presence of mTLS.
Mutual Tls-Specific Remediation in Spring Boot — concrete code fixes
To reduce the risk of API key exposure while using mTLS in Spring Boot, combine strict transport configuration with application-level handling of secrets. The following patterns illustrate how to enforce mTLS and avoid leaking API keys in request handling.
Enforce mTLS in application.properties or application.yml
server.port=8443
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=changeit
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat
server.ssl.trust-store=classpath:truststore.p12
server.ssl.trust-store-password=changeit
server.ssl.trust-store-type=PKCS12
server.ssl.client-auth=need
Setting server.ssl.client-auth=need ensures that the server requests and validates client certificates. Requests without a valid client cert will fail the TLS handshake before reaching Spring Security.
Restrict allowed client principals with Spring Security
@Configuration
@EnableWebSecurity
public class MtlsSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.x509(x509 -> x509
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(username ->
User.withUsername(username)
.password("ignored")
.roles("CLIENT")
.build()
)
);
return http.build();
}
}
This configuration uses an X.509 filter that extracts the common name (CN) from the client certificate and maps it to a Spring Security authentication. By anchoring the regex to CN=...<end>, you avoid parsing ambiguities that could allow spoofed identities.
Avoid logging or echoing API keys in responses
@RestController
@RequestMapping("/api/v1/resource")
public class ResourceController {
private final ResourceService service;
public ResourceController(ResourceService service) {
this.service = service;
}
@GetMapping
public ResponseEntity getResource(
@RequestHeader("X-Request-ID") String requestId) {
// Do NOT log the API key; log only a redacted identifier
String safeId = requestId != null ? requestId : "unknown";
return ResponseEntity.ok(service.fetchResource(safeId));
}
}
Ensure that API keys are never included in logs, error messages, or serialization output. Use request-scoped identifiers for tracing instead of raw keys.
Validate and sanitize inputs before forwarding
@Service
public class ProxyService {
private final WebClient webClient;
public ProxyService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("https://downstream.example.com").build();
}
public Mono forwardWithKey(String clientProvidedPath, String apiKey) {
if (!Paths.get(clientProvidedPath).normalize().equals(Paths.get(clientProvidedPath))) {
throw new IllegalArgumentException("Invalid path");
}
// Use apiKey from a secure source, not from client input
return webClient.get()
.uri("/" + clientProvidedPath)
.header("Authorization", "ApiKey " + apiKey)
.retrieve()
.bodyToMono(String.class);
}
}
Never construct outbound URLs or headers directly from client-controlled input. Validate paths, use allowlists, and keep API keys in server-side configuration or a secrets manager rather than deriving them from requests.