fix: add CSP headers and Content-Disposition to prevent SVG XSS 🚨 #61
+8
−0
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 files served through the skills and souls file endpoints (
/api/v1/skills/:name/fileand/api/v1/souls/:name/file) execute arbitrary JavaScript via<foreignObject>. Because these files areserved from the main
clawdhub.comorigin with no Content-Security-Policy or Content-Disposition headers,any script in an SVG runs with full same-origin access to
localStorage, including Convex auth JWTs andrefresh tokens.
An attacker can publish a skill containing a malicious SVG, then link to it from their skill's README.
Anyone who clicks the link while logged in has their session tokens exposed. Refresh tokens mean the
attacker maintains access even after the JWT expires.
PoC (open while logged in to ClawdHub): https://clawdhub.com/api/v1/skills/red-pill/file?path=icon.svg
What this patch does
Adds three layers of defense to both file-serving endpoints in
convex/httpApiV1.ts:Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src * data:; media-src *-- blocks all script execution while still allowing static SVG rendering with inline styles and images
X-Content-Type-Options: nosniff-- prevents the browser from reinterpreting content typesContent-Disposition: attachmenton SVG files specifically -- forces download instead of inlinerendering as a belt-and-suspenders measure
Affected endpoints
skillsGetRouterV1Handler(line ~378)soulsGetRouterV1Handler(line ~987)Test plan
.js,.md,.json) still serve normally with correct content typesX-Content-Type-Options: nosniffis present on all file responses