pingdotgg / pingdotgg/t3code

[Bug]: Compressed asset responses lose their Content-Type, so HTML previews render as plain text

Open Beginner friendly
#10,935 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug upstream via-triage
Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Before submitting
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.
Area

apps/server

Steps to reproduce
  1. Run the server from main at or after bd56e920b7 (the Effect rc.112 upgrade, #10652). Reproduced against a dev server from 7220dfe2c; the relevant code is unchanged on main as of 6c583620ff.
  2. Get a signed asset URL for an .html workspace file larger than 1 KiB (assets.createUrl with a workspace-file resource, which is what opening an HTML file in the file viewer does) and request it the way a browser does:
    curl -sD - -o /dev/null -H 'Accept-Encoding: gzip, deflate, br, zstd' 'http://127.0.0.1:<port>/api/assets/<token>/page.html'
    
  3. Request the same URL without Accept-Encoding.
Expected behavior

Both responses carry content-type: text/html; charset=utf-8 and the sandbox CSP that assetResponseHeaders sets, and the browser renders the page.

Actual behavior

Step 2 answers 200 with content-encoding: br, x-content-type-options: nosniff and the CSP header, and no content-type at all. Step 3 answers with content-type: text/html; charset=utf-8. Because of nosniff, Chromium shows the HTML source as plain text, and a .css file served the same way is refused as a stylesheet. Images are unaffected (the middleware does not compress them) and so are bodies under 1 KiB.

The tracer records headers before the compression handler runs, so http.server GET spans still show text/html for a response that reached the client without a type.

Impact

Major degradation: every HTML, CSS, JS and PDF file the asset route serves to a browser that accepts compression, which is every browser.

Cause

httpCompressionLayer in apps/server/src/http.ts is Effect's HttpMiddleware.compression(). In effect 4.0.0-rc.112 the Node implementation rebuilds a file response around a body that carries no content type, and HttpServerResponse.setBody then strips the header. HttpServerResponse.text(..., { contentType }) survives because its body carries the type; HttpServerResponse.file puts the type in the headers only. Details, a minimal reproduction and a suggested fix are in the Effect issue: https://github.com/Effect-TS/effect/issues/8146

Workaround

Append no-transform to the asset Cache-Control value in assetResponseHeaders. The middleware honours the directive and asset responses go out uncompressed with their type. Two expectations in http.test.ts pin the exact header value and need the same change.

Contributor guide

Open the contributing guide

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.

Research direction

Start in apps/server/src/http.ts by reading httpCompressionLayer and assetResponseHeaders, then inspect the two related expectations in http.test.ts. Reproduce an asset request with and without Accept-Encoding and update the handling so compressed HTML, CSS, JS, and PDF responses retain their content type; the tests and browser response headers should confirm the fix.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
84/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.