Skip to content

[Detail Bug] Sensitive headers and bodies leaked in ExceptionResponseBuilder.describeException #31

@detail-app

Description

@detail-app

Summary

  • Context: ExceptionResponseBuilder is a utility used by multiple controllers (Chat, Ingestion, GuidedLearning) to build standardized error responses for the API.
  • Bug: The describeException method appends unfiltered HttpHeaders and full response bodies to the error description string which is then returned to the API client.
  • Actual vs. expected: All headers (including sensitive ones like Authorization and Cookie) and the entire response body are included in the diagnostic details; expected behavior is to filter out sensitive headers and sanitize or omit the body to prevent information exposure.
  • Impact: High. Sensitive authentication tokens, session cookies, and internal system data are exposed to any API client encountering an error during an external service call.

Code with bug

    private void appendRestClientDetails(StringBuilder details, RestClientResponseException exception) {
        // ...
        String responseBody = exception.getResponseBodyAsString();
        if (!responseBody.isBlank()) {
            details.append(", body=").append(responseBody); // <-- BUG 🔴 Unfiltered response body leaked to client
        }
        HttpHeaders headers =
                Optional.ofNullable(exception.getResponseHeaders()).orElseGet(HttpHeaders::new);
        if (!headers.isEmpty()) {
            details.append(", headers=").append(headers); // <-- BUG 🔴 Unfiltered headers (Authorization, Cookie) leaked to client
        }
        details.append("]");
    }

(Similar issues exist in appendWebClientDetails and appendOpenAiDetails)

Evidence

  • Header Leakage Confirmed: A reproduction test using HttpClientErrorException showed that sensitive headers added to the exception are included in the output string without any masking or filtering.
    @Test
    void describeException_leaksSensitiveHeaders() {
        org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
        headers.add("Authorization", "Bearer sensitive-token");
        headers.add("Cookie", "session=secret");

        org.springframework.web.client.HttpClientErrorException exception = org.springframework.web.client.HttpClientErrorException.create(
            HttpStatus.UNAUTHORIZED,
            "Unauthorized",
            headers,
            null,
            null
        );

        ExceptionResponseBuilder builder = new ExceptionResponseBuilder();
        String details = builder.describeException(exception);

        assertTrue(details.contains("Authorization"));
        assertTrue(details.contains("Bearer sensitive-token")); // Leaked!
        assertTrue(details.contains("Cookie"));
        assertTrue(details.contains("session=secret")); // Leaked!
    }
  • Direct Violation of Mandates: This violates the "Security First" mandate in AGENTS.md which states: "Never introduce code that exposes, logs, or commits secrets, API keys, or other sensitive information."

Exploit scenario

  1. An attacker triggers an operation that causes a downstream service call to fail (e.g., providing an invalid session ID or malformed input that results in a 4xx error from an external provider like OpenAI or a local search service).
  2. The server-side code catches the resulting exception and passes it to ExceptionResponseBuilder.describeException().
  3. The server returns a JSON error response or an SSE error event containing the details field.
  4. The attacker inspects the details field and retrieves sensitive Authorization headers, session Cookies, or internal state information leaked from the downstream service's response body.

Why has this bug gone undetected?

This bug often goes undetected because it is perceived as a "diagnostic feature" rather than a vulnerability. Developers frequently want rich error information during development and may not realize that these details are being passed all the way to the end-user's browser in production. Since it only occurs on error paths, it doesn't break the "happy path" functionality of the application.

Recommended fix

Implement a header filtering mechanism that masks or removes sensitive headers (e.g., Authorization, Cookie, Set-Cookie, X-API-Key) before appending them to the description. Additionally, consider truncating the response body or only including a summary if it's too large or likely to contain sensitive data.

Related bugs

  • NullPointerException in describeException: The same class contains multiple potential crashes where statusText.isBlank() or headers.isEmpty() is called on potentially null values (specifically in WebClientResponseException handling where headers/status can be null). This was confirmed with a reproduction test:
    @Test
    void describeException_withNullStatusText_throwsNpe() {
        org.springframework.web.client.HttpClientErrorException exception =
            new org.springframework.web.client.HttpClientErrorException(HttpStatus.BAD_REQUEST, null, null, null, null);

        ExceptionResponseBuilder builder = new ExceptionResponseBuilder();
        // This throws NPE because statusText is null and isBlank() is called on it
        assertThrows(NullPointerException.class, () -> builder.describeException(exception));
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdetail

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions