- Case ID
- #4842
- Severity
- INFO
- Decision
- BLOCK
- Agent
- All Agents
- Category
- Governance
Correction note (2026-10-13). The per-call timings and latency figures that appeared in this article had no benchmark behind them and were removed. Sample output in this article is illustrative, not a recorded run. The only published measurement is p50 0.39 ms and p99 1.91 ms (sigmashake-bench daemon-socket run, 2026-06-10, ssg 0.29.156, n=500). Sub-2 ms evaluation is a design target. The local audit log records decisions on a best-effort basis: entries can be dropped if the daemon queue is full, and old rows are deleted at the row caps. It is local, not append-only, and not immutable. The init adapter ids are claude-code and copilot, not claude or vscode.
What MCP is and why it matters for security
The Model Context Protocol (MCP) is the standard that lets AI coding agents -- Claude Code, Cursor, VS Code Copilot, and others -- connect to external servers and use their tools. An MCP server might expose tools to query a database, call an API, search a file system, or execute shell commands.
When your agent connects to an MCP server, every tool that server exposes becomes a live action the agent can take. The agent sees the tool's description, decides when to call it, and calls it -- with arguments it constructed from your prompt and its current context.
That is a much larger attack surface than it looks.
The risk of an untrusted MCP server
An MCP server you install is code running on your machine (or on a remote host you trust). Most MCP servers are legitimate and useful. But the model connecting to them does not verify the server's identity -- it reads the server's self-reported tool descriptions and acts on them.
A malicious or compromised MCP server can:
- Describe a tool as doing one thing while doing another (prompt injection via tool description)
- Expose tools with broad filesystem or shell access that the agent will use freely
- Exfiltrate context by having the agent call a logging or webhook tool with sensitive data
- Trigger actions in other connected systems when the agent uses the tool
None of this requires the model to behave unexpectedly. The model is doing exactly what it was asked to do. The risk is that "what it was asked to do" was defined by a server you do not fully control.
How SSG governs MCP tool calls
SSG governs AI agent tool calls at the point where the agent makes them -- before they execute. For MCP tool calls, this means SSG evaluates the call at the agent's hook layer, the same way it evaluates native shell commands, file reads, and file writes.
The MCP tool call arrives as a structured JSON payload: the tool name (mcp__<server>__<tool>) and the arguments (input.<key>). SSG evaluates this payload against your rules and returns a decision
before the call reaches the MCP server.
Agent decides to call MCP tool
|
v
SSG hook (PreToolUse / MCP middleware)
|
Rule evaluation (local)
|
ALLOW / DENY / ASK / FORCE
|
v
MCP server receives the call (or not)
FORCE refuses the original risky action and returns a safe substitute or required safer path for the agent to use next, so MCP governance can redirect an unsafe tool call instead of only blocking or asking.
This works regardless of which MCP server you are talking to, and regardless of whether the server is local or remote.
A real example: an MCP server with shell access
The filesystem and shell MCP servers are commonly installed for development. The
filesystem server exposes tools like read_file, write_file, and list_directory. The
shell server exposes run_command.
Without governance, the agent has unrestricted access to these tools. With SSG, you write rules that scope what the agent can do through each server.
An MCP tool call reaches the rule engine with the tool name mcp__<server>__<tool>
(for example mcp__filesystem__read_file) and the call arguments under input.<key>.
The rule DSL has no server field and no arguments field. You match the server and the
tool with the tool field, and you match an argument with input.<key>.
Add to .sigmashake/rules/mcp.rules:
rule mcp-no-shell-destructive {
enabled true
priority 100
severity error
DENY any
IF tool EQUALS "mcp__shell__run_command"
AND input.command REGEX "rm\\s+-[a-zA-Z]*r[a-zA-Z]*f"
MESSAGE "Recursive force-delete via the shell MCP server is blocked."
}
rule mcp-no-secret-file-read {
enabled true
priority 100
severity error
DENY any
IF tool EQUALS "mcp__filesystem__read_file"
AND (input.path GLOB "**/.env*" OR input.path GLOB "**/*.pem" OR input.path GLOB "**/id_rsa")
MESSAGE "Secret file read via the filesystem MCP server is blocked."
}
rule mcp-ask-unknown-server {
enabled true
priority 80
severity warning
ASK any
IF tool STARTS_WITH "mcp__"
AND tool NOT STARTS_WITH "mcp__filesystem__"
AND tool NOT STARTS_WITH "mcp__shell__"
AND tool NOT STARTS_WITH "mcp__github__"
AND tool NOT STARTS_WITH "mcp__postgres__"
MESSAGE "Tool call from an unrecognized MCP server requires human approval."
PROMPT "Allow this tool call from an unrecognized MCP server?"
}
The third rule is the important one: any tool call from a server not on your explicit allowlist goes to a human for approval. This catches newly added servers, servers the agent installs on its own, and any server with an unexpected name.
Scoping by server and tool
The tool field carries the server and tool name (mcp__<server>__<tool>), and input.<key> fields carry the call arguments, so you can write fine-grained policies:
rule mcp-github-read-only {
enabled true
priority 90
severity error
DENY any
IF tool EQUALS "mcp__github__create_repository"
OR tool EQUALS "mcp__github__delete_repository"
OR tool EQUALS "mcp__github__push_files"
MESSAGE "Write operations on the GitHub MCP server are not allowed. Use the terminal for git operations."
}
rule mcp-db-no-write {
enabled true
priority 90
severity error
DENY any
IF tool EQUALS "mcp__postgres__query"
AND input.sql REGEX "\\b([iI][nN][sS][eE][rR][tT]|[uU][pP][dD][aA][tT][eE]|[dD][eE][lL][eE][tT][eE]|[tT][rR][uU][nN][cC][aA][tT][eE])\\b"
MESSAGE "Write queries via the Postgres MCP server are blocked. The agent has read-only access."
}
These rules let the agent use the GitHub MCP server to read issues, pull requests, and repository metadata -- but block any write operations. The Postgres rule lets the agent run SELECT queries but blocks a statement that contains an obvious write keyword. A regex is a coarse filter: it does not parse SQL, so use a read-only database role as well.
Why you should maintain an explicit server allowlist
The mcp-ask-unknown-server rule above is a useful baseline, but a stricter approach is
to deny unknown servers by default and require explicit opt-in for each one:
rule mcp-deny-unknown-server {
enabled true
priority 75
severity error
DENY any
IF tool STARTS_WITH "mcp__"
AND tool NOT STARTS_WITH "mcp__filesystem__"
AND tool NOT STARTS_WITH "mcp__shell__"
AND tool NOT STARTS_WITH "mcp__github__"
AND tool NOT STARTS_WITH "mcp__postgres__"
AND tool NOT STARTS_WITH "mcp__brave-search__"
MESSAGE "MCP server is not on the allowlist. Add it to mcp.rules before using it."
}
This means a newly added MCP server -- one that appears in your .mcp.json or
mcpServers config but has not been reviewed -- cannot be used until you explicitly add
it to the rule. The agent will see the block message and you will know to review the
server.
Correction note (2026-10-13). An earlier version of the rules above used a
serverfield, anarguments.<key>field and anIN/NOT INoperator. The rule DSL has none of these, so the rules did not load. The rules now use thetoolfield (mcp__<server>__<tool>),input.<key>and the supported operators. Group the OR arms of one rule in parentheses so they stay inside the AND.
What the audit log shows
SSG records MCP tool call decisions in the audit log on a best-effort basis, with the tool, input, rule ID, and timestamp:
# illustrative sample output
[ALLOW] mcp:filesystem/read_file path=src/utils.ts
[ALLOW] mcp:github/get_issue number=42
[BLOCK] mcp:filesystem/read_file path=.env
rule: mcp-no-secret-file-read
[ASK] mcp:unknown-server/exec cmd=ls -la
rule: mcp-ask-unknown-server -- pending approval
This gives you a best-effort record of what the agent did through each MCP server, which helps you audit for unexpected tool usage, spot servers the agent connected to that you did not configure, and verify that sensitive operations were either blocked or explicitly approved.
Deploying MCP governance today
# macOS / Linux — free installer
curl -fsSL https://install.sigmashake.com | sh
# Windows (PowerShell)
# iwr -useb https://install.sigmashake.com/install.ps1 | iex
# Initialize (wires the MCP middleware automatically for supported clients)
ssg init --client=claude-code
ssg init --client=cursor
ssg init --client=copilot
# Pull the MCP governance ruleset from the Hub
ssg hub pull rules-mcp
# Start the daemon
ssg daemon &
# Open the dashboard (ASK decisions from unrecognized servers queue here)
ssg serve
After initialization, every MCP tool call from the configured agent goes through SSG
before it reaches the server. The first time the agent tries to read .env through the
filesystem server, you will see:
# illustrative sample output
[BLOCK] mcp:filesystem/read_file -- rule: mcp-no-secret-file-read
That block is the whole point. The server never received the call.
Links: Download · Docs · Playground · Hub