Skip to content

Inbound Edge Cookie identifiers are checked with the outbound backstop, so another deployment's identifier is accepted #1095

Description

@jwrosewell

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions