[PR #61] [MERGED] fix: add CSP headers and Content-Disposition to prevent SVG XSS 🚨 #240

Closed
opened 2026-02-15 17:16:27 -05:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/openclaw/clawhub/pull/61
Author: @orlyjamie
Created: 1/28/2026
Status: Merged
Merged: 1/28/2026
Merged by: @thewilloftheshadow

Base: mainHead: fix/svg-xss-csp-headers


📝 Commits (1)

  • a4b5983 fix: add CSP headers and Content-Disposition to prevent SVG XSS

📊 Changes

1 file changed (+8 additions, -0 deletions)

View changed files

📝 convex/httpApiV1.ts (+8 -0)

📄 Description

Summary

SVG files served through the skills and souls file endpoints (/api/v1/skills/:name/file and
/api/v1/souls/:name/file) execute arbitrary JavaScript via <foreignObject>. Because these files are
served from the main clawdhub.com origin 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 and
refresh 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 types
  • Content-Disposition: attachment on SVG files specifically -- forces download instead of inline
    rendering as a belt-and-suspenders measure

Affected endpoints

  • skillsGetRouterV1Handler (line ~378)
  • soulsGetRouterV1Handler (line ~987)

Test plan

  • Open the PoC SVG link above after deploying -- should download instead of rendering inline
  • Verify non-SVG files (.js, .md, .json) still serve normally with correct content types
  • Confirm CSP header is present on all file responses via browser dev tools
  • Confirm X-Content-Type-Options: nosniff is present on all file responses

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/openclaw/clawhub/pull/61 **Author:** [@orlyjamie](https://github.com/orlyjamie) **Created:** 1/28/2026 **Status:** ✅ Merged **Merged:** 1/28/2026 **Merged by:** [@thewilloftheshadow](https://github.com/thewilloftheshadow) **Base:** `main` ← **Head:** `fix/svg-xss-csp-headers` --- ### 📝 Commits (1) - [`a4b5983`](https://github.com/openclaw/clawhub/commit/a4b59834312317f16d5e9d15cc5bd97044efbf63) fix: add CSP headers and Content-Disposition to prevent SVG XSS ### 📊 Changes **1 file changed** (+8 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `convex/httpApiV1.ts` (+8 -0) </details> ### 📄 Description ## Summary SVG files served through the skills and souls file endpoints (`/api/v1/skills/:name/file` and `/api/v1/souls/:name/file`) execute arbitrary JavaScript via `<foreignObject>`. Because these files are served from the main `clawdhub.com` origin 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 and refresh 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 types - **`Content-Disposition: attachment`** on SVG files specifically -- forces download instead of inline rendering as a belt-and-suspenders measure ## Affected endpoints - `skillsGetRouterV1Handler` (line ~378) - `soulsGetRouterV1Handler` (line ~987) ## Test plan - [ ] Open the PoC SVG link above after deploying -- should download instead of rendering inline - [ ] Verify non-SVG files (`.js`, `.md`, `.json`) still serve normally with correct content types - [ ] Confirm CSP header is present on all file responses via browser dev tools - [ ] Confirm `X-Content-Type-Options: nosniff` is present on all file responses --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-02-15 17:16:27 -05:00
yindo closed this issue 2026-02-15 17:16:27 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: openclaw/clawhub#240