isInlineContentType is the security boundary that decides whether an
uploaded file is served with Content-Disposition: inline (renderable
in the document origin) or attachment. The SVG carve-out added in
#3023 to block stored-XSS via uploaded .svg only matched the exact
literal "image/svg+xml", so callers that supply "IMAGE/SVG+XML",
"image/svg+xml; charset=utf-8", or whitespace-padded variants would
still see disposition=inline. MIME type matching is case-insensitive
per RFC 2045 §5.1 and may carry parameters, so the safe thing is to
normalize at the boundary instead of trusting every caller.
Today both call sites (S3.Upload and LocalStorage.Serve) happen to
feed in the exact literal because the upload handler overrides .svg
to "image/svg+xml" before storage sees it, so this is defense-in-depth
rather than a live regression. Hardens the helper so any future caller
(including one that ever trusts a client-supplied Content-Type) stays
behind the same guard.
Co-authored-by: multica-agent <github@multica.ai>
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.
Co-authored-by: Tom Qiao <tomqiaozc@users.noreply.github.com>
- Rename `filepath` local var to `dest` in LocalStorage.Upload to avoid
shadowing the path/filepath package import
- Remove unused detectContentType and overrideContentType functions from
util.go (no longer needed after ServeFile switched to http.ServeFile)
* feat(storage): add local file storage fallback
- Add local storage implementation for file uploads
- Update .env.example with LOCAL_UPLOAD_DIR and LOCAL_UPLOAD_BASE_URL
- Integrate local storage into server router and handlers
- Add storage abstraction layer with util functions
* ♻️ refactor(storage): improve path handling and file serving
switch from path to filepath for better cross-platform support and replace manual file serving logic with http.ServeFile to enhance security against path traversal. update unit tests to use t.Setenv for cleaner environment variable management.