Claude Code Preview Window Vulnerability: The Attack Surface Beyond Agent Security

Claude Code preview window bypassed protections via file:// for large files, exposing a missing application-layer security boundary.
Claude Code Desktop's preview window once had a CVSS 8.7 high-severity vulnerability: HTML files over 256KB were loaded via file://, bypassing CSP and request filtering, executing scripts on open and stealing SSH private keys, cloud credentials, and other local sensitive data with no permission prompts. After Noma Labs reported it, Anthropic's first patch only blocked main-thread fetch and was bypassed via a blob: Worker; the complete fix targeted the loading conditions themselves and shipped in version 1.30096.1. The article distills three lessons: multiple code paths for the same feature need independent security review; permission systems protect the agent, not the rendering application; and when reviewing unknown files, the display interface should be the least capable. It also provides a builder's checklist based on Reado's defensive practices.
Claude Code Desktop's preview window once had a high-severity vulnerability: opening a malicious HTML file could steal local sensitive credentials. The vulnerability has been fixed, but the attack path it exposed—security boundaries built on the agent rather than the application itself—warrants a fresh look by everyone who builds or uses AI coding tools.

Vulnerability Recap: When "Viewing a File" Becomes "Executing Code"
According to Noma Labs' analysis, Claude Code Desktop implemented three layers of protection when previewing HTML files to guard against risk: a CSP that blocks script execution, source isolation via data: URLs, and request filtering by the dev server proxy. The problem lay in the coverage of these three layers.
Files smaller than 256KB were loaded via data: URLs, protected by CSP; files over 256KB were written to temporary files and loaded with the file:// protocol—a path with neither CSP nor a request filter, which only applied when proxying the dev server. In Chromium, file:// pages can fetch() other local files. An attacker only needed to pad a malicious script into an HTML file over 256KB—disguised as a test coverage report, for instance—and once the victim cloned the repository and asked Claude to view the file, the script would execute in the renderer, read SSH private keys, AWS keys, npm and GitHub tokens, Kubernetes and Docker credentials, and send them to an attacker's server.
There were no permission prompts throughout the entire process. Claude didn't execute a command, didn't write a file—it just read a file, the most benign operation an agent can perform. Code execution happened as a side effect of the application rendering the result, not in the agent's decision chain. Anthropic rated this vulnerability CVSS 8.7 (high severity).
CSP (Content Security Policy) is a browser security mechanism declared via HTTP response headers or <meta> tags that uses an allowlist to specify which scripts, styles, and images a page can load, and which origins it can make network requests to. In this case, CSP's script-src directive can block inline script execution, and connect-src can restrict the destination addresses of fetch, thereby blocking both code execution and data exfiltration. The key point is that CSP only takes effect on documents loaded via HTTP(S) or data: URLs, while local files loaded via the file:// protocol inherit no CSP by default—leaving the large-file preview path entirely outside policy protection.
The difference between data: URLs and the file:// protocol is also fundamental to understanding this vulnerability. data: URLs embed file content directly in the URL; the browser treats them as separate opaque origins that cannot access other local files via relative paths or scripts, providing inherent isolation. In Chromium, file:// pages by default have permission to read files in the same directory and even other local files, and their origin is treated as a local context where scripts can use fetch() to read any readable file on disk. Thus, the same HTML can have vastly different security capabilities depending on how it is loaded.
CVSS 8.7 is the rating given by the Common Vulnerability Scoring System, falling in the "high" range. The score combines the attack vector (network-based remote exploitation here), attack complexity (low—only requires inducing the user to open a file), privileges required (none), user interaction (victim must clone and view the file), and impact on confidentiality, integrity, and availability. A score of 8.7 means the vulnerability can be triggered remotely without special privileges and causes severe sensitive data leakage, but has not reached the "critical" level of 9.0 or above—typically because it requires some user interaction or has limited scope.
The Fix: From Blocking APIs to Eliminating Conditions
Noma reported the vulnerability on July 4. Anthropic's first patch intercepted fetch() and XMLHttpRequest calls to file:// in the page. Noma promptly moved the same fetch() into a Web Worker created from a blob: URL—a context the filter didn't cover—and the file was successfully read again. On July 24, Noma reported the bypass; on August 3, Anthropic confirmed the first fix was incomplete and began a comprehensive fix covering CSP, file:// loading, and the Worker bypass path. On August 17, the complete fix was released via auto-update with Claude Desktop 1.30096.1.
In its report, Noma acknowledged that Anthropic "iterated on each bypass until achieving a comprehensive layered fix." But the bypass itself is the key lesson: the first patch closed the API used in the proof of concept—an attacker just switches to another; the complete fix targeted the conditions themselves—CSP, file:// loading, and Workers.
blob: URLs and Web Workers are key to understanding the bypass technique. A blob: URL is generated in memory by URL.createObjectURL(), pointing to a segment of binary data; its origin inherits from the page that created it, but in some browser implementations, a Worker created from a blob: has an independent execution context and may not be constrained by page-level connect-src or monkey-patching of fetch. Web Workers run on a separate thread and cannot directly access the DOM, but can still make network requests and read files. The attacker exploited this isolation to move the fetch() call into a Worker, evading the request-filtering hook on the main thread and thereby reading local files again. This reminds defenders: any patch that intercepts a specific API must cover that API's variants across all execution contexts (main thread, Worker, Service Worker, iframe).
Three Security Blind Spots to Watch For
A second code path is a second security model. The 256KB threshold likely came from performance considerations—large data: URLs load slowly—but no one would decide that "large files don't deserve CSP." Yet a threshold quietly sent large files down a path with different rules. If the same feature has two implementations, each requires independent security review; better still, keep only one.
The permission system protects the agent, not the application around it. Most of our security measures for coding agents revolve around the agent itself: approving commands, prohibiting writes outside the project, sandboxing the shell. All of these were in place in this vulnerability, and none of them mattered. As Carlos put it in an analysis on dev.to: "Here, the file in the repository is the attacker, and the viewer is the tool." Any interface that renders repository content—previews, Markdown, notebooks, images, embedded browsers—is part of the attack surface, whether or not an agent is involved.
"Review this file" is exactly when you should trust the file least. The request that triggered SneakPeek was itself a review: take a look at this coverage report. At that moment, the file hasn't been examined by anyone you trust, and the interface displaying it should be the least capable part of the application, not the most.
A Self-Check Checklist for an IDE
After reading Noma's report, the author of Reado ran their own application through SneakPeek's three questions—does the file's code execute? can the page read the disk? can it send data out?—one by one. Reado's approach offers a useful defensive reference:
- HTML files open as source by default. Reado renders Markdown, Jupyter notebooks, images, and PDFs; everything else, including HTML, is text in the editor. Coverage reports are meant to be read, not run.
- HTML in Markdown and notebook output goes through a sanitizer. READMEs are full of HTML (centered logos, badges,
<details>), and Reado renders them via rehype-sanitize, stripping scripts, event handlers, andjavascript:URLs. - Behind the sanitizer is the application's own CSP. Only Reado's built-in scripts can run (
script-src 'self'); the same rule covers Workers, so theblob:Worker trick can't get started; frames are prohibited, and the page can only send requests to the application itself. - File size never changes the loading method. A 500KB README and a 5KB one go through the same pipeline; when a limit is exceeded, Reado stops and asks rather than switching to another loader.
- Local images are restricted to the open project. The disk image protocol starts with an empty scope and expands to the project folder only when a project is opened; SVGs are displayed via
<img>tags so scripts don't execute; PDFs are drawn on a canvas.
This check also incidentally uncovered two vulnerabilities in the embedded browser panel: design comments (stored in the .reado/ directory) carry page URLs, and the agent drives the panel through command files in the same directory. A cloned repository can include comments pointing to file: or data: pages, or pre-place commands waiting for the panel to open. The fix was to allow only http and https pages, validated uniformly at the Rust layer, and to only log—not execute—commands that already exist when the panel opens.
rehype-sanitize is an HTML sanitizer in the unified/rehype ecosystem, commonly used in Markdown rendering pipelines. After converting Markdown to HTML and before inserting it into the DOM, it strips dangerous tags and attributes according to allowlist rules—such as <script>, on* event handlers, and javascript: protocol URLs. Compared with simple regex filtering, it operates on the AST and correctly handles nesting and malformed markup, reducing the likelihood of bypass. However, sanitizers are not a silver bullet; they still need to be paired with a strict application-level CSP as defense in depth, because sanitization rule libraries may have gaps, while CSP is enforced by the browser—only together can they cover multiple stages of the rendering chain.
Advice for Users and Builders
If you use Claude Code Desktop, make sure your version is Claude Desktop 1.30096.1 or later. Noma's report notes that a GitHub security advisory and CVE are still pending assignment, so keep an eye out.
If you're building tools that display repository files, this vulnerability offers several rules: treat every file in a cloned repository as input from a stranger, including the boring-looking ones; keep only one rendering path, applying the same restrictions no matter how large the file; apply restrictions to the content's origin, not the APIs the content uses—block fetch() and an attacker will look for a Worker; show source by default, and render only when necessary.
Anthropic fixed this issue and persisted until the bypass paths were closed too. This pattern isn't unique to them: every coding tool that shows you files has its own version of the preview window—it's worth taking a look at yours before someone else does.
One-line Summary
Claude Code's preview window bypassed protections because large files went through the file:// path, exposing a missing application-layer security boundary.
Paragraph Summary
Claude Code Desktop's preview window once had a CVSS 8.7 high-severity vulnerability: HTML files over 256KB were loaded via the file:// protocol, bypassing CSP and request filtering, executing scripts on open and stealing local sensitive data such as SSH private keys and cloud credentials—all without permission prompts. After Noma Labs reported it, Anthropic's first patch only intercepted main-thread fetch and was bypassed via a blob: Worker; the complete fix ultimately targeted the loading conditions themselves, shipped with version 1.30096.1. The article distills three lessons: multiple code paths for the same feature need independent security review; permission systems protect the agent, not the rendering application; and when reviewing unknown files, the display interface should be the least capable. It also offers a builder's checklist based on Reado's defensive practices.
Related articles

From the Afghan Battlefield to Microsoft Research: Chris White's Path to Tech for Good
Microsoft Research Catalyst Lab lead Chris White shares his journey from Harvard postdoc to the Afghan battlefield, DARPA's Memex dark web search project, and driving tech for good at Microsoft—exploring problem selection, humble service, and holding onto hope in the dark.

Opus 5.5 Refuses to Ghostwrite Paper Section: Why Users Applaud This AI Guardrail
A Reddit user shares how Opus 5.5 refused to ghostwrite a dissertation paragraph: the model identified school AI restrictions, explained academic integrity risks, and returned decision-making power to the user. Why is this "explained refusal" called an ideal guardrail? This article analyzes the three-layer logic of AI guardrail design and product implications.

Google Open-Sources TorchTPU: How a PyTorch Native Backend Reshapes the TPU Ecosystem
Google and Meta open-source TorchTPU, a PyTorch native backend for Cloud TPU with eager execution by default and torch.compile/XLA support, set to replace PyTorch/XLA for new workloads. This article analyzes its architecture, execution trade-offs, and impact on developers and the TPU ecosystem.