CodeVetter · Repo Unpack · Risk map
shadcn-ui/ui
The dominant risk is supply-chain-style: remote registry JSON drives arbitrary file writes, env-file edits, and package installs inside user projects. Mitigations exist (isSafeTarget path validation, per-origin header scoping on redirects, typed errors, header-safe token checks) but content itself is trusted. Secondary risks: a huge init.ts action with process.exit calls, dead command modules, committed generated registry output, canary deps in the site, and an always-`latest` MCP install instruction.
- Registry items are a remote-code trust boundary: fetched file.content is written into the user's project after validateFilesTarget checks each target/path with isSafeTarget (null bytes, iterative URL-decode traversal, drive letters, absolute-path containment inside cwd) — paths are guarded, file contents are not.
- Private-registry secrets ride in headers/params expanded from ${ENV}; fetchWithProxy follows up to 5 redirects manually and re-attaches caller headers only for same-origin hops (Accept/User-Agent survive cross-origin) — deliberate leak prevention that must be preserved.
- Shell-out surface: updateDependencies execa's package-manager installs of registry-declared deps; `mcp init` runs `<pm> add -D shadcn@latest`; github-cli transport execa's `gh api` with 15s timeout — item-controlled dependency names reach the package manager.
- GitHub item fetching supports GH_TOKEN/GITHUB_TOKEN tokens or the `gh` CLI; tokens are validated against a printable-ASCII pattern before use and GitHubTransportError carries only sanitized fields — raw subprocess output is intentionally never attached.
- init.ts is a 1049-line commander action with ~10 process.exit calls inside prompts; components.json is backed up via FILE_BACKUP_SUFFIX and restored through a process 'exit' listener — fragile control flow where early exits must not leak backups or half-written config.
- Dead command modules: packages/shadcn/src/commands/registry/build.ts and commands/registry/mcp.ts are exported but never registered — the `registry` command only adds `add` and `validate`; stale duplicates of live functionality (build lives at commands/build.ts).
- Generated output is committed: 871 files under apps/v4/public/r are tracked in git while also being build-registry.mts 'persistent outputs' — drift between authored registry sources and committed artifacts is possible if build is skipped before commit.
- Registry items can merge env vars into user .env files (updateEnvVars + env-file merge logic in update-files) — a malicious registry could inject or overwrite environment config.
- apps/v4 pins canary/prerelease deps (@ai-sdk/* canary builds, ai: 'canary', @tanstack/ai betas) — the site/registry build is exposed to upstream instability.
- MCP install instructions hardcode `shadcn@latest` (SHADCN_MCP_VERSION='latest') and tell clients to run `npx shadcn@latest mcp` — no version pinning for the server AI clients will execute.
- Module-level registryCache in fetcher.ts persists for the process lifetime keyed by url+headers hash; in the long-lived MCP server this can serve stale items (mitigated by useCache:false in MCP tool calls). (inferred)
- init propagates design settings into every workspace package's components.json (writes outside the top-level cwd's own config) — a failed run mid-loop could leave inconsistent workspace configs. (inferred)