Skip to main content

Introducing open weights model Apex Flash-1

All articles

Vulnerability Research

Storybook Could Let Someone on Your Network Change Your Code

Apex found a Storybook development-server flaw that could let someone on the same network inject code into a developer's story files.

Cantina Security Research 8 min read
Storybook Code injection Developer security Vitest Apex
On this page
Storybook could let someone on your network change your code. A network connection reaches a code editor and changes a highlighted line.

Technical details

  • Apex found a high-severity Storybook vulnerability. Someone on the same local network could inject code into a developer’s story files if they could access the development server.
  • The issue occurred because anyone who could load the page could get the token needed to authorize editing. The editing commands accepted input that turned into executable JavaScript when saved.
  • If the Vitest addon was used, an attacker could write extra files. Depending on the setup, this might allow code to run on the developer’s machine. Published static Storybook builds are not affected by this issue.
  • To fix this, update to version 8.6.19, 9.1.21, 10.6.2, or 11.0.0-alpha.6, depending on your release. The latest Storybook patches now limit network access by default, improve input checks, and turn off risky Vitest permissions. See the Storybook advisory for more details.

Apex, Cantina’s autonomous pentester identified a high-severity code injection vulnerability in Storybook’s development server that could let someone on the same reachable local network modify a developer’s story files, with a further path to executing code on the developer’s machine under specific conditions.

A developer could be using Storybook to check a button or test a form while another device on the network was connected to the development server. The page exposed the token used to authorize its editing commands, so loading that page could give someone a way to change project files without access to the team’s Git repository.

The vulnerability is rated High, with a CVSS v4 score of 8.7. Storybook released patches on October 12, 2026.

Why a preview had permission to edit files

Storybook helps teams build and test user interfaces outside their full application. Developers write “stories” that show a component in a particular state, such as a payment form with an error or a button while it is loading, then use Storybook to view and test those states.

The development server can also save changes to story files and start tests. Those features connect the browser to operations on the developer’s machine, so access to the server matters even when the project itself has never been published.

Storybook is widely used: npm recorded 25,115,316 downloads of the storybook package from October 5–11, 2026. That measures package downloads, including repeated installs, rather than unique developers or vulnerable machines.

Before the fix, a default development server could listen on network interfaces beyond the developer’s own machine. The change to the listening address explicitly addresses this exposure. A developer opening localhost:6006 in their browser did not mean that only their laptop could reach the server.

How the exposed token reached the developer’s code

Storybook’s browser interface used a WebSocket, a persistent connection to the development server, to send commands. In development mode, the manager configuration included CHANNEL_OPTIONS.wsToken, giving the browser the token it needed for that connection.

The server checked the token when accepting a WebSocket connection. However, a person who could load the page could obtain that same token. The token therefore failed to separate the developer from someone else who could reach the server.

One available command was saveStoryRequest. Its handler took the supplied story path, arguments and name, generated updated source code and wrote it to disk. The input checks did not sufficiently constrain the strings that became JavaScript names or the path used to select a story file.

For example, the code that duplicated a story assigned the supplied name directly to a JavaScript identifier:

// v10.6.1: code/core/src/core-server/utils/save-story/
// duplicate-story-with-new-name.ts:23–26
if (path.node.name === storyName) {
  found = true;
  path.node.name = newStoryName; // The supplied name becomes part of the code.
}

Two examples of input becoming code

For this write-up, we tested Storybook’s unchanged saveStoryRequest handler and helper files from 10.6.1 and 10.6.2 against their matching npm packages. Each test started with a small story showing a button labelled “Save”; the injected code only set an in-memory marker so its execution was visible.

A story name that adds code

When a developer saves a new story, the name field should contain a name such as Secondary. We instead supplied these fields in the save request:

{
  "name": "Demo = (globalThis.__storybookExample = \"story-name\"); export const Copy",
  "args": "{}"
}

Version 10.6.1 accepted the request and appended the following code to the fixture:

// fixtures/story-name-injection.stories.js:4–6, generated by 10.6.1
export const Demo = (globalThis.__storybookExample = "story-name"); export const Copy = {
  args: {}
};

The supplied “name” became an assignment followed by another export. When we evaluated the generated module, globalThis.__storybookExample became "story-name", confirming that the text had become executable JavaScript. The vulnerable helper had placed the whole string into an identifier without checking whether it was a valid name.

An argument name that adds an expression

The same problem appeared in argument names. The save request carries arguments as JSON in its args field; we supplied this object before serializing it:

{
  "label: (globalThis.__storybookExample = \"argument-name\"), other": true
}

Version 10.6.1 added the key as code inside the story’s args object:

// fixtures/argument-name-injection.stories.js:2–5, generated by 10.6.1
export const Primary = { args: {
  label: 'Save',
  label: (globalThis.__storybookExample = "argument-name"), other: true
} };

Evaluating this module set the marker to "argument-name". Although the input arrived as valid JSON, the argument update function treated the key as a JavaScript identifier when generating the file. The colon and comma therefore became part of the program’s syntax.

What the patched handler did

Version 10.6.2 rejected the first input with Invalid story name and the second with Invalid arg name, leaving both fixture files unchanged. Ordinary story names and argument updates still succeeded in both versions. These results match the protections covered by Storybook’s published regression tests.

This local reproduction covers the save handler and the generated JavaScript. It does not exercise the network connection or the later Vitest steps described below.

How the attack could go beyond story files

The disclosed attack chain continued through the testing tools:

  1. A person on the same network obtained the development server’s WebSocket token.
  2. They used the connection to insert code into a story file and request a test run.
  3. With the Vitest addon installed, the injected code could use a Vitest API to write additional files on the developer’s machine.
  4. Those files could lead to code execution, depending on their contents, destination and the software the developer used.

The Vitest integration’s patch explains why browser execution could reach the host: Vitest APIs could allow code running in the test browser to write files or run commands through Node. Storybook now disables both permissions by default.

For a developer, the immediate consequence was that someone outside the project could alter files through a tool running on their machine. The more serious outcome depended on the testing setup and subsequent execution of the written content; the advisory does not establish that every exposed Storybook server offered immediate control of the machine.

What Storybook changed

The security patch closes several parts of the chain.

The development server now stays on the local machine by default. The new address helper falls back to 127.0.0.1, which prevents another device from connecting directly. Sharing a live development server across a network now requires an explicit host setting.

Story edits receive stricter checks before reaching the filesystem. Story names must be valid JavaScript identifiers, and the save handler checks that the chosen path stays within the project root. Argument names receive their own validation:

 // code/core/src/core-server/utils/save-story/update-args-in-csf-file.ts
 // v10.6.1 → v10.6.2, lines 8–14 in the patched file
 const args = Object.fromEntries(
   Object.entries(input).map(([k, v]) => {
+    if (!t.isValidIdentifier(k, false)) {
+      throw new SaveStoryError(`Invalid arg name: ${JSON.stringify(k)}`);
+    }
     return [k, valueToAST(v)];
   })
 );

The path validation tests check that requests outside the project cannot write files, while still allowing legitimate stories elsewhere within the project.

The test browser loses unnecessary access to the host. The Vitest integration defaults allowWrite and allowExec to false, while preserving explicit user overrides. This limits what injected browser code could do even if another injection issue were found later.

Affected versions and what to do

The advisory lists the following affected storybook releases:

Release Affected Patched
8 >=8.0.0, <8.6.19 8.6.19
9 >=9.0.0, <9.1.21 9.1.21
10 >=10.0.0, <10.6.2 10.6.2
11 alpha >=11.0.0-alpha.0, <11.0.0-alpha.6 11.0.0-alpha.6

It also lists affected releases of @storybook/core, @storybook/core-server, @storybook/experimental-addon-test, @storybook/addon-vitest and @storybook/addon-mcp; their individual ranges differ.

Upgrade to the patched version for your release line before running the development server again. Published static Storybook builds are outside this advisory’s scope, as are servers that other network devices cannot reach. If you intentionally share a live development server, review its host configuration and restrict who can connect.

Disclosure timeline

  1. August 19, 2026: Storybook received the responsible disclosure described in the advisory.
  2. October 12, 2026: Storybook merged security PR #36726.
  3. October 12, 2026: Patched releases 8.6.19, 9.1.21, 10.6.2 and 11.0.0-alpha.6 became available, and Storybook published the advisory.

What this means for development tools

A credential only protects an operation if untrusted visitors cannot obtain it. Here, the page delivered the token needed to reach editing commands. Reviewing the token check alone would miss how easily someone could acquire the token.

Browser code inherits risk from the tools connected to it. Writing a story and running its tests linked the development server, generated source code and a browser with access to host APIs. Security testing needs to follow that whole sequence, including what each tool permits the next one to do.

Cantina uses Apex to investigate how bugs connect across code and running applications.

Sources