|
1 | 1 | ### DR-009: Handler error-propagation contract |
2 | 2 |
|
3 | | -**Status:** Accepted |
| 3 | +**Status:** Accepted; revised 2026-05-29 (Revision 1) |
4 | 4 | **Date:** 2026-04-30 |
5 | 5 | **Context:** With DR-4 (return-by-value), null-return is impossible. Two cases remain: handler throws, library-internal exception during dispatch. |
6 | 6 |
|
|
19 | 19 | - `internal_error_handler` is the single user-overridable error escape hatch. |
20 | 20 |
|
21 | 21 | --- |
| 22 | + |
| 23 | +### Revision 1 (2026-05-29) — Sanitize the default 500 body |
| 24 | + |
| 25 | +**Context:** The original decision had `internal_error_page` surface |
| 26 | +`e.what()` in the default-handler 500 body for debugging ergonomics. |
| 27 | +This was flagged by two security reviewers on TASK-031 (task-031 #3, |
| 28 | +task-031 #4) and one Pass-1 sweep on TASK-036 (task-036 #44) as a |
| 29 | +CWE-209 information-disclosure surface: handler exception text |
| 30 | +routinely embeds file paths, SQL fragments, internal identifiers, and |
| 31 | +attacker-influenced input that must not cross a process boundary to an |
| 32 | +untrusted client. |
| 33 | + |
| 34 | +**Revised decision:** The default body of the no-handler-configured 500 |
| 35 | +path is the fixed string `"Internal Server Error"`. The originating |
| 36 | +exception message is still: |
| 37 | + |
| 38 | +1. Forwarded verbatim to a user-configured `internal_error_handler` (if |
| 39 | + one is wired) — that handler can render any body it wants. |
| 40 | +2. Forwarded verbatim to the user-configured `log_error` callback (the |
| 41 | + server-side log). |
| 42 | + |
| 43 | +Only the **default** HTTP response body is sanitized; everything else |
| 44 | +is unchanged. A new opt-in builder flag, |
| 45 | +`create_webserver::expose_exception_messages(bool)`, restores the |
| 46 | +pre-revision behaviour of including the message in the default body. |
| 47 | +The flag is documented as development-only (see the `@warning` block |
| 48 | +on the setter). |
| 49 | + |
| 50 | +**Contract points 1–6 in §5.2 stand; only the default-body clause in |
| 51 | +point 1 is amended.** |
| 52 | + |
| 53 | +**Consequences (Revision 1):** |
| 54 | +- The regression tests |
| 55 | + `dr009_runtime_error_message_surfaces_in_default_body` and |
| 56 | + `dr009_non_std_exception_yields_unknown_exception_in_default_body` |
| 57 | + are renamed/split into a "fixed body" pair (the new contract) plus a |
| 58 | + "verbose body" pair (the opt-in round-trip). See |
| 59 | + `test/integ/basic.cpp`. |
| 60 | +- The legacy integration tests in `basic_suite` that pre-date this |
| 61 | + revision and assert on the message-in-body (`exception_forces_500`, |
| 62 | + `untyped_error_forces_500`, `file_serving_resource_missing`, |
| 63 | + `file_serving_resource_dir`, `long_error_message`, |
| 64 | + `dr009_feature_unavailable_lands_as_generic_500`) opt the |
| 65 | + suite-shared `ws` into the verbose mode via |
| 66 | + `.expose_exception_messages(true)` so their original assertion |
| 67 | + intent — "the dispatcher's message-forwarding path works" — is |
| 68 | + preserved without diluting coverage. |
| 69 | +- No request-id concept is introduced as part of this revision. The |
| 70 | + original action item mentioned "with the request id (if any) |
| 71 | + appended"; the codebase has no request-id plumbing today and adding |
| 72 | + one is out of scope for a security-fix revision. The fixed-body |
| 73 | + alone satisfies CWE-209. A request-id concept can be added later |
| 74 | + without re-opening this decision. |
| 75 | +- `log_dispatch_error` continues to log `e.what()` verbatim regardless |
| 76 | + of the flag. The error log is the canonical destination for the |
| 77 | + verbatim exception text. Handlers that may throw exceptions |
| 78 | + containing sensitive data (DB URLs, credentials, attacker-influenced |
| 79 | + input) SHOULD catch and sanitize the message before re-throwing if |
| 80 | + those values must not appear in the server log either; see the |
| 81 | + Doxygen note on `webserver_impl::log_dispatch_error`. |
| 82 | +- ABI: this revision is shipping on `feature/v2.0` before v2.0 cuts; |
| 83 | + SOVERSION is already 2 and there is no released ABI to preserve. |
| 84 | + The added `const bool expose_exception_messages` on `webserver` and |
| 85 | + the `bool _expose_exception_messages` on `create_webserver` recompile |
| 86 | + cleanly for any consumer TU. |
| 87 | + |
| 88 | +**Related findings:** task-031 #3, task-031 #4, task-036 #44, task-034 #24. |
| 89 | + |
| 90 | +--- |
0 commit comments