REST downloads from the S3 backend drop ETag and Cache-Control on 304 responses

Open
#1,649 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@hsusul is already working on this.

Since Nov 3, 2026.

  • #1653 by @hsusul — open

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
aws, typescript
Domain
api, backend

Research direction

Start with Renderer.render() and its error branch, then compare the S3 backend’s 304 handling with the file backend’s setHeaders() path and the existing S3 protocol GetObject handling. Reproduce with a stub upstream returning 304 and ETag, Cache-Control, and Last-Modified; done means the REST download’s 304 preserves the headers expected for the corresponding 200 response. Pull request #1653 is already open for this issue.

Written by the indexing model from the issue text.

Description

Bug report

With the S3 storage backend, a REST object download that revalidates with If-None-Match gets a bare 304 Not Modified. The response has no ETag, Cache-Control or Last-Modified. RFC 9110 section 15.4.5 requires a 304 to carry the ETag and Cache-Control that a 200 would have sent.

The AWS SDK throws on a 304, so Renderer.render() handles it in its error branch. That branch only sets Vary before it sends the 304. The file backend returns its 304 as a value, so it goes through setHeaders() and keeps these headers. The S3 protocol GetObject path already copies them from the SDK error's raw response.

To reproduce

  1. Run Storage with STORAGE_BACKEND=s3 and upload bucket/object.txt to a public bucket.
  2. GET /object/public/bucket/object.txt and note the ETag.
  3. Send the same request with If-None-Match: <that ETag>.

A unit test that points S3Backend at a stub upstream, which answers the conditional request with 304 plus ETag, Cache-Control and Last-Modified, gets this at upstream b74897cf:

statusCode: 304
etag: undefined

Expected behavior

The 304 carries the upstream ETag and Last-Modified, and the same Cache-Control (or signed URL Expires) that the 200 response would have. Browsers and CDNs then keep the stored validator and refresh its freshness instead of revalidating on every request.

Scope

This covers the REST renderer for downloads through the S3 backend. #1511/#1512 cover response overrides on S3 protocol 304s, and #1607 covers HeadObject conditionals.

Dominant language
TypeScript
Stars
1.3k
Forks
318
Avg merge
1d 14h
Merged PRs (30d)
106

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from supabase/storage

All issues in supabase/storage

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.