Missing catch-all exception handler — stack traces leak to clients #7
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
GlobalExceptionHandleronly handles 4 specific exception types. Any unexpected exception (NPE, database errors, Hibernate exceptions, etc.) falls through to Spring Boot's default error handler, which returns stack traces with internal class names, file paths, and SQL details.Affected files
GlobalExceptionHandler.java— missing@ExceptionHandler(Exception.class)Attack scenario
An attacker triggers an unexpected error (e.g., malformed UUID in a path variable, database timeout) and receives a stack trace revealing internal package structure, library versions, and potentially SQL queries.
Recommended fix
Add a catch-all handler:
Severity
High — information disclosure via stack traces aids further exploitation.
👨💻 Kai — Frontend Engineer
Backend fix, but it directly affects what the SvelteKit frontend receives and has to handle. A few things I want to pin down:
Current frontend behavior on unexpected errors:
+page.server.tsfetch calls need to handle 500 responses gracefully — returning a user-friendlyfail()orerror(), not crashing the SSR render.{ code: "INTERNAL_ERROR", message: "An unexpected error occurred" }body — which is much easier to handle uniformly in SvelteKit.SvelteKit error boundary:
+error.sveltepage that catches unhandled SvelteKit errors (including 500s from the backend) and renders something meaningful in German. Does one exist? If not, this issue is a good prompt to add it.handleFetchorhandleErrorinhooks.server.ts— are we currently logging or transforming fetch errors from the backend? We should at minimum log the backend error on the server side without surfacing it to the client.Questions:
+error.sveltein the project already, and does it handle the case of a server-side rendering failure gracefully?🔧 Backend Engineer — Spring Boot / PostgreSQL Specialist
The fix is correct and the recommended code snippet in the issue is close to what I'd write. A few details to get right:
The catch-all handler:
@ExceptionHandler(Exception.class)will catch everything not handled by more specific handlers, includingRuntimeException. This is what we want.Exception.classas a catch-all won't override the existing 4 handlers. No ordering annotation needed.log.error("Unexpected error", ex)— include the exception so the stack trace appears in server logs (where we want it) but not in the response body.ApiError.of()consistency:ApiError.of("INTERNAL_ERROR", "An unexpected error occurred")matches the sameApiErrorstructure used by the existing 4 handlers. Inconsistent error response shapes are a maintenance problem and break frontend error handling.Spring Boot's default error handling:
BasicErrorControlleralso serves error responses at/error. With a@ControllerAdvicecatch-all, most exceptions will be intercepted before reachingBasicErrorController— but check whetherserver.error.include-stacktrace=neveris set inapplication.ymlas a defense-in-depth measure.server.error.include-message=neverandserver.error.include-binding-errors=neverin production profile.MethodArgumentTypeMismatchException:MethodArgumentTypeMismatchExceptionorConstraintViolationException— both should now be caught by the catch-all, but worth verifying they don't have existing specific handlers that map them somewhere unexpected.Questions:
server.error.include-stacktracecurrently configured inapplication.yml? If it's set toalwaysoron_param, that's also leaking and needs to be fixed regardless of the catch-all.🧪 QA Engineer
This is a small code change with broad impact — every unhandled exception in the application now flows through this handler. Test coverage needs to verify both that it works and that it doesn't accidentally swallow exceptions that should be handled more specifically.
Unit tests (GlobalExceptionHandler):
shouldReturn500WithGenericBodyForNullPointerException()— most common unexpected runtime errorshouldReturn500WithGenericBodyForRuntimeException()— generic caseshouldReturn500WithGenericBodyForDatabaseException()— simulates a JPA/Hibernate error reaching the handlershouldNotExposeExceptionMessageInResponseBody()— response body contains only the generic code + message, no exception detailshouldLogExceptionToServerLog()— verifylog.error()is called with the exception (use a log appender spy or verify via Mockito captor)Integration tests:
{ code: "INTERNAL_ERROR", message: "An unexpected error occurred" }(not a stack trace)Content-Type: application/json, nottext/html(Spring's default error page is HTML)Regression — existing handlers not affected:
Spring Boot default error endpoint:
GET /error(Spring's fallback) also returns a clean response, not a stack trace — this is separate from the@ControllerAdvicehandler and may need independent configurationEdge cases:
OutOfMemoryError— this is aThrowable, not anException. The catch-all won't catch it. Is that acceptable, or should we addThrowable.classhandling?Questions:
🔐 Sable — Security Engineer
High priority and easy to fix — this is a textbook OWASP A05 (Security Misconfiguration) / information disclosure issue. The attack scenario is real: stack traces reliably reveal package names, library versions, internal file paths, and sometimes SQL fragments — all useful for building a targeted exploit chain.
The fix is necessary but layer 1 of defense-in-depth:
@ExceptionHandler(Exception.class)catch-all handles most cases, but Spring Boot also has a fallback error controller at/errorfor errors that occur outside the servlet filter chain (e.g., in aFilteritself). Confirmserver.error.include-stacktrace=neveris set inapplication.ymlfor all profiles — don't rely solely on the@ControllerAdvice.server.error.include-message=never,server.error.include-exception=false. These suppress the exception class name and message from Spring Boot's own error responses.Information in the log:
log.error("Unexpected error", ex)is correct — the full stack trace belongs in the server log, not the response. Confirm the logging configuration doesn't write to a file or endpoint accessible from outside (e.g., an/actuator/logfileendpoint that is publicly accessible).Actuator endpoints:
application.ymlfor the error config, also check that/actuator/healthis the only actuator endpoint exposed, or that actuator is properly gated. Exposed actuator endpoints (especially/actuator/env,/actuator/beans,/actuator/mappings) leak far more than a stack trace.Exception message leakage in existing handlers:
ex.getMessage()into theApiErrorbody. Ifex.getMessage()contains internal details (table names, column names, file paths), those are also information leaks — worth auditing alongside this fix.Questions:
spring.profiles.activeset correctly in the production deployment so the productionapplication-prod.yml(withinclude-stacktrace=never) is actually loaded?@ExceptionHandlermethods pass rawex.getMessage()into the response body?🎨 Atlas — UI/UX Designer
Backend-only fix, but it has a direct UX consequence: what does the user see when an unexpected error occurs? That's a design concern.
Error state UX for unexpected failures:
+error.sveltepage (if it exists) should show: a friendly German message, a clear explanation that it's not the user's fault, and a single action — typically "Zur Startseite" or "Neu laden" depending on context.--color-pagebackground,Frauncesfor the heading (human, warm),DM Sansfor the body copy, the standard button style (13px, weight 500, tracking 0.04em).Avoid the "white screen of death":
+error.svelteboundary needs to handle this gracefully and always render something useful.No new visual components needed today — but this issue is a good prompt to confirm the error page exists and matches the design system. If it's a plain unstyled page right now, that's a design debt item.
Questions: