[Bug]: Compressed asset responses lose their Content-Type, so HTML previews render as plain text
Nobody has claimed this yet.
- 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
- Run the server from
mainat or after bd56e920b7 (the Effect rc.112 upgrade, #10652). Reproduced against a dev server from 7220dfe2c; the relevant code is unchanged onmainas of 6c583620ff. - Get a signed asset URL for an
.htmlworkspace file larger than 1 KiB (assets.createUrlwith aworkspace-fileresource, 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' - 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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