crates/trusted-server-core/src/edge_cookie.rs validates incoming identifiers with ec_id_has_only_allowed_chars, which on main at commit d516a9e94 is the single line ec_id.chars().all(is_allowed_ec_id_char), where is_allowed_ec_id_char accepts [A-Za-z0-9._-]. There is no length bound, no format check, and ~ is not in the allowlist. Nothing else is applied.
That function's own doc comment in ec/cookies.rs says the strict check is the one used to reject untrusted request values before they enter the system. So the lax backstop is being used where the strict path was intended, and the code contradicts its own documentation.
There is a second half worth knowing. x-ts-ec is in INTERNAL_HEADERS, so it is stripped from responses, but it is not in SPOOFABLE_FORWARDED_HEADERS, so it is not stripped from inbound requests. The header is therefore the client's to set, and the raw reader prefers it over the cookie.
What the correct check is
Not the built-in strict shape check. A vendor provider's identifier is not required to match the built-in {64 hex}.{6 alphanumeric} form, so holding every deployment to it would drop exactly the opaque identifiers a pluggable provider exists to carry.
The correct check is ownership. Dispatch on the {code}~ prefix to the provider that owns it, and let that provider decide whether it accepts the value. That is what the identity lifecycle already does on read-back.
How it is fixed in this stack
The raw reader cannot make that judgement, because it has neither the settings nor the selected provider, so it is no longer public. The only way in from outside the module is the checked path. Nothing outside the crate called the old name.
The test drives three identifiers this deployment could never have issued, being a value of the wrong shape entirely, a correctly shaped value carrying another deployment's provider code, and a value claiming the built-in code without the built-in shape. It shows the character bounds accept all three, and requires the public path to recognize none of them, while a genuinely owned identifier still returns. Replacing the ownership check with the bounds fails it.
Produced with AI assistance and needs human review. Verified against upstream/main.
crates/trusted-server-core/src/edge_cookie.rsvalidates incoming identifiers withec_id_has_only_allowed_chars, which onmainat commitd516a9e94is the single lineec_id.chars().all(is_allowed_ec_id_char), whereis_allowed_ec_id_characcepts[A-Za-z0-9._-]. There is no length bound, no format check, and~is not in the allowlist. Nothing else is applied.That function's own doc comment in
ec/cookies.rssays the strict check is the one used to reject untrusted request values before they enter the system. So the lax backstop is being used where the strict path was intended, and the code contradicts its own documentation.There is a second half worth knowing.
x-ts-ecis inINTERNAL_HEADERS, so it is stripped from responses, but it is not inSPOOFABLE_FORWARDED_HEADERS, so it is not stripped from inbound requests. The header is therefore the client's to set, and the raw reader prefers it over the cookie.What the correct check is
Not the built-in strict shape check. A vendor provider's identifier is not required to match the built-in
{64 hex}.{6 alphanumeric}form, so holding every deployment to it would drop exactly the opaque identifiers a pluggable provider exists to carry.The correct check is ownership. Dispatch on the
{code}~prefix to the provider that owns it, and let that provider decide whether it accepts the value. That is what the identity lifecycle already does on read-back.How it is fixed in this stack
The raw reader cannot make that judgement, because it has neither the settings nor the selected provider, so it is no longer public. The only way in from outside the module is the checked path. Nothing outside the crate called the old name.
The test drives three identifiers this deployment could never have issued, being a value of the wrong shape entirely, a correctly shaped value carrying another deployment's provider code, and a value claiming the built-in code without the built-in shape. It shows the character bounds accept all three, and requires the public path to recognize none of them, while a genuinely owned identifier still returns. Replacing the ownership check with the bounds fails it.
Produced with AI assistance and needs human review. Verified against
upstream/main.