critical

CVE

CVE-2026-114125, CVE-2026-114126

CWE

CWE-346, CWE-1021, CWE-94, CWE-306

Affected Surface

  • npm packages `storybook`, `@storybook/core`, `@storybook/core-server`, and `@storybook/experimental-addon-test` versions `>= 8.0.0, < 8.6.19`, `>= 9.0.0, < 9.1.21`, `>= 10.0.0, < 10.6.2`, and `>= 11.0.0-alpha.0, < 11.0.0-alpha.6`
  • npm package `@storybook/addon-vitest` versions `>= 9.0.0, < 9.1.21`, `>= 10.0.0, < 10.6.2`, and `>= 11.0.0-alpha.0, < 11.0.0-alpha.6`
  • npm package `@storybook/addon-mcp` versions `>= 10.6.0, < 10.6.2` and `>= 11.0.0-alpha.0, < 11.0.0-alpha.6`
  • Developer workstations and shared dev containers running `storybook dev` while browsing untrusted websites or connected to shared Wi-Fi and LAN segments

TL;DR

On 21 and 22 October 2026, the GitHub Advisory Database and NVD published two coordinated advisories for Storybook’s development server (storybook dev):

  1. CVE-2026-114125 (GHSA-f2ph-cmg5-wxc8, CVSS v4.0 9.4 Critical): While storybook dev is running locally, a third-party website can embed the Storybook preview iframe (http://localhost:6006/iframe.html) and send cross-origin postMessage events into the preview channel. By chaining the preview’s HIGHLIGHT menu handler into the server WebSocket channel, an attacker can trigger createNewStoryfileRequest or saveStoryRequest, inject arbitrary JavaScript into a .stories.* file on disk, and execute it with the developer’s OS privileges.
  2. CVE-2026-114126 (GHSA-px85-f4q5-jh77, CVSS v4.0 8.7 High): Before the patch, storybook dev bound to all network interfaces (0.0.0.0 / ::) by default and embedded its WebSocket authentication token in HTML responses served to any client. Any host on the same LAN or Wi-Fi network could read that token over HTTP, open a WebSocket connection to /storybook-server-channel, write story files, and trigger code execution without any user interaction (UI:N).

Static builds produced by storybook build are not affected. Upgrade storybook and its companion packages to 8.6.19, 9.1.21, 10.6.2 (or 10.6.3), or 11.0.0-alpha.6+ before running storybook dev again.

Affected packages and projects

EcosystemPackageCVE / GHSAAffected versionsFixed versionsNotes
npmstorybookCVE-2026-114125 (GHSA-f2ph-cmg5-wxc8), CVE-2026-114126 (GHSA-px85-f4q5-jh77)>= 8.0.0, < 8.6.19, >= 9.0.0, < 9.1.21, >= 10.0.0, < 10.6.2, >= 11.0.0-alpha.0, < 11.0.0-alpha.68.6.19, 9.1.21, 10.6.2, 11.0.0-alpha.6Main CLI and dev server across v8 through v11
npm@storybook/core, @storybook/core-serverCVE-2026-114125, CVE-2026-114126>= 8.0.0, < 8.6.198.6.19On npm, the v8 dist-tag points to 8.6.19, while latest still points to 8.6.14; pin 8.6.19 explicitly
npm@storybook/experimental-addon-testCVE-2026-114125, CVE-2026-114126>= 8.0.0, < 8.6.198.6.19Storybook 8 test runner addon
npm@storybook/addon-vitestCVE-2026-114125, CVE-2026-114126>= 9.0.0, < 9.1.21, >= 10.0.0, < 10.6.2, >= 11.0.0-alpha.0, < 11.0.0-alpha.69.1.21, 10.6.2, 11.0.0-alpha.6Disables Vitest Browser RPC allowWrite and allowExec by default
npm@storybook/addon-mcpCVE-2026-114125, CVE-2026-114126>= 10.6.0, < 10.6.2, >= 11.0.0-alpha.0, < 11.0.0-alpha.610.6.2, 11.0.0-alpha.6Enforces Origin host validation and Content-Type: application/json on /mcp

Technical analysis

The patches in commit 243c06bf8f (PR #36726), commit 3be03ec6f1 (PR #36724), and commit 5540f03b20 show that the worst-case RCE chain crosses five separate trust boundaries inside the Storybook dev server.

1. Cross-origin iframe embedding and postMessage into the preview (CVE-2026-114125)

Before the fix, the Storybook dev server did not set a Content-Security-Policy: frame-ancestors header, and PostMessageTransport.handleEvent() in code/core/src/channels/postmessage/index.ts processed incoming window.postMessage payloads without checking rawEvent.origin. It also broadcast outbound messages with f.postMessage(data, '*').

A remote website visited by the developer could embed http://localhost:6006/iframe.html in an iframe and send storybook-channel JSON packets straight into the running preview.

The patch adds getSecurityHeadersMiddleware() (Content-Security-Policy: frame-ancestors 'self' http://localhost:* http://127.0.0.1:* ...) and restricts PostMessageTransport in development mode so the preview only accepts messages from its own origin and loopback hosts:

--- a/code/core/src/channels/postmessage/index.ts
+++ b/code/core/src/channels/postmessage/index.ts
@@ -117,9 +177,12 @@ export class PostMessageTransport implements ChannelTransport {
-    frames.forEach((f) => {
+    frames.forEach(({ contentWindow, targetOrigin }) => {
+      if (this.config.page === 'manager' && isOnInitialBlankDocument(contentWindow)) {
+        return;
+      }
       try {
-        f.postMessage(data, '*');
+        contentWindow.postMessage(data, this.pinsTargetOrigin ? targetOrigin : '*');
       } catch (e) {
         logger.error('sending over postmessage fail');
       }
@@ -200,6 +380,10 @@ export class PostMessageTransport implements ChannelTransport {
   private handleEvent(rawEvent: MessageEvent): void {
     try {
+      if (!this.isAllowedSender(rawEvent)) {
+        return;
+      }
+
       const { data } = rawEvent;

2. Escalating from postMessage to the server WebSocket via HIGHLIGHT and un-scoped roles

Even though the remote website could only talk to the preview iframe over postMessage, the preview iframe itself holds an authenticated WebSocket connection (ServerChannelTransport) back to the Node.js dev server.

Two flaws let the attacker bridge that gap:

  1. In code/core/src/highlight/useHighlights.ts, the HIGHLIGHT channel event accepted a menu array where each item specified an arbitrary channel event name in clickEvent. When the developer clicked the highlight overlay in the embedded iframe, useHighlights emitted channel.emit(event, id, getEventDetails(target)) onto the shared channel, including onto the WebSocket transport.
  2. In code/core/src/core-server/utils/get-server-channel.ts, the dev server’s WebSocket handler accepted every event from any connected WebSocket client without checking whether the connection belonged to the manager UI or the preview iframe.

The patch removes caller-chosen clickEvent names from useHighlights.ts (replacing them with a fixed HIGHLIGHT_MENU_CLICK event) and introduces CORE_EVENT_SENDERS in code/core/src/core-server/utils/event-senders.ts so ServerChannelTransport drops manager-only events such as saveStoryRequest and createNewStoryfileRequest if they arrive over a preview socket:

--- a/code/core/src/highlight/useHighlights.ts
+++ b/code/core/src/highlight/useHighlights.ts
@@ -477,10 +480,11 @@ export const useHighlights = (channel: Channel) => {
-                  menuItems.map(
-                    ({ id, title, description, iconLeft, iconRight, clickEvent: event }) => {
-                      const onClick =
-                        event && (() => channel.emit(event, id, getEventDetails(target)));
+                  menuItems.map(({ id, title, description, iconLeft, iconRight, clickable }) => {
+                    const onClick =
+                      clickable &&
+                      (() =>
+                        channel.emit(HIGHLIGHT_MENU_CLICK, target.id, id, getEventDetails(target)));

3. LAN WebSocket token exposure and 0.0.0.0 binding (CVE-2026-114126)

On the local network, an attacker did not need postMessage or user interaction at all. When storybook dev started without --host, Node bound the HTTP and WebSocket server to all interfaces (0.0.0.0 / ::). Any peer on the same Wi-Fi or LAN segment could fetch the Storybook page by the developer’s LAN IP, extract the WebSocket token embedded in the page, and connect directly to ws://<lan-ip>:6006/storybook-server-channel?token=....

In code/core/src/core-server/utils/server-address.ts, Storybook now defaults to binding exclusively to 127.0.0.1:

export const getListenHost = (host: string | undefined) => host || '127.0.0.1';

In addition, @storybook/addon-mcp (code/addons/mcp/src/preset.ts) now validates the Origin header against isValidHost() and rejects any POST to /mcp whose Content-Type is not application/json, forcing browsers to send a CORS preflight request that the dev server refuses.

4. Unescaped template interpolation and path traversal in story writers

Once an attacker could emit createNewStoryfileRequest or saveStoryRequest on the server channel, two server-side handlers wrote attacker-controlled strings into project files:

  • In code/core/src/core-server/utils/get-new-story-file.ts and new-story-templates/*.ts, componentExportName and basenameWithoutExtension were interpolated directly into the generated .stories.ts / .stories.js source (import { ${importName} } from './${data.basenameWithoutExtension}') without checking whether componentExportName was a valid JavaScript identifier or escaping quotes and newlines.
  • In code/core/src/core-server/utils/save-story/save-story.ts, sourceFilePath = join(process.cwd(), importPath) did not verify that sourceFilePath stayed inside the project root or that name was a valid identifier.

The patch validates identifiers with Babel’s t.isValidIdentifier() (excluding eval and arguments), escapes paths with escapeForTemplate(), and blocks path traversal in save-story.ts:

--- a/code/core/src/core-server/utils/save-story/save-story.ts
+++ b/code/core/src/core-server/utils/save-story/save-story.ts
@@ -60,6 +61,19 @@ export function initializeSaveStory(channel: Channel, options: Options) {
     try {
+      if (name && !t.isValidIdentifier(name)) {
+        throw new SaveStoryError(`Invalid story name: ${JSON.stringify(name)}`);
+      }
+
       sourceFileName = basename(importPath);
       sourceFilePath = join(process.cwd(), importPath);
+
+      const pathFromProjectRoot = relative(getProjectRoot(), sourceFilePath);
+      if (
+        isAbsolute(pathFromProjectRoot) ||
+        pathFromProjectRoot.split(/[\\/]/).some((segment) => /^\.{2,}$/.test(segment))
+      ) {
+        throw new SaveStoryError(`Invalid importPath: must resolve within the project root.`);
+      }

5. From story file modification to OS command execution via @storybook/addon-vitest

Writing code into a .stories.ts file executes JavaScript inside the browser preview, not yet in Node.js. However, when @storybook/addon-vitest is installed, Vitest Browser mode exposes a Node RPC API to browser test code. Whenever the server is bound to loopback, Vitest defaults allowWrite and allowExec to true, letting browser-side story code write arbitrary files on the host or spawn OS commands.

In code/addons/vitest/src/vitest-plugin/index.ts (and follow-up commit 5540f03b20 for Vitest 4 and 5 compatibility), Storybook now sets both allowWrite and allowExec to false by default on both config.api and config.browser.api:

const { api, browser } = context.project.config as {
  api: ApiPermissions;
  browser: { api?: ApiPermissions };
};
for (const target of [api, browser.api]) {
  if (target) {
    target.allowWrite = userApiPermissions.allowWrite ?? false;
    target.allowExec = userApiPermissions.allowExec ?? false;
  }
}

IOCs and exposure markers

Because CVE-2026-114125 and CVE-2026-114126 are vulnerabilities in local development tooling rather than a single attacker campaign, there are no fixed C2 domains or malware hashes. Instead, responders should check for these local exposure and exploitation markers:

  • Uncommitted or unexpected edits in *.stories.js, *.stories.ts, *.stories.jsx, or *.stories.tsx created while storybook dev was running
  • Browser console warnings logged by patched builds (Ignored Storybook channel message from unexpected origin or Ignored "saveStoryRequest" event from a preview connection)
  • storybook dev processes listening on 0.0.0.0:6006 or :::6006 instead of 127.0.0.1:6006, or CLI scripts passing --host 0.0.0.0 without core.allowedHosts
  • Configuration files enabling security.insecureRemoteIframeEmbed: true or security.insecureIframeMessaging: true in .storybook/main.*, or allowWrite: true / allowExec: true in vitest.config.*

Detection and remediation

1. Audit installed Storybook versions across lockfiles

Check your repositories for affected versions of storybook, @storybook/core, @storybook/core-server, @storybook/addon-vitest, and @storybook/addon-mcp:

# Check installed versions in the current workspace
npm ls storybook @storybook/core @storybook/core-server @storybook/addon-vitest @storybook/addon-mcp @storybook/experimental-addon-test

# Search lockfiles and package manifests across repositories
rg -n '"(storybook|@storybook/(core|core-server|addon-vitest|addon-mcp|experimental-addon-test))"\s*:\s*"' \
  package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null

2. Upgrade to patched releases (and watch the @storybook/core v8 dist-tag)

  1. Upgrade to 8.6.19, 9.1.21, 10.6.2+ (10.6.3 is current), or 11.0.0-alpha.6+:
    # Storybook 10.x
    npm install --save-dev storybook@^10.6.3 @storybook/addon-vitest@^10.6.3 @storybook/addon-mcp@^10.6.3
    
    # Storybook 9.x
    npm install --save-dev storybook@^9.1.21 @storybook/addon-vitest@^9.1.21
    
    # Storybook 8.x (pin 8.6.19 explicitly; @storybook/core "latest" dist-tag is still 8.6.14)
    npm install --save-dev storybook@8.6.19 @storybook/core@8.6.19 @storybook/core-server@8.6.19
  2. Ensure Vitest is recent enough to honor allowWrite / allowExec: As noted in the patch comments, Vitest gates browser file writes and command execution on allowWrite and allowExec starting in Vitest 3.2.7 and 4.1.0; earlier Vitest versions ignore those flags.
  3. Avoid --host 0.0.0.0 on untrusted networks: If you need to share a Storybook preview with teammates, build a static bundle with storybook build rather than exposing the live storybook dev server on a shared network.

Timeline

  • 2026-08-19: The Storybook team receives a responsible disclosure demonstrating that a LAN peer can steal the dev server’s WebSocket token and chain story file writes with Vitest to execute code (CVE-2026-114126).
  • 2026-09-30: A second disclosure shows that a malicious website can embed the Storybook preview iframe and send events to the dev server (CVE-2026-114125).
  • 2026-10-12: Storybook merges the multi-branch security patches (243c06bf8f, 3be03ec6f1, 5540f03b20) and publishes 8.6.19, 9.1.21, 10.6.2, and 11.0.0-alpha.6 to npm.
  • 2026-10-21 (17:23 UTC): GHSA-f2ph-cmg5-wxc8 (CVE-2026-114125, CVSS v4.0 9.4 Critical) is published to the global GitHub Advisory Database and NVD.
  • 2026-10-22 (16:31 UTC): GHSA-px85-f4q5-jh77 (CVE-2026-114126, CVSS v4.0 8.7 High) is published to the global GitHub Advisory Database and NVD.

From research to remediation

Check whether this pattern exists in your codebase

Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.

References