mirror of
https://github.com/Comfy-Org/ComfyUI.git
synced 2026-08-05 18:05:08 +08:00
Fix SVG previews broken by the stored-XSS forced-download (#15149)
* Fix SVG previews broken by the stored-XSS forced-download /view and the assets download route force every SVG to application/octet-stream + attachment. That blocks the stored XSS from GHSA-779p-m5rp-r4h4, but it also breaks the SVG node output and Media Assets previews, which request the file with a plain <img>. Exempt only that case. An SVG referenced by an <img> loads in secure static mode with scripting and external references disabled, so the payload cannot fire. The attack needs the SVG to become a document, which arrives with a different Sec-Fetch-Dest. Browsers set that header themselves and page script cannot override it. A missing header, from a non-browser client or a proxy that strips it, fails closed. The blocklist itself is unchanged; this is a call-site gate. * Don't let a cache replay the inline SVG into document context The Sec-Fetch-Dest exemption makes /view and the assets content route vary their Content-Type and Content-Disposition on a request header, but neither response said so. FileResponse emits Last-Modified/ETag and the cache_control middleware skips /view (the filename is in the query string, not the path), so the inline image/svg+xml variant is heuristically cacheable. A cache keyed on the URL alone could hand an entry primed by an <img> load to a later top-level navigation of the same URL, turning the SVG back into a document and re-enabling the stored XSS the forced download blocks. Set Vary: Sec-Fetch-Dest and Cache-Control: no-store on both branches, not just the exempt one: a cached attachment replayed to an <img> would re-break the preview this fix exists to restore. Also strip parameters from content_type before building the assets response. mime_type there is uploader-supplied and unvalidated, and aiohttp rejects a charset in the content_type argument with ValueError, so a stored "image/svg+xml; charset=utf-8" turned a valid inline SVG into a 500. Route-level guards now pin the headers on both branches and the parameterised mime type; all three fail against the previous commit.
This commit is contained in:
@@ -306,6 +306,23 @@ def is_dangerous_content_type(content_type: str | None) -> bool:
|
||||
return normalized.endswith('+xml') or normalized.endswith('/xml')
|
||||
|
||||
|
||||
def renders_safely_as_image(content_type: str | None, sec_fetch_dest: str | None) -> bool:
|
||||
"""Return True if a dangerous `content_type` is safe to serve inline anyway.
|
||||
|
||||
An SVG referenced by an ``<img>`` is loaded in secure static mode: scripts
|
||||
and external references are disabled, so the stored XSS that
|
||||
``is_dangerous_content_type`` guards against cannot fire. The attack needs
|
||||
the SVG to become a document, which is a separate ``Sec-Fetch-Dest``.
|
||||
Browsers set that header themselves and script cannot override it (the
|
||||
``Sec-`` prefix makes it a forbidden header name), so it is trustworthy for
|
||||
this decision. Anything else, including a missing header from a non-browser
|
||||
client or a proxy that strips it, fails closed.
|
||||
"""
|
||||
if sec_fetch_dest != 'image':
|
||||
return False
|
||||
return (content_type or '').split(';', 1)[0].strip().lower() == 'image/svg+xml'
|
||||
|
||||
|
||||
def is_within_directory(directory: str, target: str) -> bool:
|
||||
"""Return True if `target` resolves to a path inside `directory`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user