From 6c341c16aeaf5ade177a4a1ba4fb9afcd33d5fee Mon Sep 17 00:00:00 2001 From: shadcn Date: Fri, 11 Jul 2025 18:19:34 +0400 Subject: [PATCH] feat: fix safe target and add docs (#7795) * feat: fix safe target and add docs * chore: add changeset * fix: changelog --- .changeset/cyan-elephants-hear.md | 5 ++ apps/v4/content/docs/(root)/changelog.mdx | 8 +++ apps/v4/content/docs/registry/examples.mdx | 67 +++++++++++++++++++ apps/v4/content/docs/registry/index.mdx | 17 ++--- .../docs/registry/registry-item-json.mdx | 1 + apps/v4/public/schema/registry-item.json | 6 +- packages/shadcn/src/registry/schema.ts | 1 + .../shadcn/src/utils/is-safe-target.test.ts | 25 +++++++ packages/shadcn/src/utils/is-safe-target.ts | 20 ++++-- 9 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 .changeset/cyan-elephants-hear.md diff --git a/.changeset/cyan-elephants-hear.md b/.changeset/cyan-elephants-hear.md new file mode 100644 index 0000000000..6506306120 --- /dev/null +++ b/.changeset/cyan-elephants-hear.md @@ -0,0 +1,5 @@ +--- +"shadcn": patch +--- + +fix safe target handling diff --git a/apps/v4/content/docs/(root)/changelog.mdx b/apps/v4/content/docs/(root)/changelog.mdx index 0dd7f74cca..6308c43433 100644 --- a/apps/v4/content/docs/(root)/changelog.mdx +++ b/apps/v4/content/docs/(root)/changelog.mdx @@ -4,6 +4,14 @@ description: Latest updates and announcements. toc: false --- +## July 2025 - Universal Registry Items + +We've added support for universal registry items. This allows you to create registry items that can be distributed to any project i.e. no framework, no components.json, no tailwind, no react required. + +This new registry item type unlocks a lot of new workflows. You can now distribute code, config, rules, docs, anything to any code project. + +See the [docs](/docs/registry/examples) for more details and examples. + ## July 2025 - Local File Support The shadcn CLI now supports local files. Initialize projects and add components, themes, hooks, utils and more from local JSON files. diff --git a/apps/v4/content/docs/registry/examples.mdx b/apps/v4/content/docs/registry/examples.mdx index cbe6f1fcd3..c2cde08d74 100644 --- a/apps/v4/content/docs/registry/examples.mdx +++ b/apps/v4/content/docs/registry/examples.mdx @@ -368,3 +368,70 @@ Note: you need to define both `@keyframes` in css and `theme` in cssVars to use } } ``` + +## Universal Items + +As of `2.9.0`, you can create universal items that can be installed without framework detection or components.json. + +To make an item universal i.e framework agnostic, all the files in the item must have an explicit target. + +Here's an example of a registry item that installs custom Cursor rules for _python_: + +```json title=".cursor/rules/custom-python.mdc" showLineNumbers {9} +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "python-rules", + "type": "registry:item", + "files": [ + { + "path": "/path/to/your/registry/default/custom-python.mdc", + "type": "registry:file", + "target": "~/.cursor/rules/custom-python.mdc", + "content": "..." + } + ] +} +``` + +Here's another example for installation custom ESLint config: + +```json title=".eslintrc.json" showLineNumbers {9} +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "my-eslint-config", + "type": "registry:item", + "files": [ + { + "path": "/path/to/your/registry/default/custom-eslint.json", + "type": "registry:file", + "target": "~/.eslintrc.json", + "content": "..." + } + ] +} +``` + +You can also have a universal item that installs multiple files: + +```json title="my-custom-starter-template.json" showLineNumbers {9} +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "my-custom-start-template", + "type": "registry:item", + dependencies: ["better-auth"] + "files": [ + { + "path": "/path/to/file-01.json", + "type": "registry:file", + "target": "~/file-01.json", + "content": "..." + }, + { + "path": "/path/to/file-02.vue", + "type": "registry:file", + "target": "~/pages/file-02.vue", + "content": "..." + } + ] +} +``` diff --git a/apps/v4/content/docs/registry/index.mdx b/apps/v4/content/docs/registry/index.mdx index 68eb454235..7bea8fc622 100644 --- a/apps/v4/content/docs/registry/index.mdx +++ b/apps/v4/content/docs/registry/index.mdx @@ -1,15 +1,14 @@ --- title: Registry -description: Run your own component registry. +description: Run your own code registry. --- - - **Note:** This feature is currently experimental. Help us improve it by - testing it out and sending feedback. If you have any questions, please [reach - out to us](https://github.com/shadcn-ui/ui/discussions). - +You can use the `shadcn` CLI to run your own code registry. Running your own registry allows you to distribute your custom components, hooks, pages, config, rules and other files to any project. -You can use the `shadcn` CLI to run your own component registry. Running your own registry allows you to distribute your custom components, hooks, pages, and other files to any React project. + + **Note:** The registry works with any project type and any framework, and is + not limited to React. +
- Distribute code to any React project. + A distribution system for code
-Registry items are automatically compatible with the `shadcn` CLI and `Open in v0`. - ## Requirements You are free to design and host your custom registry as you see fit. The only requirement is that your registry items must be valid JSON files that conform to the [registry-item schema specification](/docs/registry/registry-item-json). diff --git a/apps/v4/content/docs/registry/registry-item-json.mdx b/apps/v4/content/docs/registry/registry-item-json.mdx index 527a3675cf..6942ec854f 100644 --- a/apps/v4/content/docs/registry/registry-item-json.mdx +++ b/apps/v4/content/docs/registry/registry-item-json.mdx @@ -107,6 +107,7 @@ The following types are supported: | `registry:file` | Use for miscellaneous files. | | `registry:style` | Use for registry styles. eg. `new-york` | | `registry:theme` | Use for themes. | +| `registry:item` | Use for universal registry items. | ### author diff --git a/apps/v4/public/schema/registry-item.json b/apps/v4/public/schema/registry-item.json index 18e03d59bf..b343f77d10 100644 --- a/apps/v4/public/schema/registry-item.json +++ b/apps/v4/public/schema/registry-item.json @@ -17,7 +17,8 @@ "registry:theme", "registry:page", "registry:file", - "registry:style" + "registry:style", + "registry:item" ], "description": "The type of the item. This is used to determine the type and target path of the item when resolved for a project." }, @@ -79,7 +80,8 @@ "registry:theme", "registry:page", "registry:file", - "registry:style" + "registry:style", + "registry:item" ], "description": "The type of the file. This is used to determine the type of the file when resolved for a project." }, diff --git a/packages/shadcn/src/registry/schema.ts b/packages/shadcn/src/registry/schema.ts index 6b1af728a0..3b3773fd76 100644 --- a/packages/shadcn/src/registry/schema.ts +++ b/packages/shadcn/src/registry/schema.ts @@ -13,6 +13,7 @@ export const registryItemTypeSchema = z.enum([ "registry:file", "registry:theme", "registry:style", + "registry:item", // Internal use only "registry:example", diff --git a/packages/shadcn/src/utils/is-safe-target.test.ts b/packages/shadcn/src/utils/is-safe-target.test.ts index 490f478f74..c76d921cca 100644 --- a/packages/shadcn/src/utils/is-safe-target.test.ts +++ b/packages/shadcn/src/utils/is-safe-target.test.ts @@ -67,6 +67,11 @@ describe("isSafeTarget", () => { description: "Unicode normalization attacks", target: "foo/../\u2025/etc/passwd", }, + { + description: + "path traversal with square brackets outside [...] pattern", + target: "foo/[bar]/../../etc/passwd", + }, ])("$description", ({ target }) => { expect(isSafeTarget(target, cwd)).toBe(false) }) @@ -102,6 +107,26 @@ describe("isSafeTarget", () => { description: "path with special characters", target: "components/@ui/button.tsx", }, + { + description: "framework routing with square brackets", + target: "pages/[id].tsx", + }, + { + description: "catch-all routes with [...param]", + target: "server/api/auth/[...].ts", + }, + { + description: "optional catch-all routes", + target: "pages/[[...slug]].tsx", + }, + { + description: "dollar sign routes", + target: "routes/$userId.tsx", + }, + { + description: "complex routing patterns", + target: "app/[locale]/[...segments]/page.tsx", + }, ])("$description", ({ target }) => { expect(isSafeTarget(target, cwd)).toBe(true) }) diff --git a/packages/shadcn/src/utils/is-safe-target.ts b/packages/shadcn/src/utils/is-safe-target.ts index 22886e2201..baab35992c 100644 --- a/packages/shadcn/src/utils/is-safe-target.ts +++ b/packages/shadcn/src/utils/is-safe-target.ts @@ -27,15 +27,27 @@ export function isSafeTarget(targetPath: string, cwd: string): boolean { const normalizedRoot = path.normalize(cwd) // Check for explicit path traversal sequences in both encoded and decoded forms. + // Allow [...] pattern which is common in framework routing (e.g., [...slug]) + const hasPathTraversal = (path: string) => { + // Remove [...] patterns before checking for .. + const withoutBrackets = path.replace(/\[\.\.\..*?\]/g, "") + return withoutBrackets.includes("..") + } + if ( - normalizedTarget.includes("..") || - decodedPath.includes("..") || - targetPath.includes("..") + hasPathTraversal(normalizedTarget) || + hasPathTraversal(decodedPath) || + hasPathTraversal(targetPath) ) { return false } // Check for current directory references that might be used in traversal. + // First, remove [...] patterns to avoid false positives + const cleanPath = (path: string) => path.replace(/\[\.\.\..*?\]/g, "") + const cleanTarget = cleanPath(targetPath) + const cleanDecoded = cleanPath(decodedPath) + const suspiciousPatterns = [ /\.\.[\/\\]/, // ../ or ..\ /[\/\\]\.\./, // /.. or \.. @@ -47,7 +59,7 @@ export function isSafeTarget(targetPath: string, cwd: string): boolean { if ( suspiciousPatterns.some( - (pattern) => pattern.test(targetPath) || pattern.test(decodedPath) + (pattern) => pattern.test(cleanTarget) || pattern.test(cleanDecoded) ) ) { return false