From a4c8d31cb8b758ef01a06aad1424314d0b3c68ad Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 12 Oct 2026 02:49:55 -0700 Subject: [PATCH] s3api: ignore response header overrides on anonymous requests AWS requires response-* query parameters to be signed; an anonymous caller must not choose how a stored object is served. --- weed/s3api/s3api_object_handlers.go | 6 +++-- ...i_object_handlers_conditional_read_test.go | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index c3da49513..f5e6b546c 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -2430,8 +2430,10 @@ func (s3a *S3ApiServer) setResponseHeaders(w http.ResponseWriter, r *http.Reques // Apply S3 passthrough headers from query parameters // AWS S3 supports overriding response headers via query parameters like: // ?response-cache-control=no-cache&response-content-type=application/json - // This allows presigned URLs to control how browsers handle the downloaded content - if r != nil { + // This allows presigned URLs to control how browsers handle the downloaded content. + // AWS requires the request to be signed; anonymous requests must not influence + // how the stored object is served. + if r != nil && getRequestAuthType(r) != authTypeAnonymous { for queryParam, headerValue := range r.URL.Query() { if normalizedHeader, ok := s3_constants.PassThroughHeaders[strings.ToLower(queryParam)]; ok && len(headerValue) > 0 && headerValue[0] != "" { w.Header().Set(normalizedHeader, headerValue[0]) diff --git a/weed/s3api/s3api_object_handlers_conditional_read_test.go b/weed/s3api/s3api_object_handlers_conditional_read_test.go index 1c4dd2dcd..71e9acd94 100644 --- a/weed/s3api/s3api_object_handlers_conditional_read_test.go +++ b/weed/s3api/s3api_object_handlers_conditional_read_test.go @@ -238,3 +238,30 @@ func TestRemoteObjectETagFallback(t *testing.T) { t.Errorf("local object ETag = %q", got) } } + +func TestSetResponseHeadersIgnoresOverridesForAnonymous(t *testing.T) { + s3a := &S3ApiServer{} + entry := &filer_pb.Entry{ + Name: "object", + Attributes: &filer_pb.FuseAttributes{FileSize: 8, Mtime: 100}, + } + for name, url := range map[string]string{ + "anonymous": "/bucket/object?response-content-type=text%2Fhtml&response-content-disposition=inline", + "signed": "/bucket/object?response-content-type=text%2Fhtml&response-content-disposition=inline", + "presigned": "/bucket/object?X-Amz-Credential=k&response-content-type=text%2Fhtml", + } { + r := httptest.NewRequest(http.MethodGet, url, nil) + if name == "signed" { + r.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=k") + } + w := httptest.NewRecorder() + s3a.setResponseHeaders(w, r, entry, 8) + if name == "anonymous" { + if got := w.Header().Get("Content-Disposition"); got != "" { + t.Errorf("anonymous request must not override response headers, got Content-Disposition %q", got) + } + } else if got := w.Header().Get("Content-Type"); got != "text/html" { + t.Errorf("%s request should apply response overrides, got Content-Type %q", name, got) + } + } +}