Repository navigation
Conversation
SVG is listed in allowedUploadMimeTypes for every upload backend except imgur, but since the magic number check from 7a88f9d ("Check upload image mime type", released in 2.3.0) every SVG upload is rejected with 403: image-type cannot detect SVG because it is plain XML without a magic number. Handle SVG uploads separately: sanitize them with DOMPurify (SVG profile) on a jsdom window and replace the temporary upload with the sanitized, re-serialized XML before it is handed to the upload backend. Files that do not consist of a single <svg> root element, or that are larger than 2 MiB, are rejected. As defense in depth, files served from /uploads get X-Content-Type-Options: nosniff, and SVG files additionally get a restrictive Content-Security-Policy with sandbox, so an SVG opened directly can neither run scripts nor load external resources. Signed-off-by: Markus Consoir <m.consoir@fz-juelich.de>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SVG uploads have been rejected with
403 Forbiddensince 2.3.0, althoughimage/svg+xmlis listed inallowedUploadMimeTypesfor all upload backends except imgur. This PR makes SVG uploads work again and sanitizes them so they cannot be used for stored XSS.Problem
checkImageValid()inlib/imageRouter/index.js(added in 7a88f9d, "Check upload image mime type") detects the file type from its first bytes withimage-type. SVG is plain XML and has no magic number, soimageType()always returnsnulland every SVG upload fails.Simply skipping the check is not an option: an uploaded SVG is served from the CodiMD origin, and opening
/uploads/<file>.svgdirectly would execute any embedded script in the context of the CodiMD session.Changes
lib/imageRouter/sanitizeSvg.js(new): sanitizes SVG source with DOMPurify (SVG and SVG filter profiles) on a jsdom window and re-serializes the single<svg>root element as standalone XML.<script>, event handlers,javascript:URLs,<foreignObject>, animation-based attribute injection and everything else outside the SVG profile.XMLSerializeralways yields well-formed XML with the SVG namespace, which<img>requires (e.g. for files withoutxmlnsor with HTML entities like ).nullunless the input consists of exactly one<svg>root element.lib/imageRouter/index.js: files with an.svgextension skip the magic number check. They are sanitized instead, and the temporary upload is replaced with the sanitized version before it reaches the upload backend, so this works for filesystem, S3, MinIO, Azure and Lutim. Uploads larger than 2 MiB are rejected. The handling of all other formats is unchanged.app.js:/uploadsresponses getX-Content-Type-Options: nosniff. SVG files additionally getContent-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandboxas a second line of defense.dompurify(already in the lockfile as a transitive dependency) andjsdom@20, which still supports Node 14–16. Both are server-side only and do not affect the frontend bundle.Testing
test/sanitizeSvg.js. They parse the output as XML, the way browsers load an SVG image.npm run lintand the full mocha suite pass on Node 16.20 (137 passing).POST /uploadimagewith the filesystem backend:onload,<script>,onclick, anxlink:href="javascript:…"link and<foreignObject><iframe>is stored without any of them, and the shapes and text remain.xmlnsand with is stored as valid XML with the namespace added..svgis rejected with 403, and so is a 3 MB SVG.GET /uploads/<file>.svgreturnsContent-Type: image/svg+xml,X-Content-Type-Options: nosniffand the CSP header.Notes
nosniffheaders only apply when files are served by CodiMD itself (imageUploadType: filesystem). With S3, MinIO or Azure, the sanitizing still applies, but the headers would have to be configured on the bucket or CDN.allowedUploadMimeTypes.<use>: DOMPurify removes<use>elements by default, even with local references. Sprite-based icons therefore lose those parts. I kept the stricter default; allowing local#idreferences only would be possible with a DOMPurify hook if you prefer that.