Found while reviewing the Edge Cookie provider work in
PR #1043. Neither of
the two reviews on that pull request raised it.
The problem
On main at commit d516a9e94, the live helper
edge_cookie::get_ec_id hands back any value a request carries in the
x-ts-ec header or the ts-ec cookie provided the value passes a character
allowlist, and three production paths then send that value onward. Nothing
checks that this deployment issued the value, so a browser carrying a
cookie-safe string of someone else's choosing has that string forwarded to the
origin, to click targets, and to a partner endpoint.
This is the same trap Aram flagged in review on the dead helper
ec::get_ec_id, but on paths that run in production.
What was checked and found
Read on main at d516a9e94.
| Where |
What is there |
crates/trusted-server-core/src/edge_cookie.rs:64-94 |
get_ec_id returns the x-ts-ec request header value, then the ts-ec cookie value, if ec_id_has_only_allowed_chars passes |
crates/trusted-server-core/src/ec/cookies.rs:27-40 |
ec_id_has_only_allowed_chars is a character allowlist alone, being [A-Za-z0-9._-], with no length bound and no format check |
crates/trusted-server-core/src/proxy.rs:1264 |
append_ec_id puts the value on the outbound origin URL as the ts-ec query parameter |
crates/trusted-server-core/src/proxy.rs:1609 |
the click and redirect path puts the value on the redirect target |
crates/trusted-server-core/src/integrations/testlight.rs:189 |
the testlight integration posts the value as user.id |
The x-ts-ec source is worth calling out separately, because a request header
is set by whoever made the request, so the value need not have come from a
cookie this deployment ever wrote.
Why it matters
Any value that fits the alphabet is egressed as though Trusted Server had
issued it. That includes an identifier created by a different Trusted Server
deployment and an identifier a page script chose. The pluggable-providers
design says the opposite, being that a value the selected provider does not
recognize is never used or egressed.
What resolves it
PR #1043 already
carries the fix, in commit 8684c6954 ("Egress only an Edge Cookie identifier
the provider recognizes"). On that branch:
crates/trusted-server-core/src/edge_cookie.rs adds recognized_ec_id, which
takes the raw inbound value and then dispatches the {code}~ prefix to the
provider that owns it, returning None for a foreign code, for a value the
selected provider rejects, and for every value when the deployment selects no
provider at all.
- The proxy origin and click paths in
crates/trusted-server-core/src/proxy.rs
call recognized_ec_id instead of the raw reader.
- The testlight integration calls it too and fails the request rather than
proxying when no recognized identifier is present.
So this issue is only resolvable by merging
PR #1043. It is filed
so the change is traceable rather than buried in a 44-file pull request, and it
should be closed when that pull request merges.
The related residue is resolved on the same branch. get_ec_id was public and
read the client-settable x-ts-ec request header directly, and commit
252aadefc ("Stop exposing an inbound Edge Cookie identifier nothing has
vouched for") makes it pub(crate) and renames it
unvalidated_ec_id_from_request, so the only way in from outside the module is
the checked recognized_ec_id path. That change is the inbound twin of this
egress fix and is written up in full in #1095.
Note on authorship
This issue was drafted with AI assistance. Every file and line reference above
was read at the commits named. A human should review it before acting on it.
Found while reviewing the Edge Cookie provider work in
PR #1043. Neither of
the two reviews on that pull request raised it.
The problem
On
mainat commitd516a9e94, the live helperedge_cookie::get_ec_idhands back any value a request carries in thex-ts-echeader or thets-eccookie provided the value passes a characterallowlist, and three production paths then send that value onward. Nothing
checks that this deployment issued the value, so a browser carrying a
cookie-safe string of someone else's choosing has that string forwarded to the
origin, to click targets, and to a partner endpoint.
This is the same trap Aram flagged in review on the dead helper
ec::get_ec_id, but on paths that run in production.What was checked and found
Read on
mainatd516a9e94.crates/trusted-server-core/src/edge_cookie.rs:64-94get_ec_idreturns thex-ts-ecrequest header value, then thets-eccookie value, ifec_id_has_only_allowed_charspassescrates/trusted-server-core/src/ec/cookies.rs:27-40ec_id_has_only_allowed_charsis a character allowlist alone, being[A-Za-z0-9._-], with no length bound and no format checkcrates/trusted-server-core/src/proxy.rs:1264append_ec_idputs the value on the outbound origin URL as thets-ecquery parametercrates/trusted-server-core/src/proxy.rs:1609crates/trusted-server-core/src/integrations/testlight.rs:189user.idThe
x-ts-ecsource is worth calling out separately, because a request headeris set by whoever made the request, so the value need not have come from a
cookie this deployment ever wrote.
Why it matters
Any value that fits the alphabet is egressed as though Trusted Server had
issued it. That includes an identifier created by a different Trusted Server
deployment and an identifier a page script chose. The pluggable-providers
design says the opposite, being that a value the selected provider does not
recognize is never used or egressed.
What resolves it
PR #1043 already
carries the fix, in commit
8684c6954("Egress only an Edge Cookie identifierthe provider recognizes"). On that branch:
crates/trusted-server-core/src/edge_cookie.rsaddsrecognized_ec_id, whichtakes the raw inbound value and then dispatches the
{code}~prefix to theprovider that owns it, returning
Nonefor a foreign code, for a value theselected provider rejects, and for every value when the deployment selects no
provider at all.
crates/trusted-server-core/src/proxy.rscall
recognized_ec_idinstead of the raw reader.proxying when no recognized identifier is present.
So this issue is only resolvable by merging
PR #1043. It is filed
so the change is traceable rather than buried in a 44-file pull request, and it
should be closed when that pull request merges.
The related residue is resolved on the same branch.
get_ec_idwas public andread the client-settable
x-ts-ecrequest header directly, and commit252aadefc("Stop exposing an inbound Edge Cookie identifier nothing hasvouched for") makes it
pub(crate)and renames itunvalidated_ec_id_from_request, so the only way in from outside the module isthe checked
recognized_ec_idpath. That change is the inbound twin of thisegress fix and is written up in full in #1095.
Note on authorship
This issue was drafted with AI assistance. Every file and line reference above
was read at the commits named. A human should review it before acting on it.