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
Describe the bug
Region::Customaccepts an endpoint that carries a path.Region::hostkeeps that path, whichBucket::urlneeds so it can place the bucket and key underneath it.Request::host_headerreturns the same string, so the path ends up in theHostheader too:RFC 9110 §7.2 defines
Hostasuri-host [ ":" port ]. A server that checks answers400 Bad Requestwith 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
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:54321returns200, as do ranged and unrangedGETs afterwards.Expected behavior
The
Hostheader carries the authority, and the endpoint path stays in the request line whereBucket::urlput it.Environment
1.950.37.2Additional context
host_headeris used only for theHOSTheader (request_trait.rs:406and:729), so taking the authority there leaves URL construction untouched. ChangingRegion::hostinstead would breakBucket::url, which depends on the path being present.PR: #474