Back to nomalabs

SneakPeek: How the Preview Pane in Claude Code Desktop Can Exfiltrate Your Data

Gal Moyal

Eli Ainhorn

October 14, 2026

TL;DR

Noma Labs discovered an exploit chain in Claude Code Desktop's preview pane that achieves arbitrary file read via unrestricted JavaScript execution. Disguised as a coverage report, the attacker places poisoned HTML in a public repo that bypasses the preview sandbox and runs JavaScript without a Content Security Policy to block it.

The delivery mechanism makes this attack path particularly interesting: a victim clones a public repo and asks Claude to review a file. Claude reads the file, its preview pane renders it automatically, and the embedded malicious payload executes. Because Claude's preview pane loads files above 256KB via file:// URLs, the payload gets same-origin access to the victim's entire local filesystem.

Sensitive credentials and secrets such as API keys, SSH private keys, cloud credentials, Claude's own OAuth tokens, and even the victim's real IP address via WebRTC are silently exfiltrated.

‍
Anthropic scored Noma Labs' discovery as CVSS 8.7 (High) and treated the impact as equivalent to code execution on the host.

SneakPeek vulnerability score from Anthropic

What is Claude Code Desktop's Preview Pane?

Claude Code Desktop’s preview pane is a feature most users have used, but few ever consider from a security perspective. Whenever Claude reads or edits an HTML file, the preview pane automatically renders it in an embedded panel so the user can see the result right away.

Under the hood, this preview pane is an artifact renderer that takes HTML content Claude has modified and displays it in the application using Electron's Chromium-based rendering engine.
The victim doesn't navigate to a URL or open a file in their browser; instead, the preview pane renders the file automatically.

To protect against malicious content, the preview pane runs inside a sandbox where a Content Security Policy (CSP) blocks script execution, origin isolation limits what rendered content can access, and a request filter controls which resources the renderer can fetch. This architecture creates three layers of defense between an HTML file and the victim’s machine. Noma Labs researchers bypassed all three.

The Sandbox Bypass

Anthropic sandboxes the preview pane with three defenses: a Content Security Policy (CSP) with script-src 'none' that blocks JavaScript execution, data: URL loading that gives the page an opaque origin so it can't fetch local files, and a request filter on the dev server proxy that catches outbound requests from the renderer.

How We Found It

We started by looking at how Claude Code Desktop renders HTML files in the preview pane. Claude’s preview pane renders small and large files differently:

  • Files under 256KB load as data: URLs, which restrict what embedded scripts can access.
  • Files over 256KB are written to a temp file and loaded as file:// URLs, which in Chromium can read other local files through fetch().

A CSP is applied to files rendered in the preview, blocking all inline script execution. But it only covers the data: URL path. The file:// path never receives the CSP, so JavaScript in a large file runs unrestricted.
There is also a request filter meant to block file:// reads from the preview. When the preview proxies a running dev server, such as a local React or Node app, the filter is active and blocks those reads. But when it renders a static HTML file directly from disk, like a coverage report, the filter does not engage and fetch() calls resolve against the real filesystem.

These three gaps chained together give an attacker arbitrary file read through the preview pane:

  • Pad the file past 256KB to get a file:// origin
  • Exploit the missing CSP to run scripts
  • Use the inactive request filter to read any local path.

Vulnerability Proof of Concept


One Question, Full Compromise

‍To demonstrate the vulnerability, Noma Labs researchers set up a repository that looked exactly like a normal project, including source files, tests, and a coverage report. Testing tools generate these reports to show which parts of a codebase have coverage. Repos of this type are normally large because they bundle source maps and CSS, so a 500KB report doesn't raise any flags. We made ours over 256KB and embedded a JavaScript payload in it.

  1. A victim clones the repo and opens Claude Code Desktop.
  2. The victim asks Claude to look at the coverage report, a routine request like reviewing test results or checking which modules have coverage gaps.
  3. Claude reads the file. The preview pane renders it automatically, and the embedded payload executes.
  4. The script resolves the victim's home directory from the temp file path and starts pulling files via fetch() and XMLHttpRequest:
  • SSH private keys
  • AWS access keys and secrets
  • npm and GitHub auth tokens
  • Kubernetes cluster credentials
  • Docker registry auth
  • Git identity and tokens

Because Claude Code Desktop has no CSP restricting outbound requests, the script sends everything back to the attacker's server using navigator.sendBeacon(), a web API that silently fires a one-way HTTP POST in the background. This exfiltration method works because, as mentioned previously, Claude Code Desktop is an Electron app. Electron apps' entire UI runs on a full Chromium browser engine with access to all standard web APIs.

In practical terms, this means the victim doesn't need to approve or grant Claude Desktop access to anything. No permission request is created to ask the victim to review the coverage report or its contents. The original victim's request to Claude to view the coverage report is enough to trigger the preview, execute the payload, and exfiltrate data and secrets. Because Claude renders the untrusted content on the victim's behalf, the rendering step is where the malicious code executes.

Bypassing the Fix

‍After our initial report, Anthropic deployed a patch that blocked fetch() and XMLHttpRequest calls to file:// paths from the main page context. However, Noma Labs researchers found another way around the patch.

By moving the fetch() call into a Web Worker created from a blob: URL, we were able to originate the request from a context that the CSP filter did not cover.

const workerCode = `
    self.onmessage = async function(e) {
      for (const target of e.data) {
        try {
          // Worker's fetch() bypasses the renderer's request filter
          const r = await fetch('file://' + target.path);
          if (r.ok) {
            const text = await r.text();
            // Send stolen file contents back to the main thread for exfiltration
            self.postMessage({tag: target.tag, data: text});
          }
        } catch(e) {}
      }
    };
  `;
  // Spawn the Worker via the blob: URL, creating a separate execution context not covered by the filter
  const blob = new Blob([workerCode], {type: 'application/javascript'});
  const worker = new Worker(URL.createObjectURL(blob)); 

The Worker read the same set of sensitive files and sent them back to the main thread, which exfiltrated them via sendBeacon().All three original defects remained exploitable through this path.‍

Anthropic's Response

Anthropic took Noma's disclosure seriously from the start.
‍
They triaged the initial report, scored it CVSS 8.7 (High) using CVSS v4.0, and deployed a patch blocking direct fetch() and XMLHttpRequest calls to file:// paths from the renderer.
‍
After we submitted the bypass demonstrating that  URL Web Workers could circumvent the fix, they confirmed the issue remained unresolved and committed to additional work to address it:

After the final retest, Anthropic fixed all issues; they closed the vulnerability and marked it as resolved.

Disclosure Timeline

A sincere thank you to the Anthropic team for their transparent, responsive partnership and relentless persistence throughout this disclosure process. Safely advancing frontier AI requires a model of mutual responsibility in which security researchers and platform vendors collaborate transparently through continuous feedback loops. Anthropic’s dedication to iterating on every bypass until achieving a comprehensive, layered fix exemplifies the collaborative standard needed to protect the broader community. 

  • July, 4, 2026        Initial Report: Vulnerability reported to Anthropic with full PoC demonstrating exfiltration of sensitive credentials via arbitrary JavaScript execution in the file preview pane.
  • July, 9, 2026        Triage: Anthropic scored the vulnerability CVSS 8.7 (High).
  • July 24, 2026       Patch and Retest: Anthropic fixed the vulnerability and requested a retest.
  • July 24, 2026       Bypass Reported: Noma Labs demonstrated that the blob: URL scheme Web Workers bypass the request filter. Full bypass PoC with video walkthrough submitted.
  • August 3, 2026    Bypass Confirmed: Anthropic confirmed the fix is incomplete and moved the report back to Triaged. Complete fix covering CSP, file:// loading, and the Worker bypass is in progress.
  • August 17, 2026   Anthropic issued a fix in Claude Desktop 1.30096.1 and later, delivered via auto-update.
  • October, 2026      GitHub Advisory and CVE pending publish

Learn More

The SneakPeek discovery is just one part of our broader mission at Noma Labs. Proactive, frontier vulnerability research is core to how we actualize protection and defense of the modern AI tech stack. You can read more about our recent findings and ongoing security analysis across the rest of the Noma Labs research index here and on Noma's YouTube channel here. We'll continue sharing our discoveries as we hunt for what's the most vulnerable and risky.

READ TIME
9 min
TABLE OF CONTENTS
100%
Share this:

Discover more

Noma Labs
Vulnerability
Research

Workflow Identity Hijacking: The Silent Backdoor in AI Workflows

Gal Moyal

Sasi Levi

October 1, 2026

Noma Labs

RufRoot: The MCP Bridge Vulnerability That Turns Agents Into Rogue Admins (CVE-2026-59726)

Gal Moyal

Eli Ainhorn

October 1, 2026

Noma Labs

GitLost: How We Tricked GitHub’s AI Agent into Leaking Private Repos

Gal Moyal

Sasi Levi

October 1, 2026