Skip to content

fix(security): force attachment disposition for SVG uploads#3023

Open
tomqiaozc wants to merge 1 commit into
multica-ai:mainfrom
tomqiaozc:security/svg-xss-attachment-disposition
Open

fix(security): force attachment disposition for SVG uploads#3023
tomqiaozc wants to merge 1 commit into
multica-ai:mainfrom
tomqiaozc:security/svg-xss-attachment-disposition

Conversation

@tomqiaozc
Copy link
Copy Markdown
Contributor

Summary

Fixes #3022.

Uploaded SVG files were served with Content-Disposition: inline because isInlineContentType returned true for any image/* MIME type. SVG is XML — <script>, <foreignObject>, and onload= execute when rendered inline — so a workspace member could upload a crafted .svg, share its attachment URL, and any clicker would execute attacker-controlled JS in the application's first-party origin (cookie theft, authenticated API calls).

This PR excludes image/svg+xml from isInlineContentType, so the local and S3 storage backends both serve SVG with Content-Disposition: attachment. Browsers do not run scripts in downloaded files.

Why this approach

  • Single-file behavior change in a shared helper used by both storage backends — guaranteed coverage.
  • No upload-time rejection: existing SVG attachments (logos, diagrams) keep working; they download instead of rendering inline.
  • A full SVG sanitizer would be a larger, separate change. Forcing attachment is the minimal sufficient fix.

Test plan

  • New server/internal/storage/util_test.go covers isInlineContentType with image/svg+xmlfalse and the existing inline types → true.
  • Extended TestLocalStorage_ServeFile_ContentDispositionFromSidecar with an SVG case asserting Content-Disposition: attachment; filename="logo.svg".
  • make test (reviewer-side; my local environment lacks Go).

SVG files are XML and can carry <script>, <foreignObject>, or onload=
attributes that execute in the document's origin when rendered inline.
The upload handler maps .svg to image/svg+xml, and storage backends
(local + S3) previously set Content-Disposition: inline based on the
image/ prefix in isInlineContentType. A workspace member could upload
a crafted SVG, share its attachment URL in an issue or comment, and any
teammate who clicks the link would execute attacker-controlled JS in
the application's first-party origin (reading auth cookies, posting to
authenticated endpoints).

Exclude image/svg+xml from isInlineContentType so both storage paths
serve SVG with Content-Disposition: attachment.

Test coverage:
- New util_test.go covers the inline/attachment matrix including SVG.
- Existing local_test.go ContentDisposition table gains an SVG case.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

@tomqiaozc is attempting to deploy a commit to the IndexLabs Team on Vercel.

A member of the Team first needs to authorize it.

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.

Security: stored XSS via SVG upload (Content-Disposition: inline on image/svg+xml)

1 participant