CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27148
8.90.36%

Storybook Ending: Dev Server RCE via WebSocket Hijacking

Alon Barad
Alon Barad
Software Engineer

Feb 26, 2026·6 min read·5 visits

PoC Available

Executive Summary (TL;DR)

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.

The Hook: Your Localhost is Not a Castle

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 Flaw: The 'Origin' Myth

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.

The Code: The Smoking Gun

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.

The Exploit: From Hijack to RCE

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:

  1. Recon: The malicious script attempts to connect to default ports (6006, 6007, etc.) to find a live Storybook instance.
  2. Connection: The socket opens. The attacker listens for the initial handshake.
  3. Payload Delivery: The attacker sends a JSON payload masquerading as a 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"
    }
  ]
}
  1. Execution: Storybook receives the event. It assumes the request is coming from its own UI (because why would it check?). It writes the malicious content to Button.stories.ts.
  2. Trigger: The Dev Server usually runs a file watcher (like Webpack or Vite). It sees the file change, recompiles, and hot-reloads the module. The moment that file is processed or rendered in the Preview iframe, the attacker's JavaScript executes within the Node.js context of the build process or the browser context of the preview.

This is effectively RCE. If I can write to your filesystem and force a recompile, I own your shell.

The Fix: Closing the Loop

The mitigation is straightforward: Update. The Storybook team has released patches for all supported major versions.

  • Storybook 7: Upgrade to 7.6.23
  • Storybook 8: Upgrade to 8.6.17
  • Storybook 9: Upgrade to 9.1.19
  • Storybook 10: Upgrade to 10.2.10

If 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.

Official Patches

StorybookGitHub Security Advisory and Patch Notes

Fix Analysis (1)

Technical Appendix

CVSS Score
8.9/ 10
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
EPSS Probability
0.36%
Top 42% most exploited

Affected Systems

Storybook Dev Server (< 7.6.23)Storybook Dev Server (8.1.0 - < 8.6.17)Storybook Dev Server (9.0.0 - < 9.1.19)Storybook Dev Server (10.0.0 - < 10.2.10)

Affected Versions Detail

Product
Affected Versions
Fixed Version
Storybook
Storybook.js
< 7.6.237.6.23
Storybook
Storybook.js
8.1.0 - < 8.6.178.6.17
Storybook
Storybook.js
9.0.0 - < 9.1.199.1.19
Storybook
Storybook.js
10.0.0 - < 10.2.1010.2.10
AttributeDetail
Attack VectorNetwork (Drive-By)
CVSS v4.08.9 (High)
CWECWE-74 / CWE-79
ImpactRCE / Persistent XSS
Exploit StatusPoC Available
ComponentServerChannelTransport

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1566Phishing
Initial Access
T1204User Execution
Execution
CWE-74
Injection

Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')

Known Exploits & Detection

GitHub Security AdvisoryOfficial advisory detailing the CSWSH vector.

Vulnerability Timeline

Patches merged into Storybook repository
2026-02-18
Public disclosure and GHSA published
2026-02-25
CVE-2026-27148 assigned
2026-02-25

References & Sources

  • [1]GHSA-mjf5-7g4m-gx5w
  • [2]MITRE ATT&CK: Drive-by Compromise

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.