Skip to content

fix: keep the endpoint path out of the Host header - #474

Open
eastriverlee wants to merge 1 commit into
durch:masterfrom
eastriverlee:fix/host-header-authority
Open

fix: keep the endpoint path out of the Host header#474
eastriverlee wants to merge 1 commit into
durch:masterfrom
eastriverlee:fix/host-header-authority

Conversation

@eastriverlee

@eastriverlee eastriverlee commented Aug 14, 2026

Copy link
Copy Markdown

Fixes #473.

Region::Custom accepts an endpoint that carries a path, and Region::host keeps that path so Bucket::url can place the bucket and key underneath it. host_header returned the same string, so the path travelled in the Host header as well:

PUT /storage/v1/s3/media/0000….bin HTTP/1.1
host: project.supabase.co/storage/v1/s3

RFC 9110 §7.2 defines Host as uri-host [ ":" port ]. A server that checks answers 400 Bad Request with no body, before any signature is verified, so every request to such an endpoint fails and nothing says why. Supabase Storage's S3 endpoint is https://<project>.supabase.co/storage/v1/s3 and is unusable for this reason. AWS, MinIO and R2 have no path in their endpoints, so nothing had exercised the case.

What changed

host_header now takes the authority. Region::host is untouched, so the request line keeps the endpoint path it needs, and Bucket::url, path_style_host and subdomain_style_host behave as before. host_header feeds the HOST header only (request_trait.rs:406, :729), so nothing else sees the narrower value.

Verification

Against a Supabase Storage bucket, before and after, with the same 300 KB object:

before after
put_object_with_content_type 400 Bad request 200
head_object unreachable 200, content_length=300000
get_object unreachable 200, 300000 bytes
get_object_range(0, 1023) unreachable 206, 1024 bytes
get_object_range(100_000, 200_000) unreachable 206, 100001 bytes

MinIO passes the same five before and after.

cargo fmt --all -- --check is clean, clippy is clean for with-tokio,tokio-rustls-tls, and cargo test --lib passes 61 with 20 ignored. Two unit tests cover the split.


This change is Reviewable

`Region::Custom` accepts an endpoint that carries a path, and `Region::host`
keeps that path so `Bucket::url` can place the bucket and key underneath it.
`host_header` returned the same string, so the path travelled in the `Host`
header as well:

    PUT /storage/v1/s3/media/0000.bin HTTP/1.1
    host: project.supabase.co/storage/v1/s3

RFC 9110 §7.2 defines `Host` as `uri-host [ ":" port ]`. A server that checks
answers `400 Bad Request` with no detail, which makes every request to such an
endpoint fail and says nothing about why.

Supabase Storage's S3 endpoint is `https://<project>.supabase.co/storage/v1/s3`
and is unusable for this reason: `put_object` fails before any signature is
verified. AWS, MinIO and R2 have no path in their endpoints, so nothing here
had exercised the case.

`host_header` now takes the authority and leaves `Region::host` alone, so the
request line keeps the endpoint path it needs. Verified against a Supabase
Storage bucket, where `put_object`, `head_object`, `get_object` and
`get_object_range` all succeed after the change and only the last three are
reachable before it, and against MinIO, which is unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Host header carries the endpoint path, so any S3 endpoint with a path prefix fails

1 participant