Skip to main content

MCP-001 Unpinned npx / uvx registry install

An MCP server entry runs npx <pkg> or uvx <pkg> against a registry package with no version pin. Both runners resolve the package fresh on every host launch. A publisher account takeover, a maintainer swap, or a typo on the publisher's next push lands directly inside the process your LLM client has agreed to run, with whatever filesystem and credential reach the host grants.

This is the simplest supply-chain trap in the MCP space and the easiest one to close: pin the version.

What Vulkro detects

Rule fires when an mcpServers[*] entry in any MCP host config has:

  1. command of npx or uvx.
  2. A first positional argument that is a registry package spec (not a local path, not a git URL).
  3. No version pin on the spec.

Pin detection is per-runner:

  • npm-style ([email protected], @scope/[email protected], pkg@^1.0.0, tags like pkg@beta) for npx. Bare @scope/pkg is NOT treated as pinned (the leading @ is the scope marker, not a version).
  • Python name==version (or npm-style as a fallback) for uvx.

Skipped: local paths (./bin/server, /usr/local/..., ~/...), git URLs (handled by MCP-002).

Severity: Medium by default. Escalates to High when the server has filesystem reach (the package name contains server-filesystem, filesystem-server, mcp-server-fs, or server-fs); a fresh resolve of a typosquatted filesystem server can read everything the mount points at.

Evidence signal: mcp-server-unpinned-npx, weight 0.8, source Pattern. Confidence: High.

Non-compliant config

{
"mcpServers": {
"fs": {
"command": "npx",
"args": [
"@modelcontextprotocol/server-filesystem",
"/Users/me/work"
]
},
"github": {
"command": "uvx",
"args": ["mcp-server-github"]
}
}
}

Both entries re-resolve on every Claude Desktop / Cursor / Windsurf / VS Code launch.

Compliant config

{
"mcpServers": {
"fs": {
"command": "npx",
"args": [
"@modelcontextprotocol/[email protected]",
"/Users/me/work"
]
},
"github": {
"command": "uvx",
"args": ["mcp-server-github==0.4.2"]
}
}
}

Remediation

  1. Add an explicit version to the package spec. For npm, [email protected] or @scope/[email protected]. For uvx targeting PyPI, pkg==x.y.z.
  2. Update the pin in a controlled review when you genuinely want the newer build. The supply-chain footgun is silent re-resolution at launch time, not the act of updating.
  3. If you need a moving label for development, prefer pkg@<commit-sha> against a known release tag rather than a bare name.

See also

  • vulkro mcp-audit - parent CLI command.
  • MCP-002 - the sibling rule for git-source installs with a mutable ref.
  • MCP-006 - exact-version catalog hit (known-compromised release).
  • Confidence model - what High, Medium, Low mean for MCP findings.

References