Skip to content

Commit 762a56c

Browse files
rohan-b99claude
andcommitted
fix(rhai): hide errors from the router's own Rhai functions from clients
The router's Rhai functions raised their errors as plain strings, the same shape as a script's own `throw "..."`, so their text reached clients. Any caller could learn which environment variables a script reads (`env::get()` on an unset variable), or trigger parser errors from `base64::decode()` and `json::decode()` on a client header. Those functions now raise a `RouterFunctionError`. `process_error` recognises it before the thrown-string branch: the client gets the status code's reason phrase and the full text is logged. A thrown string that the script chose still reaches the client unchanged. Rhai prints a custom type by its type name, so the error is swapped for its text before it is logged, and the script `log_*` functions do the same. `to_string` and `to_debug` are registered so `${err}` in a script shows the text. This replaces `NO_CLIENT_MESSAGE`. A missing header now raises a message that names the header, since that text no longer reaches the client. A `catch` block that catches one of these errors now receives an error object instead of a string. `${err}` still gives the message, but `type_of(err)` and comparisons such as `err == "..."` change. The docs and changeset call this out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 40abde7 commit 762a56c

9 files changed

Lines changed: 297 additions & 122 deletions

File tree

‎.changesets/fix_rohan_b99_redact_rhai_internals_from_client_errors.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ throw #{ status: 403, message: "Forbidden" }; // client sees: Forbidden
2020
throw #{ status: 403, body: #{ errors: [...] } }; // client sees the custom body
2121
```
2222

23-
Anything the script did *not* choose is replaced with the status code's reason phrase, and the underlying error is logged at `ERROR` level instead. This covers failures raised by the Rhai engine itself (such as calling an undefined function or a type mismatch), a `throw` carrying only a status - `throw #{ status: 400 }` now reads `Bad Request` rather than dumping the thrown object - failures in the router's own Rhai functions that carry no message, such as reading a header that isn't present, and a `throw` the router cannot read as a message - a value that is not a string or an object map, such as `throw 42`, or a map with an unreadable field, such as `throw #{ status: "four hundred", message: "Invalid request" }`, which is discarded whole so the `message` beside the bad status goes with it.
23+
Anything the script did *not* choose is replaced with the status code's reason phrase, and the underlying error is logged at `ERROR` level instead. This covers:
2424

25-
Failures in the router's own Rhai functions that *do* carry a message are only partly covered: the wrapper, the script line and position and the chain of callbacks are gone, but the function's own message still reaches the client. `env::get()` on a variable that isn't set still reports `could not expand variable: MY_VAR, environment variable not found`, and `json::decode()` on malformed input still reports the parse error. A router function's error is indistinguishable from a script's own `throw`, so telling them apart would take recording which side raised it - the Rhai customization docs carry this as a documented limitation. If a script of yours calls those functions on a client-facing path, catch the error and throw your own.
25+
- Failures raised by the Rhai engine itself, such as calling an undefined function or a type mismatch.
26+
- A `throw` carrying only a status: `throw #{ status: 400 }` now reads `Bad Request` rather than dumping the thrown object.
27+
- A `throw` the router cannot read as a message: a value that is not a string or an object map, such as `throw 42`, or a map with an unreadable field, such as `throw #{ status: "four hundred", message: "Invalid request" }`. The whole map is discarded, so the `message` beside the bad status goes with it.
28+
29+
Errors raised by the router's own Rhai functions, such as `env::get()` on an unset variable or `base64::decode()` and `json::decode()` on malformed input, are no longer sent to clients. Clients see the status code's reason phrase, and the full error is logged.
2630

2731
Two things to be aware of when upgrading:
2832

2933
- Client-facing messages for a thrown string no longer include the `rhai execution error: 'Runtime error: ... (line N, position M)'` wrapper. Only the string you threw is returned. The full error is still in the logs.
30-
- Nothing changes for scripts themselves: a `catch` block receives exactly what it received before, and status codes are unchanged.
34+
- A `catch` block that catches an error from one of the router's Rhai functions now receives an error object instead of a string. `${err}` still gives the message, but `type_of(err)` and comparisons such as `err == "..."` change.
3135

3236
By [@rohan-b99](https://github.com/rohan-b99) in https://github.com/apollographql/router/pull/10004

0 commit comments

Comments
 (0)