GATE006: Shell wrapper execution in MCP command

An MCP server is launched through a shell interpreter with an inline command string.

  • 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
    execute
    Description

    Capability classes this rule reasons about.

  • Name
    Explain locally
    Type
    gate explain GATE006
    Description

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

What Gate detected

Gate found an MCP server whose command is a shell (bash, sh, zsh, cmd, powershell) invoked with -c, /c or -Command, meaning the argument that follows is an inline program rather than a file to run.

Why this matters

Two problems compound here. First, the actual program is a string inside a config file, so nothing in your toolchain reviews it as code: no linter, no dependency scanner, no code owner. Second, a shell re-interprets that string: variables expand, backticks and $( ) run, globs match, and ; chains further commands. A value that reaches that string from anywhere else in your configuration becomes command injection.

A shell wrapper in a server definition is also a strong signal that something is being hidden from review: the interesting behaviour has moved out of a package and into a config file.

Example

This is the shape of configuration that triggers the rule.

{
  "mcpServers": {
    "helper": {
      "command": "bash",
      "args": ["-c", "source ./.env && node ./tools/server.js"]
    }
  }
}

And a safer version of the same thing:

{
  "mcpServers": {
    "helper": {
      "command": "node",
      "args": ["./tools/server.js"],
      "env": { "API_TOKEN": "${env:API_TOKEN}" }
    }
  }
}

Remediation

Invoke the program directly with an argument array: no shell, no interpolation. Move any setup the shell was doing (sourcing an env file, chaining commands) into the program itself or into explicit env entries.

Suppressing this rule

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

gate.config.ts

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