feat(errors): keep backend routing detail out of client error bodies #8
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/redact-upstream-errors"
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?
A transport failure's error text names the upstream's hostname, port, and
resolved IP. That was going straight into the 502 body, so any client
could read the route table off a failed request -- and a client behind
Caddy can neither reach those addresses nor act on them.
The description now goes to the log and the client gets the bare fact.
Timeouts keep their detail in the body, since the length that expired is
SLP's own configuration rather than backend topology, and it is the one
thing here a caller can act on. The same leak in the url.Parse path,
whose errors quote the URL, is closed the same way.
Redacting detail from the client makes the two sides harder to join, so
requests now carry an id. The shape matches gllm's -- a four-letter
prefix that never occurs in the hex body, then 12 random bytes -- so one
expression finds either: (gdqz|slpq)[0-9a-f]{24}. The prefix differs
because the origin does: gllm mints gdqz when it has accepted a request,
SLP mints slpq and only surfaces it when there is no upstream response,
so an slpq id in an error body is a positive claim that the request never
reached the backend. On a successful forward the upstream's own header
wins the header copy, and log_requests records it as upstream=gdqz...,
which is what joins an SLP log line to a gllm one.
SLP does not adopt an inbound X-Request-Id: gllm ignores the request
header too, so honouring one would buy no correlation across the hop and
would put an untrusted client string in the log. It is not dropped
silently either -- the value is recorded per request as client="...",
and the first one seen logs why it was ignored, once per process.
Inbound values are truncated and always logged quoted, so a caller
cannot forge log lines with a newline; there is a test for that.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
A transport failure's error text names the upstream's hostname, port, and resolved IP. That was going straight into the 502 body, so any client could read the route table off a failed request -- and a client behind Caddy can neither reach those addresses nor act on them. The description now goes to the log and the client gets the bare fact. Timeouts keep their detail in the body, since the length that expired is SLP's own configuration rather than backend topology, and it is the one thing here a caller can act on. The same leak in the url.Parse path, whose errors quote the URL, is closed the same way. Redacting detail from the client makes the two sides harder to join, so requests now carry an id. The shape matches gllm's -- a four-letter prefix that never occurs in the hex body, then 12 random bytes -- so one expression finds either: (gdqz|slpq)[0-9a-f]{24}. The prefix differs because the origin does: gllm mints gdqz when it has accepted a request, SLP mints slpq and only surfaces it when there is no upstream response, so an slpq id in an error body is a positive claim that the request never reached the backend. On a successful forward the upstream's own header wins the header copy, and log_requests records it as upstream=gdqz..., which is what joins an SLP log line to a gllm one. SLP does not adopt an inbound X-Request-Id: gllm ignores the request header too, so honouring one would buy no correlation across the hop and would put an untrusted client string in the log. It is not dropped silently either -- the value is recorded per request as client="...", and the first one seen logs why it was ignored, once per process. Inbound values are truncated and always logged quoted, so a caller cannot forge log lines with a newline; there is a test for that. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>