CVE-2025-32391 Overview
CVE-2025-32391 is a stored cross-site scripting (XSS) vulnerability [CWE-79] in HedgeDoc, an open source real-time collaborative markdown notes application. Versions prior to 1.10.3 allow an authenticated user to upload a malicious SVG file that executes script when opened in a new browser tab. The attack exploits the JSONP capabilities of GitHub Gist embeddings that were permitted by the application's Content Security Policy. Only instances using the local filesystem upload backend, or configurations where uploads are served from the same origin as HedgeDoc, are affected. The vulnerability is fixed in HedgeDoc 1.10.3.
Critical Impact
An authenticated attacker can upload a crafted SVG that runs arbitrary JavaScript in the origin of the HedgeDoc instance, enabling session theft and note tampering against other users.
Affected Products
- HedgeDoc versions prior to 1.10.3
- HedgeDoc instances using the local filesystem upload backend
- HedgeDoc instances serving uploads from the same domain as the application
Discovery Timeline
- 2025-04-10 - CVE-2025-32391 published to NVD
- 2026-06-17 - Last updated in NVD database
Technical Details for CVE-2025-32391
Vulnerability Analysis
HedgeDoc allows users to upload files, including SVG images, which are then served from the /uploads route. When such an upload is opened directly in a new browser tab rather than embedded through the editor, the browser renders the SVG as an active document within the HedgeDoc origin. Because SVG files can carry inline <script> elements, the file executes JavaScript in the security context of the HedgeDoc site.
The application's Content Security Policy (CSP) was intended to constrain script execution, but its script-src directive permitted https://gist.github.com/. GitHub Gist supports JSONP-style script inclusion, letting attacker-controlled JavaScript be fetched from a trusted script source. The uploaded SVG chains these two behaviors to bypass CSP and run arbitrary code against authenticated sessions.
Root Cause
The root cause is twofold. First, files under /uploads were served without a Content-Disposition: attachment header and without a restrictive per-response CSP, allowing browsers to render SVG uploads inline. Second, the global CSP whitelisted external script origins that offered JSONP endpoints, converting a same-origin file-viewing behavior into a full script injection primitive.
Attack Vector
An authenticated user uploads a crafted SVG containing script markup that references a Gist-hosted JSONP endpoint. The attacker distributes the direct /uploads/<file>.svg URL to a victim who is logged into the same HedgeDoc instance. When the victim opens the link in a new tab, the SVG renders and executes JavaScript in the HedgeDoc origin, exposing cookies, notes, and API tokens.
// Patch: tighten CSP in lib/csp.js — remove gist.github.com from script-src,
// move gist embedding to iframes instead of inline scripts.
frameSrc: ['\'self\'', 'https://player.vimeo.com', 'https://www.youtube.com', 'https://gist.github.com'],
scriptSrc: [
config.serverURL + '/build/',
config.serverURL + '/js/',
config.serverURL + '/config',
// 'https://gist.github.com/' <-- removed
'\'unsafe-inline\''
]
// Source: https://github.com/hedgedoc/hedgedoc/commit/0636b5c20b7097afccfa6641830352ad068ef964
Detection Methods for CVE-2025-32391
Indicators of Compromise
- SVG files present in the HedgeDoc uploads directory containing <script> tags, onload= handlers, or references to gist.github.com.
- HTTP access logs showing direct GET /uploads/*.svg requests immediately followed by outbound requests to gist.github.com.
- Unexpected changes to notes, session usage from new IP addresses, or newly created API tokens after users viewed a shared upload link.
Detection Strategies
- Scan the upload storage backend for SVG files and inspect their contents for embedded scripting, event handlers, or external resource references.
- Review HedgeDoc reverse proxy and web server logs for /uploads/ responses that were served with a text/html or image/svg+xml content type without an attachment disposition.
- Correlate authenticated session activity with recent access to attacker-supplied upload URLs to identify potentially hijacked accounts.
Monitoring Recommendations
- Enable CSP violation reporting on the HedgeDoc origin and monitor reports for blocked script sources tied to /uploads/ referers.
- Alert on any HedgeDoc egress connections to Gist or other JSONP-capable endpoints from user browsers referred by the /uploads/ path.
- Track upload volume and file-type distribution to flag anomalous SVG uploads from low-reputation accounts.
How to Mitigate CVE-2025-32391
Immediate Actions Required
- Upgrade HedgeDoc to version 1.10.3 or later on all instances.
- Audit the uploads directory for existing SVG files and remove or quarantine any that contain script content.
- Rotate session secrets and invalidate active sessions if evidence of exploitation is found.
Patch Information
The fix is delivered in HedgeDoc 1.10.3 through three commits. Commit 0636b5c refactors Gist embedding to use iframes and removes https://gist.github.com/ from script-src. Commit 3f520ea5 drops SlideShare embedding and further tightens CSP by removing additional external script sources. Commit d2585fbd sets Content-Disposition: attachment and a locked-down Content-Security-Policy on all /uploads responses. Full details are in the HedgeDoc Security Advisory GHSA-3983-rrqh-mvx5.
Workarounds
- If upgrading is not immediately possible, add Content-Disposition: attachment and Content-Security-Policy: default-src 'none' headers to all responses under /uploads.
- Remove external URLs from the script-src directive of the global HedgeDoc Content-Security-Policy header.
- Restrict file upload permissions to trusted users and consider disabling SVG uploads at the reverse proxy layer.
# Example nginx snippet enforcing safe headers on the uploads route
location /uploads/ {
add_header Content-Disposition "attachment" always;
add_header Content-Security-Policy "default-src 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
proxy_pass http://hedgedoc_upstream;
}
# Source: https://github.com/hedgedoc/hedgedoc/commit/d2585fbd3b398b3359f6f88aa1fda8f4ecf2f78d
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.