GATE020: Environment passthrough exposes multiple credentials to one server

A single MCP server is handed three or more credentials, or an entire environment file.

  • Name
    Severity
    Type
    High
    Description

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

  • Name
    Capabilities
    Type
    secrets
    Description

    Capability classes this rule reasons about.

  • Name
    Explain locally
    Type
    gate explain GATE020
    Description

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

What Gate detected

Gate found an MCP server whose configuration supplies three or more distinct credentials, or which is pointed at an environment file such as .env.

Why this matters

Each credential handed to a server is a credential that server can lose. Bundling several into one process means a single compromised or malicious MCP server, or a single prompt injection that reaches a tool on it: yields all of them at once. This is the difference between an incident scoped to one integration and an incident scoped to your whole stack. An .env file is worse still: it is every secret the application has, handed over as a unit, including the ones the server has no use for.

Example

This is the shape of configuration that triggers the rule.

{
  "mcpServers": {
    "kitchen-sink": {
      "command": "node",
      "args": ["./tools/server.js", "--env-file", ".env"],
      "env": {
        "GITHUB_TOKEN": "${env:GITHUB_TOKEN}",
        "AWS_SECRET_ACCESS_KEY": "${env:AWS_SECRET_ACCESS_KEY}",
        "STRIPE_SECRET_KEY": "${env:STRIPE_SECRET_KEY}",
        "OPENAI_API_KEY": "${env:OPENAI_API_KEY}"
      }
    }
  }
}

Remediation

Split the server, or narrow what it receives. Give each MCP server exactly the credentials its own tools need and nothing else. Never pass a shared .env file to a server: enumerate the variables explicitly so that the list is reviewable.

Suppressing this rule

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

gate.config.ts

export default defineConfig({
  ignore: [
    {
      rule: 'GATE020',
      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?