Skip to content

Core: Sanitize inputs for save from controls#33868

Merged
valentinpalkovic merged 5 commits into
nextfrom
valentin/sanitize-save-from-control-inputs
Feb 24, 2026
Merged

Core: Sanitize inputs for save from controls#33868
valentinpalkovic merged 5 commits into
nextfrom
valentin/sanitize-save-from-control-inputs

Conversation

@valentinpalkovic
Copy link
Copy Markdown
Contributor

@valentinpalkovic valentinpalkovic commented Feb 18, 2026

Closes #

What I did

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • Bug Fixes

    • Improved security when generating code/templates by escaping special characters in interpolated names and import paths to prevent XSS and malformed output.
  • Tests

    • Added tests covering character escaping and XSS protection to ensure generated files remain safe and correctly escaped.

@valentinpalkovic valentinpalkovic self-assigned this Feb 18, 2026
@valentinpalkovic valentinpalkovic added ci:daily Run the CI jobs that normally run in the daily job. bug patch:yes Bugfix & documentation PR that need to be picked to main branch core ci:normal and removed ci:daily Run the CI jobs that normally run in the daily job. labels Feb 18, 2026
@valentinpalkovic valentinpalkovic moved this to In Progress in Core Team Projects Feb 18, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 18, 2026

Fails
🚫 The "#### Manual testing" section must be filled in. Please describe how to test the changes you've made, step by step, so that reviewers can confirm your PR works as intended.

Generated by 🚫 dangerJS against 4d8237d

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Feb 18, 2026

View your CI Pipeline Execution ↗ for commit 4d8237d

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ❌ Failed 10m 22s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-24 12:18:52 UTC

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c4f034 and 4d8237d.

📒 Files selected for processing (2)
  • code/core/src/core-server/utils/safeString.test.ts
  • code/core/src/core-server/utils/safeString.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • code/core/src/core-server/utils/safeString.ts
  • code/core/src/core-server/utils/safeString.test.ts

📝 Walkthrough

Walkthrough

Adds escapeForTemplate to escape backslashes, quotes, dollar signs, backticks, newlines, and carriage returns for safe interpolation in templates; integrates it into story file generation to sanitize basename and preview import path before template interpolation.

Changes

Cohort / File(s) Summary
Template Escaping Utility
code/core/src/core-server/utils/safeString.ts, code/core/src/core-server/utils/safeString.test.ts
New exported escapeForTemplate function implemented and covered by Vitest tests validating escaping of backticks, dollar signs, backslashes, quotes, newlines, and combined cases, plus preservation of normal file paths.
Story File Generation Security
code/core/src/core-server/utils/get-new-story-file.ts, code/core/src/core-server/utils/get-new-story-file.test.ts
Imports and applies escapeForTemplate to basenameWithoutExtension and previewImportPath used in generated story templates; adds a test asserting generated output contains escaped import/path values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
code/core/src/core-server/utils/safeString.test.ts (1)

4-23: Consider adding test coverage for carriage return and other matched characters.

The safeJsString regex matches \r (carriage return) and \t (tab), but no tests verify their escaping behavior. Adding tests for these would improve coverage and help catch regressions if the function is modified.

🧪 Suggested additional tests
   it('should skip escaping if not needed', () => {
     expect(safeJsString('./button.tsx')).toMatchInlineSnapshot(`"./button.tsx"`);
   });
+
+  it('should escape carriage return characters', () => {
+    expect(safeJsString('line1\rline2')).toMatchInlineSnapshot(`"line1\\rline2"`);
+  });
+
+  it('should escape tab characters', () => {
+    expect(safeJsString('col1\tcol2')).toMatchInlineSnapshot(`"col1\\tcol2"`);
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/utils/safeString.test.ts` around lines 4 - 23, Add
unit tests for carriage return and tab escaping to cover characters matched by
safeJsString's regex: add cases asserting safeJsString('const s = "\\rfoo"') and
safeJsString('const s = "\\tfoo"') (and optionally a mixed string containing \r
and \t together) produce the correctly escaped output; place them alongside the
existing tests in safeString.test.ts referencing the safeJsString function so
changes to the regex will be caught by CI.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/core-server/utils/safeString.ts`:
- Around line 6-14: The regex in the replace call inside safeString.ts includes
\b, \f and \t but the codes mapping object (codes) lacks entries for backspace,
form-feed and tab so those characters are left unescaped; update the codes
object used by the str.replace callback (the codes constant in the replace
handler) to include mappings for "\b": "\\b", "\f": "\\f" and "\t": "\\t" (or
alternatively remove those sequences from the regex), ensuring the replace
callback returns the proper escaped sequences for all characters matched by the
regex.

---

Nitpick comments:
In `@code/core/src/core-server/utils/safeString.test.ts`:
- Around line 4-23: Add unit tests for carriage return and tab escaping to cover
characters matched by safeJsString's regex: add cases asserting
safeJsString('const s = "\\rfoo"') and safeJsString('const s = "\\tfoo"') (and
optionally a mixed string containing \r and \t together) produce the correctly
escaped output; place them alongside the existing tests in safeString.test.ts
referencing the safeJsString function so changes to the regex will be caught by
CI.

Comment thread code/core/src/core-server/utils/safeString.ts Outdated
@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Feb 18, 2026

Package Benchmarks

Commit: 4d8237d, ran on 24 February 2026 at 12:17:37 UTC

The following packages have significant changes to their size or dependencies:

@storybook/addon-vitest

Before After Difference
Dependency count 2 2 0
Self size 402 KB 391 KB 🎉 -11 KB 🎉
Dependency size 338 KB 338 KB 🎉 -3 B 🎉
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 535 535 0
Self size 648 KB 648 KB 🎉 -5 B 🎉
Dependency size 59.87 MB 59.91 MB 🚨 +36 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 92 92 0
Self size 1.12 MB 1.12 MB 🎉 -41 B 🎉
Dependency size 22.39 MB 22.43 MB 🚨 +36 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 124 124 0
Self size 30 KB 30 KB 🚨 +37 B 🚨
Dependency size 23.68 MB 23.71 MB 🚨 +36 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 82 82 0
Self size 35 KB 35 KB 🎉 -4 B 🎉
Dependency size 20.17 MB 20.21 MB 🚨 +36 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 274 274 0
Self size 24 KB 24 KB 🎉 -5 B 🎉
Dependency size 44.54 MB 44.57 MB 🚨 +36 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 58 57 🎉 -1 🎉
Self size 1.19 MB 1.23 MB 🚨 +37 KB 🚨
Dependency size 13.20 MB 12.94 MB 🎉 -264 KB 🎉
Bundle Size Analyzer Link Link

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request attempts to add input sanitization for the "create new story" feature by escaping special characters in file paths and export names. However, the implementation has critical flaws that would break the functionality.

Changes:

  • Added a safeJsString utility function to escape special characters in strings
  • Added unit tests for the sanitization function
  • Applied sanitization to componentFilePath and componentExportName in the create-new-story-channel handler

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

File Description
code/core/src/core-server/utils/safeString.ts New utility function to escape JavaScript string special characters
code/core/src/core-server/utils/safeString.test.ts Unit tests for the safeJsString function
code/core/src/core-server/server-channel/create-new-story-channel.ts Integration of sanitization before passing data to generateStoryFile

Comment thread code/core/src/core-server/utils/safeString.ts Outdated
Comment thread code/core/src/core-server/server-channel/create-new-story-channel.ts Outdated
Comment thread code/core/src/core-server/utils/safeString.test.ts Outdated
Comment thread code/core/src/core-server/utils/safeString.test.ts Outdated
Comment thread code/core/src/core-server/utils/safeString.test.ts Outdated
Comment thread code/core/src/core-server/utils/safeString.test.ts Outdated
Comment thread code/core/src/core-server/utils/safeString.test.ts Outdated
Comment thread code/core/src/core-server/utils/safeString.test.ts Outdated
Comment thread code/core/src/core-server/utils/safeString.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/core/src/core-server/utils/get-new-story-file.ts (1)

43-51: ⚠️ Potential issue | 🟡 Minor

Don’t reuse escaped basename for filesystem paths.

escapeForTemplate() inserts backslashes, which then flow into alternativeStoryFileNameWithExtension. That changes the actual filename (and can introduce path separators on Windows), leading to unexpected file paths or failures when component names include ', $, or \. Use a raw basename for filenames and an escaped one only for template interpolation.

Suggested fix (separate raw vs. escaped)
   const base = basename(componentFilePath);
   const extension = extname(componentFilePath);
-  const basenameWithoutExtension = escapeForTemplate(base.replace(extension, ''));
+  const rawBasenameWithoutExtension = base.replace(extension, '');
+  const basenameWithoutExtension = escapeForTemplate(rawBasenameWithoutExtension);
   const dir = dirname(componentFilePath);

   const { storyFileName, isTypescript, storyFileExtension } = getStoryMetadata(componentFilePath);
   const storyFileNameWithExtension = `${storyFileName}.${storyFileExtension}`;
-  const alternativeStoryFileNameWithExtension = `${basenameWithoutExtension}.${componentExportName}.stories.${storyFileExtension}`;
+  const alternativeStoryFileNameWithExtension = `${rawBasenameWithoutExtension}.${componentExportName}.stories.${storyFileExtension}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/utils/get-new-story-file.ts` around lines 43 - 51,
The code currently reuses basenameWithoutExtension (which has been run through
escapeForTemplate) when building alternativeStoryFileNameWithExtension, causing
backslashes and escape characters to end up in actual filenames; change to keep
two values: a raw basenameWithoutExtensionRaw = base.replace(extension, '') for
filesystem names and an escaped basenameWithoutExtensionEscaped =
escapeForTemplate(basenameWithoutExtensionRaw) for template interpolation, then
use basenameWithoutExtensionRaw when constructing
alternativeStoryFileNameWithExtension and keep the escaped version only where
templates need it (references: escapeForTemplate,
alternativeStoryFileNameWithExtension, basenameWithoutExtension, base,
componentExportName, getStoryMetadata).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@code/core/src/core-server/utils/get-new-story-file.ts`:
- Around line 43-51: The code currently reuses basenameWithoutExtension (which
has been run through escapeForTemplate) when building
alternativeStoryFileNameWithExtension, causing backslashes and escape characters
to end up in actual filenames; change to keep two values: a raw
basenameWithoutExtensionRaw = base.replace(extension, '') for filesystem names
and an escaped basenameWithoutExtensionEscaped =
escapeForTemplate(basenameWithoutExtensionRaw) for template interpolation, then
use basenameWithoutExtensionRaw when constructing
alternativeStoryFileNameWithExtension and keep the escaped version only where
templates need it (references: escapeForTemplate,
alternativeStoryFileNameWithExtension, basenameWithoutExtension, base,
componentExportName, getStoryMetadata).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60cae62 and 3c4f034.

📒 Files selected for processing (4)
  • code/core/src/core-server/utils/get-new-story-file.test.ts
  • code/core/src/core-server/utils/get-new-story-file.ts
  • code/core/src/core-server/utils/safeString.test.ts
  • code/core/src/core-server/utils/safeString.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/core-server/utils/safeString.test.ts

@valentinpalkovic valentinpalkovic merged commit 12db183 into next Feb 24, 2026
119 of 126 checks passed
@valentinpalkovic valentinpalkovic deleted the valentin/sanitize-save-from-control-inputs branch February 24, 2026 13:05
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Core Team Projects Feb 24, 2026
@github-actions github-actions Bot mentioned this pull request Feb 24, 2026
5 tasks
valentinpalkovic added a commit that referenced this pull request Feb 24, 2026
…m-control-inputs

Core: Sanitize inputs for save from controls
(cherry picked from commit 12db183)
@github-actions github-actions Bot added the patch:done Patch/release PRs already cherry-picked to main/release branch label Feb 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ci:normal core patch:done Patch/release PRs already cherry-picked to main/release branch patch:yes Bugfix & documentation PR that need to be picked to main branch

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants