Skip to content

fix(id-web): safe blob content types — no caller-chosen text/html (#908) - #919

Merged
dezren39 merged 1 commit into
mainfrom
sweep/908-blob-safe-content-type
Oct 25, 2026
Merged

dezren39 merged 1 commit into
mainfrom
sweep/908-blob-safe-content-type

Conversation

@dezren39

@dezren39 dezren39 commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Fixes #908.

Problem

GET /blob/{hash} derived its Content-Type from the caller-supplied ?filename= parameter and sent no protective headers, so any stored blob (including content synced from peers) could be served as text/html from the id web origin — same-origin XSS by link, persisted by the immutable year-long cache header.

Fix (pkgs/id/src/web/routes.rs only)

  • Allowlist, not caller choice: new safe_blob_content_type keeps the filename-implied type only for passive types (images incl. SVG, video, audio, PDF, plain/markdown text) served inline; everything else (html/css/js/json/xml/unknown) becomes application/octet-stream + Content-Disposition: attachment.
  • Protective headers on every blob response: X-Content-Type-Options: nosniff and Content-Security-Policy: default-src 'none'; sandbox — an SVG cannot execute on top-level navigation, while <img> embedding of the upload URL keeps working.
  • Upload masquerade closed: upload_handler reconciles a supplied filename's extension with the validated content type (evil.html declared image/png is stored as evil.png; jpeg accepts jpg/jpeg) and percent-encodes the filename in the returned URL; images_list_handler encodes its blob URLs the same way.

Tests

New tests in upload_router_tests drive the real router end-to-end (upload → GET /blob):

  • ?filename=evil.html on a PNG blob → application/octet-stream + attachment + nosniff + sandbox CSP, bytes unchanged
  • PNG → inline image/png, no disposition, protective headers present
  • SVG → image/svg+xml with default-src 'none'; sandbox CSP + nosniff
  • upload named evil.html/image/png → stored/returned as evil.png
  • pure unit tests pin safe_blob_content_type and reconcile_upload_filename

Local verification: rustfmt (edition 2024) clean. Per the sandbox build rule no local cargo compile was run (id lib-test link OOMs in the sandbox); CI is the test executor — merge after the flake check leg executes these tests green.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

blob_handler derived its Content-Type from the caller-supplied
?filename= parameter and sent no protective headers, so any stored
blob could be served as text/html from the id origin (same-origin
XSS by link).

- safe_blob_content_type: only passive types (images incl. SVG,
  video, audio, PDF, plain/markdown text) are served inline with
  their type; everything else is application/octet-stream +
  Content-Disposition: attachment.
- Every blob response now carries X-Content-Type-Options: nosniff
  and Content-Security-Policy: default-src 'none'; sandbox, so an
  SVG cannot execute on top-level navigation while <img> embedding
  keeps working.
- upload_handler reconciles a supplied filename's extension with
  the validated content type (a PNG named evil.html is stored as
  evil.png) and percent-encodes the filename in the returned URL;
  images_list_handler encodes its blob URLs the same way.
- Regression tests in upload_router_tests drive the real router:
  ?filename=evil.html is not served as HTML, PNG stays inline
  image/png with protective headers, SVG carries the sandbox CSP,
  and a masquerading upload name is reconciled.
@dezren39
dezren39 requested a review from a team as a code owner October 25, 2026 22:17
@coderabbitai

coderabbitai Bot commented Oct 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 55 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fc96990b-a500-4421-93c1-0e45aa4fcc06
Commits

Reviewing files that changed from the base of the PR and between 21b7621 and 9771838.

Files selected for processing: 1
  • pkgs/id/src/web/routes.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-25T22:19:38.071698Z 9771838 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97718382e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkgs/id/src/web/routes.rs
.header(header::X_CONTENT_TYPE_OPTIONS, "nosniff")
.header(
header::CONTENT_SECURITY_POLICY,
"default-src 'none'; sandbox",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow inline SVG styles while retaining script restrictions

For uploaded or synced SVGs that use <style> blocks or style attributes, this policy blocks their styling when the blob is opened directly, changing colors, hiding strokes, or making diagrams unreadable. With no explicit style-src, it falls back to default-src 'none' under the CSP specification. SVG remains an explicitly supported inline type, so preserve its non-executable styling, for example with style-src 'unsafe-inline', while retaining the sandbox and script restrictions. The new <svg/> response-header test cannot detect this rendering regression.

Useful? React with 👍 / 👎.

@dezren39
dezren39 merged commit fc9e078 into main Oct 25, 2026
27 checks passed
@dezren39
dezren39 deleted the sweep/908-blob-safe-content-type branch October 25, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

id(web): /blob serves any blob as text/html via the ?filename= parameter — no CSP/nosniff/disposition headers (same-origin XSS by link)

1 participant