Feb 26, 2026·6 min read·78 visits
Storybook's dev server left its WebSocket door wide open. If a developer visits a malicious site while Storybook is running, the site can hijack the connection (CSWSH), write malicious files to the local disk, and trigger RCE. Patch immediately to versions 7.6.23+, 8.6.17+, 9.1.19+, or 10.2.10+.
A critical flaw in the Storybook development server allows attackers to hijack the WebSocket connection from a malicious website via Cross-Site WebSocket Hijacking (CSWSH). Because the server failed to validate the `Origin` header or require authentication, a drive-by attack can silently connect to a developer's local instance, overwrite files, and achieve Remote Code Execution (RCE) on the developer's machine.
We often treat localhost like a sanctuary. It's the safe space where code is born, broken, and fixed before it faces the cruel judgment of production. But here's the dirty secret of modern web development: your local dev server is often more exposed than your production environment. Why? Because developers prize convenience over security. "It's just running locally, who can touch it?" turns out to be a very expensive assumption.
Enter Storybook, the industry standard for UI component development. Under the hood, the Storybook Dev Server runs a WebSocket channel (/storybook-server-channel) to synchronize the Manager (the UI you click) with the Preview (the iframe rendering your components). This channel is the nervous system of the application. It handles hot module reloading, event emission, and—crucially—file system operations like creating and saving stories.
Now, imagine if that nervous system had no immune system. No antibodies. No skin. Just raw nerves exposed to the open internet. That is exactly what CVE-2026-27148 is. It’s a mechanism that allows an attacker to reach into your localhost from their website and tell your Storybook server to start rewriting your project files.
The vulnerability here is a classic Cross-Site WebSocket Hijacking (CSWSH) issue. To understand it, you have to understand how browsers handle WebSockets. When a browser initiates a WebSocket handshake, it sends an HTTP GET request with an Upgrade: websocket header. Crucially, it also sends an Origin header, telling the server which site is asking for the connection. For standard HTTP requests (fetch/XHR), the browser enforces the Same-Origin Policy (SOP). But for WebSockets? The browser relies on the server to check that Origin header and say "No."
Storybook didn't say no. In fact, it didn't even look. The ServerChannelTransport implementation blindly accepted any incoming connection to /storybook-server-channel. It didn't care if the request came from http://localhost:6006 (the legit UI) or https://evil-hacker-blog.com.
This creates a terrifyingly simple attack vector. I don't need to be on your network. I don't need to phish your credentials. I just need you to visit my website while you have Storybook running in a background tab. My JavaScript runs in your browser, sees localhost (which your browser can reach), and opens a socket. Your browser attaches cookies (if there were any, though Storybook didn't use them here) and the connection is established. Game on.
Let's look at the anatomy of the failure. The vulnerability resided in how the server handled the upgrade event. In the vulnerable versions, the code looked effectively like this:
// VULNERABLE CODE (Conceptual)
server.on('upgrade', (request, socket, head) => {
if (request.url === '/storybook-server-channel') {
// "Come on in, the water's fine!"
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
}
});See the lack of logic? If the URL matches, the connection is granted. There is no check for the Origin header, and no token verification.
Now, let's look at the fix introduced in commit 54689a8add18ea75d628c540f4bc677592a1e685. The maintainers introduced a token-based authentication system. When the dev server starts, it generates a UUID. The frontend must present this UUID to connect.
// PATCHED CODE (Simplified)
const SERVER_CHANNEL_TOKEN = randomUUID(); // Generated at startup
server.on('upgrade', (request, socket, head) => {
const url = new URL(request.url, 'http://localhost');
if (url.pathname === '/storybook-server-channel') {
// The check that saves the day
const requestToken = url.searchParams.get('token');
if (!isValidToken(requestToken, SERVER_CHANNEL_TOKEN)) {
// "You shall not pass!"
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
socket.destroy();
return;
}
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
}
});This simple check neutralizes the attack. An external website cannot guess the random UUID generated when you started your server.
So I have a socket. What can I do with it? This is where it gets dark. The Storybook channel isn't just for chatting; it's an RPC mechanism. It supports event handlers for CREATE_STORY and SAVE_STORY. These handlers take a file path and content, and write it to the disk.
Here is the attack chain:
SAVE_STORY event.{
"type": "SAVE_STORY",
"args": [
{
"importPath": "./stories/Button.stories.ts",
"content": "import { exec } from 'child_process'; exec('calc.exe'); // ... rest of story code"
}
]
}Button.stories.ts.This is effectively RCE. If I can write to your filesystem and force a recompile, I own your shell.
The mitigation is straightforward: Update. The Storybook team has released patches for all supported major versions.
7.6.238.6.179.1.1910.2.10If you are stuck on an older version and cannot upgrade immediately (we've all been there), you have limited options. You could try to run Storybook behind a reverse proxy that strips the Origin header or enforces authentication, but honestly, that's more work than just upgrading package.json.
> [!WARNING]
> Do not use ngrok or similar tunneling services to share your local Storybook unless you are absolutely certain you have patched. Exposing a vulnerable dev server to the public internet turns a "Drive-By" attack into a "Public Invitation" for anyone scanning for open WebSockets.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Storybook Storybook.js | < 7.6.23 | 7.6.23 |
Storybook Storybook.js | 8.1.0 - < 8.6.17 | 8.6.17 |
Storybook Storybook.js | 9.0.0 - < 9.1.19 | 9.1.19 |
Storybook Storybook.js | 10.0.0 - < 10.2.10 | 10.2.10 |
| Attribute | Detail |
|---|---|
| Attack Vector | Network (Drive-By) |
| CVSS v4.0 | 8.9 (High) |
| CWE | CWE-74 / CWE-79 |
| Impact | RCE / Persistent XSS |
| Exploit Status | PoC Available |
| Component | ServerChannelTransport |
Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
CVE-2026-14669 is a critical heap-based buffer overflow vulnerability in PostgreSQL's date/time formatting function to_char(timestamptz). The flaw arises from unsafe copying of user-controlled timezone abbreviations into a fixed-size internal buffer. An authenticated database user can trigger this issue by setting a long POSIX timezone abbreviation containing custom formatting, allowing them to overwrite adjacent heap structures and hijack execution control to achieve remote code execution (RCE) with the privileges of the 'postgres' operating system user.
An unauthenticated remote denial of service vulnerability exists in the Unleash feature management platform. By submitting a crafted JSON payload containing deeply nested structures to an OpenAPI-validated endpoint, an attacker can trigger uncontrolled recursion within the error formatting module. This leads to a call-stack exhaustion (RangeError: Maximum call stack size exceeded) inside the Node.js runtime, causing the service to crash immediately without recovery.
CVE-2026-63004 is a server-side request forgery (SSRF) vulnerability in the Unleash feature management platform. Authenticated administrators with CREATE_ADDON or UPDATE_ADDON privileges can exploit this vulnerability to initiate requests to loopback addresses, private networks, and cloud metadata endpoints, potentially leading to information disclosure and credential extraction.
Prior to version 8.0.3, Unleash's Markdown event formatter directly mutated the global template-escaping function of the shared mustache Node.js module, resulting in a process-wide security degradation where HTML/Markdown escaping was permanently disabled for the application lifetime.
A critical SQL injection vulnerability exists in the GeoTools open-source Java library. This vulnerability is situated within the post-processing phase of OGC Filter conversion inside the PostGIS DataStore module. Specifically, the `jsonArrayContains` function does not validate or sanitize its arguments before constructing PostgreSQL SQL/JSON path evaluation queries. An unauthenticated remote attacker can exploit this weakness by submitting crafted filters via standard OGC services like WFS or WMS to execute arbitrary SQL commands on the underlying database system.
CVE-2026-61824 is a high-severity Cross-Site Scripting (XSS) vulnerability in kepano/defuddle before version 0.19.1. Custom site extractors for platforms such as X/Twitter, Substack, and YouTube constructed HTML representations via template string interpolation without output escaping. This allowed malicious pages to bypass standard parser sanitization routines and execute arbitrary JavaScript.