Skip to content

CAS: let browsers cache downloads (Authorization header, ETag, 304) #3549

Description

@jiparis

Problem

A browser cannot keep a copy of a CAS download. Each open of the same artifact downloads it again in full, and the CAS copies the object from the storage backend each time. For large artifacts, such as AI coding session transcripts, this cost is high.

There are two causes:

  • The download URL holds a new token in the query (?t=<token>) on each call. The browser cache uses the URL as its key, so it never finds a match.
  • The download response has no ETag and no Cache-Control header. The browser cannot check its copy with the CAS.

Proposal

Change the CAS download endpoint (/download/<digest>):

  1. Accept the token in an Authorization: Bearer header. Today the endpoint reads the token only from the t query parameter. It must accept the token from the header or from the query. Clients that use the query token keep working.
  2. Send cache headers on the download response:
    • ETag: "<digest>", for example "sha256:abc...". Put the value in quotes. Without the quotes, browsers can ignore the ETag. The digest identifies the bytes, so the ETag never goes stale.
    • Cache-Control: private, no-cache. The browser keeps the copy, but it checks with the CAS before each use. Shared proxies do not store it.
  3. Answer a conditional request with 304. When the request has If-None-Match with the digest, the CAS does these steps:
    1. It checks the token.
    2. It checks that the object exists in the storage backend, with a metadata call. It does not copy the object.
    3. It answers 304 Not Modified with no content. The 304 also has the ETag and Cache-Control headers.

The CORS preflight already allows Authorization and sends Access-Control-Max-Age: 86400, so no CORS change is necessary.

sequenceDiagram
    participant B as Browser
    participant CAS
    participant S as Storage backend
    alt First open
        B->>CAS: GET /download/digest (Authorization)
        CAS->>S: Copy object, check digest
        CAS-->>B: 200, ETag, Cache-Control private no-cache
    else Next open
        B->>CAS: GET /download/digest (Authorization, If-None-Match)
        CAS->>CAS: Check token
        CAS->>S: Check that the object exists
        CAS-->>B: 304 Not Modified
    end
Loading

Acceptance criteria

  • A request with a valid token in Authorization: Bearer and no t query parameter downloads the artifact.
  • A request with the t query parameter works as before.
  • The 200 response has ETag: "<digest>" and Cache-Control: private, no-cache.
  • A request with a valid token and a matching If-None-Match gets a 304 with no content. The CAS does not copy the object from the storage backend.
  • A request with a matching If-None-Match and a bad or missing token gets a 401.
  • A request with a matching If-None-Match for an object that is not in the storage backend does not get a 304.

Activity

  1. self-assigned this
    on Oct 7, 2026
  2. jiparis commented on Oct 7, 2026

    @jiparis
    MemberAuthor

    Spec: #3552

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions