From 0e165edfb1ffaa3da83aef1df97599b1106abff2 Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Fri, 29 May 2026 20:13:17 -0700 Subject: [PATCH] fix: expose x-amz-storage-class in CORS response headers The object info modal in the WebUI was always displaying STANDARD as the storage class regardless of the actual value. The root cause is a browser CORS restriction: when the WebUI makes a cross-origin HEAD request to the S3 endpoint, the browser silently drops any response header not listed in Access-Control-Expose-Headers, causing response.headers.get('x-amz-storage-class') to return null and the UI to fall back to the hardcoded STANDARD default. Adding x-amz-storage-class to the default set of exposed headers ensures the browser makes it available to JavaScript, allowing storage classes such as GLACIER to be correctly reflected in the UI. --- s3api/controllers/cors_default_origin_test.go | 4 ++-- s3api/middlewares/apply-default-cors.go | 2 +- s3api/middlewares/apply-default-cors_test.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/s3api/controllers/cors_default_origin_test.go b/s3api/controllers/cors_default_origin_test.go index 91bc7983d..761aa5514 100644 --- a/s3api/controllers/cors_default_origin_test.go +++ b/s3api/controllers/cors_default_origin_test.go @@ -55,8 +55,8 @@ func TestApplyBucketCORS_FallbackOrigin_NoBucketCors_NoRequestOrigin(t *testing. if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin { t.Fatalf("expected Access-Control-Allow-Origin to be set to fallback, got %q", got) } - if got := resp.Header.Get("Access-Control-Expose-Headers"); got != "ETag" { - t.Fatalf("expected Access-Control-Expose-Headers to include ETag, got %q", got) + if got := resp.Header.Get("Access-Control-Expose-Headers"); got != "ETag, x-amz-storage-class" { + t.Fatalf("expected Access-Control-Expose-Headers to include ETag and x-amz-storage-class, got %q", got) } } diff --git a/s3api/middlewares/apply-default-cors.go b/s3api/middlewares/apply-default-cors.go index c7c055234..e414d47ee 100644 --- a/s3api/middlewares/apply-default-cors.go +++ b/s3api/middlewares/apply-default-cors.go @@ -22,7 +22,7 @@ import ( func ensureExposeETag(ctx *fiber.Ctx) { existing := strings.TrimSpace(string(ctx.Response().Header.Peek("Access-Control-Expose-Headers"))) - defaults := []string{"ETag"} + defaults := []string{"ETag", "x-amz-storage-class"} if existing == "" { ctx.Response().Header.Add("Access-Control-Expose-Headers", strings.Join(defaults, ", ")) return diff --git a/s3api/middlewares/apply-default-cors_test.go b/s3api/middlewares/apply-default-cors_test.go index 0c0474d87..e1597c80b 100644 --- a/s3api/middlewares/apply-default-cors_test.go +++ b/s3api/middlewares/apply-default-cors_test.go @@ -43,8 +43,8 @@ func TestApplyDefaultCORS_AddsHeaderWhenOriginSet(t *testing.T) { if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin { t.Fatalf("expected fallback origin header, got %q", got) } - if got := resp.Header.Get("Access-Control-Expose-Headers"); got != "ETag" { - t.Fatalf("expected expose headers to include ETag, got %q", got) + if got := resp.Header.Get("Access-Control-Expose-Headers"); got != "ETag, x-amz-storage-class" { + t.Fatalf("expected expose headers to include ETag and x-amz-storage-class, got %q", got) } }