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.