Found while reviewing the Edge Cookie provider work in
PR #1043. The
behavior predates that pull request.
The problem
handle_request_cookies returns an InvalidHeaderValue error when the request
Cookie header is not valid UTF-8, and the Edge Cookie lifecycle propagates
that error. On all four adapters, the GPT diagnostics request preparer rewrites
the Cookie header before the Edge Cookie context is ever built, keeping only
the field lines that decode as UTF-8, so by the time the Edge Cookie code runs
the header is always either valid UTF-8 or absent. The error arm is dead on
every adapter path that builds Edge Cookie state.
The same rewrite has a second consequence, which is that a request whose
Cookie header carries any byte outside UTF-8 silently loses every cookie in
that field line, including a perfectly good ts-ec value sitting next to the
bad bytes. Nothing logs the loss.
What was checked and found
Read on the PR #1043
branch. The line numbers below are from that branch tree.
crates/trusted-server-core/src/cookies.rs:51-67 is handle_request_cookies.
It calls HeaderValue::to_str on the Cookie header and maps the failure to
TrustedServerError::InvalidHeaderValue with the message "Cookie header
contains invalid UTF-8".
crates/trusted-server-core/src/ec/mod.rs:104 is the Edge Cookie lifecycle's
call into it, so the error surfaces from Edge Cookie context construction.
crates/trusted-server-core/src/integrations/gpt_diagnostics.rs:394-416 is
sanitize_console_cookie. It reads every Cookie field line through
to_str().ok(), dropping any line that is not UTF-8, removes the Cookie
header outright, and reinserts only the retained lines.
sanitize_console_cookie is called from prepare_request at
gpt_diagnostics.rs:277, before the enabled check that shapes the rest of the
decision, so the rewrite happens even in a deployment with GPT diagnostics
switched off.
- Each adapter calls
gpt_diagnostics::prepare_request before it builds Edge
Cookie state.
- Fastly,
crates/trusted-server-adapter-fastly/src/app.rs:585, immediately
before build_ec_request_state at :592.
- Axum,
crates/trusted-server-adapter-axum/src/app.rs:146 in the shared
request wrapper and :207 on the fallback path.
- Cloudflare,
crates/trusted-server-adapter-cloudflare/src/app.rs:200.
- Spin,
crates/trusted-server-adapter-spin/src/app.rs:577 and :718, with
build_ec_context following at :590.
- One caller can still meet a non-UTF-8
Cookie header, being the Fastly admin
Edge Cookie lookup at
crates/trusted-server-adapter-fastly/src/app.rs:576, which is dispatched
before prepare_request runs at :585. It has its own message at
crates/trusted-server-core/src/ec/admin.rs:327-333. That route exists on the
Fastly adapter alone, and the other three adapters answer the admin paths with
deny_admin_diagnostic_fallback.
Why it matters
Three things follow.
- An error path that cannot run is untested behavior that reads as tested. Any
future reader takes the Edge Cookie code as handling a hostile Cookie
header when in fact something else already handled it.
- The handling that does happen is silent cookie loss rather than a refusal, so
a returning visitor with one malformed byte in their cookie header looks like
a brand new visitor, and no log line says why.
- The ordering is load bearing and undocumented. If the GPT diagnostics
preparer ever stops running first, or stops rewriting the header, the dead
arm comes alive on every adapter at once.
What would resolve it
Decide which layer owns malformed cookie headers, then make the code say so.
- If dropping the malformed field line is the wanted behavior, log it at warn
level in sanitize_console_cookie, say so in the doc comment on
handle_request_cookies, and either delete the unreachable arm or keep it with
a comment naming the preparer that makes it unreachable.
- If refusing the request is the wanted behavior, move the UTF-8 check ahead of
the preparer so the refusal actually happens.
Either way, add a test at the adapter level rather than the unit level, because
the unit tests for the error arm pass today while no adapter can reach it.
This issue does not depend on any pull request in the current stack, though the
line numbers above are from the
PR #1043 branch.
Note on authorship
This issue was drafted with AI assistance. Every file and line reference above
was read at the commit named. A human should review it before acting on it.
Found while reviewing the Edge Cookie provider work in
PR #1043. The
behavior predates that pull request.
The problem
handle_request_cookiesreturns anInvalidHeaderValueerror when the requestCookieheader is not valid UTF-8, and the Edge Cookie lifecycle propagatesthat error. On all four adapters, the GPT diagnostics request preparer rewrites
the
Cookieheader before the Edge Cookie context is ever built, keeping onlythe field lines that decode as UTF-8, so by the time the Edge Cookie code runs
the header is always either valid UTF-8 or absent. The error arm is dead on
every adapter path that builds Edge Cookie state.
The same rewrite has a second consequence, which is that a request whose
Cookieheader carries any byte outside UTF-8 silently loses every cookie inthat field line, including a perfectly good
ts-ecvalue sitting next to thebad bytes. Nothing logs the loss.
What was checked and found
Read on the PR #1043
branch. The line numbers below are from that branch tree.
crates/trusted-server-core/src/cookies.rs:51-67ishandle_request_cookies.It calls
HeaderValue::to_stron theCookieheader and maps the failure toTrustedServerError::InvalidHeaderValuewith the message "Cookie headercontains invalid UTF-8".
crates/trusted-server-core/src/ec/mod.rs:104is the Edge Cookie lifecycle'scall into it, so the error surfaces from Edge Cookie context construction.
crates/trusted-server-core/src/integrations/gpt_diagnostics.rs:394-416issanitize_console_cookie. It reads everyCookiefield line throughto_str().ok(), dropping any line that is not UTF-8, removes theCookieheader outright, and reinserts only the retained lines.
sanitize_console_cookieis called fromprepare_requestatgpt_diagnostics.rs:277, before the enabled check that shapes the rest of thedecision, so the rewrite happens even in a deployment with GPT diagnostics
switched off.
gpt_diagnostics::prepare_requestbefore it builds EdgeCookie state.
crates/trusted-server-adapter-fastly/src/app.rs:585, immediatelybefore
build_ec_request_stateat:592.crates/trusted-server-adapter-axum/src/app.rs:146in the sharedrequest wrapper and
:207on the fallback path.crates/trusted-server-adapter-cloudflare/src/app.rs:200.crates/trusted-server-adapter-spin/src/app.rs:577and:718, withbuild_ec_contextfollowing at:590.Cookieheader, being the Fastly adminEdge Cookie lookup at
crates/trusted-server-adapter-fastly/src/app.rs:576, which is dispatchedbefore
prepare_requestruns at:585. It has its own message atcrates/trusted-server-core/src/ec/admin.rs:327-333. That route exists on theFastly adapter alone, and the other three adapters answer the admin paths with
deny_admin_diagnostic_fallback.Why it matters
Three things follow.
future reader takes the Edge Cookie code as handling a hostile
Cookieheader when in fact something else already handled it.
a returning visitor with one malformed byte in their cookie header looks like
a brand new visitor, and no log line says why.
preparer ever stops running first, or stops rewriting the header, the dead
arm comes alive on every adapter at once.
What would resolve it
Decide which layer owns malformed cookie headers, then make the code say so.
level in
sanitize_console_cookie, say so in the doc comment onhandle_request_cookies, and either delete the unreachable arm or keep it witha comment naming the preparer that makes it unreachable.
the preparer so the refusal actually happens.
Either way, add a test at the adapter level rather than the unit level, because
the unit tests for the error arm pass today while no adapter can reach it.
This issue does not depend on any pull request in the current stack, though the
line numbers above are from the
PR #1043 branch.
Note on authorship
This issue was drafted with AI assistance. Every file and line reference above
was read at the commit named. A human should review it before acting on it.