Skip to content

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

Description

@eastriverlee

Describe the bug

Region::Custom accepts an endpoint that carries a path. Region::host keeps that path, which Bucket::url needs so it can place the bucket and key underneath it. Request::host_header returns the same string, so the path ends up in the Host header too:

PUT /storage/v1/s3/media/0000000000000000000000000000000000000000000000000000000000000000.bin HTTP/1.1
host: 127.0.0.1:54321/storage/v1/s3
content-length: 300000
content-type: application/octet-stream
x-amz-content-sha256: 3c65ea93424a9c362fec0e3a69ea36031e8a358441479dd665cc6110eabe7b08
x-amz-date: 20260814T215409Z
content-md5: NPrfKXWDTpo1fsQdPm3wZw==
authorization: AWS4-HMAC-SHA256 Credential=…,SignedHeaders=content-length;content-md5;content-type;host;x-amz-content-sha256;x-amz-date,Signature=…

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 is affected: its S3 endpoint is https://<project>.supabase.co/storage/v1/s3, and no operation works. AWS, MinIO and R2 put no path in their endpoints, which is why this has gone unnoticed.

To Reproduce

let region = Region::Custom {
    region: "local".to_string(),
    endpoint: "http://127.0.0.1:54321/storage/v1/s3".to_string(),
};
let bucket = Bucket::new("media", region, credentials)?.with_path_style();
bucket.put_object_with_content_type("probe.bin", &content, "application/octet-stream").await
// Err: Got HTTP 400 with content 'Bad request'

The request itself is well formed apart from the header. Signing the same bytes by hand and sending them with Host: 127.0.0.1:54321 returns 200, as do ranged and unranged GETs afterwards.

Expected behavior

The Host header carries the authority, and the endpoint path stays in the request line where Bucket::url put it.

Environment

  • Rust version: 1.95
  • lib version: 0.37.2

Additional context

host_header is used only for the HOST header (request_trait.rs:406 and :729), so taking the authority there leaves URL construction untouched. Changing Region::host instead would break Bucket::url, which depends on the path being present.

PR: #474

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions