-
-
Notifications
You must be signed in to change notification settings - Fork 88
Description
Detail Bug Report
Summary
- Context: The
packages/screenshot/src/pretty/html.jsmodule generates HTML to display API responses (JSON/text) with syntax highlighting for screenshot capture. - Bug: User-controlled content from external API responses is directly embedded into HTML templates without HTML escaping, enabling Cross-Site Scripting (XSS) attacks.
- Actual vs. expected: Content is inserted raw into the HTML using template literals, allowing execution of malicious JavaScript; it should be HTML-escaped before insertion.
- Impact: Attackers can execute arbitrary JavaScript when users screenshot malicious APIs, potentially stealing sensitive data from the browser automation context or interfering with the screenshot process.
Code with Bug
In packages/screenshot/src/pretty/html.js:
module.exports = (payload, { contentType, prism, theme }) => {
const css = `${resetCSS}\n${theme}`
const lang = language(contentType)
const code = content(payload, contentType) // No HTML escaping
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
${css}
</head>
<body id="screenshot">
<pre><code class="${lang}">${code}</code></pre> // <-- BUG 🔴 unescaped user content injected into HTML
<script>${prism}</script>
</body>
</html>`
}Explanation
code can contain attacker-controlled API response text (notably for contentType: 'text'). It is interpolated directly into an HTML string passed to the browser, so any embedded HTML (e.g., <script>, <img onerror=...>, SVG) is parsed and can execute immediately.
A partial defense exists elsewhere (if (isHtmlContent(content)) return), but is-html-content only detects content that starts with < (regex /^\s*</). Payloads like "OK\n<script>...</script>" bypass the check.
Prism.js does not prevent exploitation because scripts/event handlers can run during initial HTML parsing before Prism sanitizes/highlights the code.
Codebase Inconsistency
packages/screenshot/src/pretty/index.js attempts to avoid HTML injection via:
if (isHtmlContent(content)) return…but that check does not cover the common bypass where the response begins with non-< text.
Exploit Scenario
An attacker controls an API endpoint that returns text/plain content containing a script tag or an HTML element with an event handler after some benign text (so it doesn’t start with <). When a user screenshots/prettifies that API response, the generated HTML executes the attacker’s JavaScript in the automation browser context.
Recommended Fix
HTML-escape content(payload, contentType) before inserting it into the template (or refactor to set code via textContent rather than string interpolation). Example escape approach:
const code = escapeHtml(content(payload, contentType))History
This bug was introduced in commit 18ab1b4. The commit added support for prettifying text/plain API responses in addition to JSON responses, and the refactor began interpolating raw strings into HTML without escaping.