Skip to main content

MCP-002 Mutable git ref

An MCP server entry installs from a git source (git+https://..., https://github.com/..., or a uvx --from git+... pair) whose ref is a moving branch (HEAD, main, master, trunk, develop, dev) or is not pinned at all. Branches can be force-pushed, tags can be silently retagged; only a commit SHA is immutable. Without a SHA pin the next host launch can resolve to different code than the one you reviewed.

What Vulkro detects

Rule fires when an mcpServers[*] entry has an argument that parses as a git URL AND the ref portion is not an immutable SHA.

The detector recognises:

  • npm convention #<ref>: git+https://github.com/vendor/repo#main, git+https://github.com/vendor/repo#a1b2c3d4...
  • uvx / pip convention @<ref>: git+https://github.com/vendor/repo@main, git+https://github.com/vendor/repo@a1b2c3d4...
  • The uvx --from git+<url> <pkg> pair (the URL after --from is inspected, the URL is not double-counted with an inline form).
  • No ref at all (the URL ends after the repo name) which is treated as equivalent to HEAD.

Immutable pins that DO satisfy the rule:

  • A 40-character hex commit SHA (full).
  • A 7-12 character hex short SHA. Git's default short abbreviation is 7; the detector accepts up to 12 for safety.

Tags (v1.0.0, release-2024) and the moving labels listed above are treated as mutable. Tags can be silently retagged on the remote; the detector errs toward asking for a SHA pin.

Severity: High. Confidence: High. Evidence signal: mcp-server-mutable-git-ref, weight 0.8, source Pattern.

Same git URL only fires once per server even when it appears both inline and behind --from.

Non-compliant config

{
"mcpServers": {
"experimental": {
"command": "npx",
"args": [
"git+https://github.com/vendor/mcp-server-experimental#main"
]
},
"internal-tool": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/internal/mcp-tool",
"mcp-tool"
]
}
}
}

The main ref on the first entry can be force-pushed. The second entry has no ref at all, equivalent to HEAD.

Compliant config

{
"mcpServers": {
"experimental": {
"command": "npx",
"args": [
"git+https://github.com/vendor/mcp-server-experimental#a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
]
},
"internal-tool": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/internal/mcp-tool@a1b2c3d",
"mcp-tool"
]
}
}
}

Remediation

  1. Replace the branch ref with a commit SHA. Use the full 40-char form (#a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0) for clarity, or a 7-12 char short SHA when terseness matters.
  2. For an npm-style URL append #<sha>. For uvx --from, append @<sha> to the git URL inside the --from arg.
  3. Update the pin in a controlled review when you genuinely want a newer commit. The risk is silent code substitution at host launch, not the act of bumping the pin.

See also

  • vulkro mcp-audit - parent CLI command.
  • MCP-001 - the sibling rule for unpinned registry installs (handles npx/uvx against npm and PyPI).
  • MCP-006 - catalog hit for a known-compromised release.
  • Confidence model - what High, Medium, Low mean for MCP findings.

References