Skip to content

Commit 6c341c1

Browse files
authored
feat: fix safe target and add docs (#7795)
* feat: fix safe target and add docs * chore: add changeset * fix: changelog
1 parent 06d03d6 commit 6c341c1

9 files changed

Lines changed: 133 additions & 15 deletions

File tree

.changeset/cyan-elephants-hear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"shadcn": patch
3+
---
4+
5+
fix safe target handling

apps/v4/content/docs/(root)/changelog.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ description: Latest updates and announcements.
44
toc: false
55
---
66

7+
## July 2025 - Universal Registry Items
8+
9+
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.
10+
11+
This new registry item type unlocks a lot of new workflows. You can now distribute code, config, rules, docs, anything to any code project.
12+
13+
See the [docs](/docs/registry/examples) for more details and examples.
14+
715
## July 2025 - Local File Support
816

917
The shadcn CLI now supports local files. Initialize projects and add components, themes, hooks, utils and more from local JSON files.

apps/v4/content/docs/registry/examples.mdx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,3 +368,70 @@ Note: you need to define both `@keyframes` in css and `theme` in cssVars to use
368368
}
369369
}
370370
```
371+
372+
## Universal Items
373+
374+
As of `2.9.0`, you can create universal items that can be installed without framework detection or components.json.
375+
376+
To make an item universal i.e framework agnostic, all the files in the item must have an explicit target.
377+
378+
Here's an example of a registry item that installs custom Cursor rules for _python_:
379+
380+
```json title=".cursor/rules/custom-python.mdc" showLineNumbers {9}
381+
{
382+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
383+
"name": "python-rules",
384+
"type": "registry:item",
385+
"files": [
386+
{
387+
"path": "/path/to/your/registry/default/custom-python.mdc",
388+
"type": "registry:file",
389+
"target": "~/.cursor/rules/custom-python.mdc",
390+
"content": "..."
391+
}
392+
]
393+
}
394+
```
395+
396+
Here's another example for installation custom ESLint config:
397+
398+
```json title=".eslintrc.json" showLineNumbers {9}
399+
{
400+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
401+
"name": "my-eslint-config",
402+
"type": "registry:item",
403+
"files": [
404+
{
405+
"path": "/path/to/your/registry/default/custom-eslint.json",
406+
"type": "registry:file",
407+
"target": "~/.eslintrc.json",
408+
"content": "..."
409+
}
410+
]
411+
}
412+
```
413+
414+
You can also have a universal item that installs multiple files:
415+
416+
```json title="my-custom-starter-template.json" showLineNumbers {9}
417+
{
418+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
419+
"name": "my-custom-start-template",
420+
"type": "registry:item",
421+
dependencies: ["better-auth"]
422+
"files": [
423+
{
424+
"path": "/path/to/file-01.json",
425+
"type": "registry:file",
426+
"target": "~/file-01.json",
427+
"content": "..."
428+
},
429+
{
430+
"path": "/path/to/file-02.vue",
431+
"type": "registry:file",
432+
"target": "~/pages/file-02.vue",
433+
"content": "..."
434+
}
435+
]
436+
}
437+
```

apps/v4/content/docs/registry/index.mdx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
---
22
title: Registry
3-
description: Run your own component registry.
3+
description: Run your own code registry.
44
---
55

6+
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.
7+
68
<Callout>
7-
**Note:** This feature is currently experimental. Help us improve it by
8-
testing it out and sending feedback. If you have any questions, please [reach
9-
out to us](https://github.com/shadcn-ui/ui/discussions).
9+
**Note:** The registry works with any project type and any framework, and is
10+
not limited to React.
1011
</Callout>
1112

12-
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.
13-
1413
<figure className="flex flex-col gap-4">
1514
<Image
1615
src="/images/registry-light.png"
@@ -27,12 +26,10 @@ You can use the `shadcn` CLI to run your own component registry. Running your ow
2726
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
2827
/>
2928
<figcaption className="text-center text-sm text-gray-500">
30-
Distribute code to any React project.
29+
A distribution system for code
3130
</figcaption>
3231
</figure>
3332

34-
Registry items are automatically compatible with the `shadcn` CLI and `Open in v0`.
35-
3633
## Requirements
3734

3835
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).

apps/v4/content/docs/registry/registry-item-json.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ The following types are supported:
107107
| `registry:file` | Use for miscellaneous files. |
108108
| `registry:style` | Use for registry styles. eg. `new-york` |
109109
| `registry:theme` | Use for themes. |
110+
| `registry:item` | Use for universal registry items. |
110111

111112
### author
112113

apps/v4/public/schema/registry-item.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"registry:theme",
1818
"registry:page",
1919
"registry:file",
20-
"registry:style"
20+
"registry:style",
21+
"registry:item"
2122
],
2223
"description": "The type of the item. This is used to determine the type and target path of the item when resolved for a project."
2324
},
@@ -79,7 +80,8 @@
7980
"registry:theme",
8081
"registry:page",
8182
"registry:file",
82-
"registry:style"
83+
"registry:style",
84+
"registry:item"
8385
],
8486
"description": "The type of the file. This is used to determine the type of the file when resolved for a project."
8587
},

packages/shadcn/src/registry/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const registryItemTypeSchema = z.enum([
1313
"registry:file",
1414
"registry:theme",
1515
"registry:style",
16+
"registry:item",
1617

1718
// Internal use only
1819
"registry:example",

packages/shadcn/src/utils/is-safe-target.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ describe("isSafeTarget", () => {
6767
description: "Unicode normalization attacks",
6868
target: "foo/../\u2025/etc/passwd",
6969
},
70+
{
71+
description:
72+
"path traversal with square brackets outside [...] pattern",
73+
target: "foo/[bar]/../../etc/passwd",
74+
},
7075
])("$description", ({ target }) => {
7176
expect(isSafeTarget(target, cwd)).toBe(false)
7277
})
@@ -102,6 +107,26 @@ describe("isSafeTarget", () => {
102107
description: "path with special characters",
103108
target: "components/@ui/button.tsx",
104109
},
110+
{
111+
description: "framework routing with square brackets",
112+
target: "pages/[id].tsx",
113+
},
114+
{
115+
description: "catch-all routes with [...param]",
116+
target: "server/api/auth/[...].ts",
117+
},
118+
{
119+
description: "optional catch-all routes",
120+
target: "pages/[[...slug]].tsx",
121+
},
122+
{
123+
description: "dollar sign routes",
124+
target: "routes/$userId.tsx",
125+
},
126+
{
127+
description: "complex routing patterns",
128+
target: "app/[locale]/[...segments]/page.tsx",
129+
},
105130
])("$description", ({ target }) => {
106131
expect(isSafeTarget(target, cwd)).toBe(true)
107132
})

packages/shadcn/src/utils/is-safe-target.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,27 @@ export function isSafeTarget(targetPath: string, cwd: string): boolean {
2727
const normalizedRoot = path.normalize(cwd)
2828

2929
// Check for explicit path traversal sequences in both encoded and decoded forms.
30+
// Allow [...] pattern which is common in framework routing (e.g., [...slug])
31+
const hasPathTraversal = (path: string) => {
32+
// Remove [...] patterns before checking for ..
33+
const withoutBrackets = path.replace(/\[\.\.\..*?\]/g, "")
34+
return withoutBrackets.includes("..")
35+
}
36+
3037
if (
31-
normalizedTarget.includes("..") ||
32-
decodedPath.includes("..") ||
33-
targetPath.includes("..")
38+
hasPathTraversal(normalizedTarget) ||
39+
hasPathTraversal(decodedPath) ||
40+
hasPathTraversal(targetPath)
3441
) {
3542
return false
3643
}
3744

3845
// Check for current directory references that might be used in traversal.
46+
// First, remove [...] patterns to avoid false positives
47+
const cleanPath = (path: string) => path.replace(/\[\.\.\..*?\]/g, "")
48+
const cleanTarget = cleanPath(targetPath)
49+
const cleanDecoded = cleanPath(decodedPath)
50+
3951
const suspiciousPatterns = [
4052
/\.\.[\/\\]/, // ../ or ..\
4153
/[\/\\]\.\./, // /.. or \..
@@ -47,7 +59,7 @@ export function isSafeTarget(targetPath: string, cwd: string): boolean {
4759

4860
if (
4961
suspiciousPatterns.some(
50-
(pattern) => pattern.test(targetPath) || pattern.test(decodedPath)
62+
(pattern) => pattern.test(cleanTarget) || pattern.test(cleanDecoded)
5163
)
5264
) {
5365
return false

0 commit comments

Comments
 (0)