Skip to content

[Detail Bug] Screenshot rendering: XSS when prettifying text/plain API responses #691

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_06887db3-bf54-40ab-976d-46c66ab2b840/bugs/bug_ac4f2a30-ec5c-4f97-99d1-feb74772537e

Summary

  • Context: The packages/screenshot/src/pretty/html.js module 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions