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):
CVE-2026-114125(GHSA-f2ph-cmg5-wxc8, CVSS v4.0 9.4 Critical): Whilestorybook devis running locally, a third-party website can embed the Storybook preview iframe (http://localhost:6006/iframe.html) and send cross-originpostMessageevents into the preview channel. By chaining the preview’sHIGHLIGHTmenu handler into the server WebSocket channel, an attacker can triggercreateNewStoryfileRequestorsaveStoryRequest, inject arbitrary JavaScript into a.stories.*file on disk, and execute it with the developer’s OS privileges.CVE-2026-114126(GHSA-px85-f4q5-jh77, CVSS v4.0 8.7 High): Before the patch,storybook devbound 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
| Ecosystem | Package | CVE / GHSA | Affected versions | Fixed versions | Notes |
|---|---|---|---|---|---|
| npm | storybook | CVE-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.6 | 8.6.19, 9.1.21, 10.6.2, 11.0.0-alpha.6 | Main CLI and dev server across v8 through v11 |
| npm | @storybook/core, @storybook/core-server | CVE-2026-114125, CVE-2026-114126 | >= 8.0.0, < 8.6.19 | 8.6.19 | On 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-test | CVE-2026-114125, CVE-2026-114126 | >= 8.0.0, < 8.6.19 | 8.6.19 | Storybook 8 test runner addon |
| npm | @storybook/addon-vitest | CVE-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.6 | 9.1.21, 10.6.2, 11.0.0-alpha.6 | Disables Vitest Browser RPC allowWrite and allowExec by default |
| npm | @storybook/addon-mcp | CVE-2026-114125, CVE-2026-114126 | >= 10.6.0, < 10.6.2, >= 11.0.0-alpha.0, < 11.0.0-alpha.6 | 10.6.2, 11.0.0-alpha.6 | Enforces 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:
- In
code/core/src/highlight/useHighlights.ts, theHIGHLIGHTchannel event accepted amenuarray where each item specified an arbitrary channel event name inclickEvent. When the developer clicked the highlight overlay in the embedded iframe,useHighlightsemittedchannel.emit(event, id, getEventDetails(target))onto the shared channel, including onto the WebSocket transport. - 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 themanagerUI or thepreviewiframe.
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.tsandnew-story-templates/*.ts,componentExportNameandbasenameWithoutExtensionwere interpolated directly into the generated.stories.ts/.stories.jssource (import { ${importName} } from './${data.basenameWithoutExtension}') without checking whethercomponentExportNamewas 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 thatsourceFilePathstayed inside the project root or thatnamewas 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.tsxcreated whilestorybook devwas running - Browser console warnings logged by patched builds (
Ignored Storybook channel message from unexpected originorIgnored "saveStoryRequest" event from a preview connection) storybook devprocesses listening on0.0.0.0:6006or:::6006instead of127.0.0.1:6006, or CLI scripts passing--host 0.0.0.0withoutcore.allowedHosts- Configuration files enabling
security.insecureRemoteIframeEmbed: trueorsecurity.insecureIframeMessaging: truein.storybook/main.*, orallowWrite: true/allowExec: trueinvitest.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)
- Upgrade to
8.6.19,9.1.21,10.6.2+(10.6.3is current), or11.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 - Ensure Vitest is recent enough to honor
allowWrite/allowExec: As noted in the patch comments, Vitest gates browser file writes and command execution onallowWriteandallowExecstarting in Vitest3.2.7and4.1.0; earlier Vitest versions ignore those flags. - Avoid
--host 0.0.0.0on untrusted networks: If you need to share a Storybook preview with teammates, build a static bundle withstorybook buildrather than exposing the livestorybook devserver 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 publishes8.6.19,9.1.21,10.6.2, and11.0.0-alpha.6to 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
- Storybook advisory GHSA-f2ph-cmg5-wxc8 (CVE-2026-114125)
- Storybook advisory GHSA-px85-f4q5-jh77 (CVE-2026-114126)
- storybookjs/storybook PR #36726 and commit 243c06bf8f
- storybookjs/storybook commit 3be03ec6f1 (v9.1.21 security patch)
- storybookjs/storybook commit 5540f03b20 (Vitest 4 and 5 browser API permissions)
- NVD: CVE-2026-114125
- NVD: CVE-2026-114126
- VulDB: CVE-2026-114125 analysis