Error and exception handling in web applications can introduce security issues, often in the form of denial of service (i.e., when a service crashes because of poor error handling) and information disclosure (i.e., when an exception containing sensitive details about the system is propagated to the user/attacker).
To combat this, the system needs to fail gracefully, revealing little details about the failure and recovering as much as possible. When it comes to exceptions, managed languages have simple and well-understood mechanisms for exception handling. However, throughout my research I have failed to find advice on how to construct a proper exception handling mechanism in modern web applications that include several layers of logic between the database and the client applications.
For example, the Spring framework has the controller (that provides the APIs and handles HTTP requests), the service (that contains the majority of the business logic) and the repositories (that handle communication between the application and the database). Additionally, the service can interface with utility components and modules that offer various functionality (i.e., file access, algorithm calculation). The following data flow diagram illustrates this architecture.
By examining the origin of exceptions, we can see that the Calculation Engine, Repository, and File Access Utility can (potentially) reveal some details about the system, should an exception be thrown when interfacing with the different datastores (i.e., information about the SQL database, knowledge base schema, etc.).
The questions then becomes, where should developers handle these exceptions, and reduce the risk to both information disclosure and denial of service?
An approach that seems suitable for me is to handle exceptions as they arise and wrap them into application-specific exceptions. The parts of the application that have data flows that cross a trust boundary (e.g., to the file system, the Internet) should sanitize all exceptions by wrapping them into "more friendly" custom exceptions (created by the developers) to stop any risk from information disclosure being propagated further, as well as to localize any component failure. The internal components should then know how to handle application exceptions more suitably.
The main drawback of this approach is that the exception handling mechanism is more complex, as it requires the introduction of application-specific exceptions for each category of native (and applicable) exceptions.
Does this approach have any other pros and cons, and is there an alternative design that handles the balance between security and workload better?