GATE007: Unpinned MCP package

An MCP server is launched with a package runner and no pinned version.

  • Name
    Severity
    Type
    Medium
    Description

    Default severity. An individual finding may be reported higher or lower when the surrounding configuration justifies it.

  • Name
    Explain locally
    Type
    gate explain GATE007
    Description

    The same text, in your terminal, with no network access.

What Gate detected

Gate found an MCP server started with npx, bunx, uvx, pnpm dlx, yarn dlx or pipx where the package specifier has no exact version, or is pinned to a moving tag such as @latest. Auto-confirm flags (-y, --yes) are noted because they remove the one prompt that would otherwise show you what is about to be installed.

Why this matters

An unpinned package runner resolves and executes whatever the registry serves at that moment, with no lockfile and no review. That means a compromised maintainer account, a hijacked package name, or a typosquat takes effect on the next agent start: inside a process you have already handed your credentials to.

This is a lower severity than the execution rules because it is a conditional risk: it requires an upstream compromise. It is on the list because agent configuration is one of the few remaining places where developers routinely run unpinned code by default.

Example

This is the shape of configuration that triggers the rule.

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"]
    }
  }
}

And a safer version of the same thing:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github@2025.4.8"]
    }
  }
}

Remediation

Pin the exact version, and update it deliberately. Better still, add the server as a project dependency so it is covered by your lockfile, your dependency scanner and your review process.

Suppressing this rule

If this finding is acceptable in your repository, record why alongside the suppression:

gate.config.ts

export default defineConfig({
  ignore: [
    {
      rule: 'GATE007',
      reason: 'Why this is acceptable here',
    },
  ],
})

Gate refuses to apply an ignore entry with no reason. The reason is the only thing that will tell the next person whether the suppression is still true.

References

Was this page helpful?