Back to skill

Security audit

building-mcp-servers

Security checks for vulnerabilities and agentic risk

Overview

This is a legitimate MCP server-building guide, but several copy-paste examples could expose local files or unauthenticated network services and run unpinned packages.

Review and adapt the examples before installing or using this skill. Pin all package versions, avoid npx downloads at run time, bind local development servers to 127.0.0.1, add authentication before any remote MCP transport, validate filesystem resource paths against a fixed root, and do not put real API keys or bearer tokens directly in command-line arguments.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
references/python_mcp_server.md:531
Finding
Path Traversal Enables Arbitrary Local File Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `references/python_mcp_server.md`, lines 531-541 **Vulnerability Type**: Unsanitized path construction and arbitrary file read **Risk Level**: High ### Vulnerable Code ```python @mcp.resource("file://documents/{name}") async def get_document(name: str) -> str: '''Expose documents as MCP resources. Resources are useful for static or semi-static data that doesn't require complex parameters. They use URI templates for flexible access. ''' document_path = f"./docs/{name}" with open(document_path, "r") as f: return f.read() ``` ### Technical Analysis The resource handler incorporates the attacker-controlled `name` parameter directly into a filesystem path. It performs no validation for absolute paths, `..` traversal components, symbolic links, or whether the resolved path remains inside the intended `./docs` directory. MCP clients capable of requesting the resource can supply values such as `../../.env` or `../../home/user/.ssh/config`. Python normalizes the traversal components when `open()` resolves the path, allowing the handler to access files outside the intended document directory. This example also conflicts with the project's own best-practice requirement to sanitize paths and prevent directory traversal. Because the code is presented as a reusable implementation pattern, generated MCP servers may inherit the flaw. ### Attack Path 1. A developer implements the documented resource handler without additional validation. 2. An attacker gains access to the MCP resource endpoint. 3. The attacker requests a crafted URI whose `name` contains traversal components, such as `../../.env`. 4. The handler constructs `./docs/../../.env`. 5. `open()` resolves the path outside `./docs` and reads the file under the server process's privileges. 6. The file contents are returned through the MCP resource response. ### Impact Assessment An attacker can read any file accessible to the ...[truncated 519 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Resolve the requested file against a fixed document root and verify containment before opening it: ```python from pathlib import Path DOCUMENT_ROOT = Path("./docs").resolve() @mcp.resource("file://documents/{name}") async def get_document(name: str) -> str: requested = (DOCUMENT_ROOT / name).resolve() if requested == DOCUMENT_ROOT or DOCUMENT_ROOT not in requested.parents: raise ValueError("Invalid document path") if not requested.is_file(): raise FileNotFoundError("Document not found") return requested.read_text(encoding="utf-8") ``` Additional hardening should include: 1. Reject absolute paths and path components equal to `..`. 2. Use an allowlist of permitted filenames or document identifiers where possible. 3. Consider rejecting symbolic links or verify the final resolved target after link resolution. 4. Run the MCP server under an account that cannot read unrelated secrets. 5. Add tests for encoded traversal sequences, absolute paths, symbolic-link escapes, and platform-specific separators. 6. Return generic errors rather than exposing resolved filesystem paths. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
references/quick-start.md:128
Finding
Quick-Start MCP Server Is Exposed on All Interfaces Without Authentication<![CDATA[ ## Vulnerability Details **File Location**: `references/quick-start.md`, lines 128-131 **Vulnerability Type**: Insecure network binding and missing access control **Risk Level**: High ### Vulnerable Code ```python if __name__ == "__main__": # Streamable HTTP mcp.run(transport="streamable-http", host="0.0.0.0", port=8000, path="/mcp") # Or stdio: mcp.run() ``` ### Technical Analysis The quick-start binds the MCP HTTP service to `0.0.0.0`, making it reachable through every network interface. The surrounding implementation does not add authentication, authorization, TLS enforcement, origin validation, or DNS-rebinding protection. This default conflicts with `references/mcp_best_practices.md`, which recommends binding local servers to `127.0.0.1`, validating the `Origin` header, and enabling DNS-rebinding protection. Although the quick-start tools are simple, developers are expected to extend the example with API, database, filesystem, or administrative capabilities. Retaining this network configuration can expose those tools to unauthorized network clients. ### Attack Path 1. A developer starts an MCP server using the quick-start configuration. 2. The service listens on port 8000 on every network interface. 3. A device on the same network, an exposed container network, or an external host where firewall rules permit access discovers the endpoint. 4. The attacker connects to `/mcp` without presenting credentials. 5. The attacker enumerates and invokes registered MCP tools and resources. 6. Any privileges held by those tools are exercised under the server process's identity. ### Impact Assessment An unauthenticated remote client may invoke all tools exposed by the generated server. The resulting scope depends on the tools subsequently added and can include: - Reading application or user data - Calling upstream APIs with server-side credentials - Querying databases - Triggering state-changing or destructive operations - Consuming servi ...[truncated 177 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use a loopback-only binding as the safe default: ```python mcp.run( transport="streamable-http", host="127.0.0.1", port=8000, path="/mcp" ) ``` For intentionally remote deployments: 1. Require OAuth 2.1 or another appropriate authentication mechanism. 2. Validate token signature, issuer, audience, expiration, and per-tool scopes. 3. Enforce TLS and do not expose bearer tokens over plaintext HTTP. 4. Validate the `Origin` header and enable DNS-rebinding protection. 5. Apply authorization independently to each sensitive tool. 6. Restrict access through firewall rules, private networks, or an authenticated reverse proxy. 7. Add rate limiting, request-size limits, audit logging, and idle timeouts. 8. Document that `0.0.0.0` must only be used after the access-control layer is configured. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:169
Finding
Unpinned Package Installation and Immediate Execution Create Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Locations**: - `SKILL.md`, lines 169-170 and 333-336 - `references/quick-start.md`, lines 8-11, 83-86, and 154-160 - `references/evaluation.md`, lines 383-393 and 552-557 **Vulnerability Type**: Mutable third-party dependency resolution and execution **Risk Level**: Medium ### Vulnerable Code ```bash npm run build npx @modelcontextprotocol/inspector ``` ```bash npx @modelcontextprotocol/inspector node path/to/server.js ``` ```bash npm install @modelcontextprotocol/sdk zod express npm install -D @types/express ``` ```bash pip install "mcp[cli]" ``` ```bash dotnet add package ModelContextProtocol.AspNetCore # stdio / local server (lighter — no ASP.NET Core dependency) # dotnet add package ModelContextProtocol ``` ```bash pip install -r scripts/requirements.txt ``` ```bash pip install anthropic mcp ``` ### Technical Analysis The documented commands install dependencies without exact versions. Package managers therefore resolve mutable versions at execution time rather than versions reviewed when this Skill was published. The `npx @modelcontextprotocol/inspector` command is especially sensitive because `npx` can download and immediately execute the currently resolved package. Installation may also run package lifecycle scripts with the user's privileges. This guidance contradicts `SKILL.md:29`, which says dependencies should be pinned to explicit versions and verified on official registries. The main `SKILL.md` lists specific recommended SDK versions, but the executable installation examples do not consistently enforce them. ### Attack Path 1. An agent or developer follows an unpinned installation or `npx` command. 2. The package manager queries the public registry and resolves the current matching release and transitive dependency graph. 3. A compromised maintainer account, registry incident, malicious future release, or dependency-confusion condition supplies attacker-controlled code. 4. Installation lifecyc ...[truncated 840 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct package to an exact reviewed version, without range operators. 2. Commit and enforce lockfiles for npm and Python environments. 3. Use hash-verified Python requirements, such as `pip install --require-hashes`. 4. Pin NuGet package versions explicitly. 5. Install the Inspector as a reviewed local development dependency and run it without downloading new code: ```bash npm install --save-dev --save-exact @modelcontextprotocol/inspector@<reviewed-version> npx --no-install @modelcontextprotocol/inspector node path/to/server.js ``` 6. Use reproducible clean installs such as `npm ci`. 7. Review transitive dependencies and verify package ownership on official registries. 8. Use registry allowlists and dependency-scanning tools in CI. 9. Disable package lifecycle scripts where compatible with the selected dependencies. 10. Keep version tables and installation commands synchronized so the documented pinned version is the one actually installed. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/evaluation.md:440
Finding
Evaluation Examples Encourage Passing Secrets Through Process Arguments<![CDATA[ ## Vulnerability Details **File Location**: `references/evaluation.md`, lines 440-470 and 564-569 **Vulnerability Type**: Credential exposure through command-line arguments **Risk Level**: Medium ### Vulnerable Code ```bash python scripts/evaluation.py \ -t stdio \ -c python \ -a my_mcp_server.py \ -e API_KEY=abc123 \ -e DEBUG=true \ evaluation.xml ``` ```bash python scripts/evaluation.py \ -t sse \ -u https://example.com/mcp \ -H "Authorization: Bearer token123" \ -H "X-Custom-Header: value" \ evaluation.xml ``` ```bash python scripts/evaluation.py \ -t http \ -u https://example.com/mcp \ -H "Authorization: Bearer token123" \ evaluation.xml ``` ```bash python scripts/evaluation.py \ -t stdio \ -c python \ -a github_mcp_server.py \ -e GITHUB_TOKEN=ghp_xxx \ -o github_eval_report.md \ my_evaluation.xml ``` ### Technical Analysis The examples use placeholders, not live credentials. However, they instruct users to substitute API keys and bearer tokens directly into command-line arguments. Command arguments can be recorded in shell history, CI command logs, terminal transcripts, debugging output, telemetry, and process-monitoring systems. Depending on the operating system and process visibility settings, other local users may also inspect active command lines. Passing an environment assignment through the harness's `-e` option is not equivalent to loading it privately from the parent environment: the `KEY=VALUE` text remains an argument to the evaluation process before the harness forwards it to the child server. ### Attack Path 1. A user replaces `token123`, `abc123`, or `ghp_xxx` with a real credential. 2. The user executes the documented command. 3. The full command is retained in shell history, captured in CI logs, or exposed through process inspection. 4. Another local user, log reader, support operator, or compromised monitoring component obtains the credential. 5. The attacker reuses the token aga ...[truncated 651 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Read secrets from the existing process environment rather than accepting values in command arguments. 2. Support a protected environment file and ensure it is excluded from source control: ```bash export ANTHROPIC_API_KEY="$(security-tool read anthropic-key)" export GITHUB_TOKEN="$(security-tool read github-token)" python scripts/evaluation.py \ -t stdio \ -c python \ -a github_mcp_server.py \ my_evaluation.xml ``` 3. Add options that accept a header value from an environment-variable name rather than from argv. 4. Support restricted secret files or file descriptors for bearer tokens. 5. Disable shell tracing before handling secrets and redact credentials from logs and error reports. 6. Ensure generated reports never include request headers or child-process environment values. 7. Use short-lived, narrowly scoped evaluation credentials. 8. Rotate any credential accidentally entered into shell history or CI logs. 9. Replace examples with placeholder variable expansion that does not embed the secret in the command itself. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (30)

Ae1

High
Category
analysis-evasion
Content
- [⚡ TypeScript Guide](./references/node_mcp_server.md) - TypeScript patterns and examples
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- [⚡ TypeScript Guide](./references/node_mcp_server.md) - TypeScript patterns and examples
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- [⚡ TypeScript Guide](./references/node_mcp_server.md) - TypeScript patterns and examples
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

External Model or Provider Selection

High
Category
Excessive Agency
Content
optional arguments:
  -h, --help            Show help message
  -t, --transport       Transport type: stdio, sse, or http (default: stdio)
  -m, --model           Claude model to use (default: claude-3-7-sonnet-20250219)
  -o, --output          Output file for report (default: print to stdout)

stdio options:
Confidence
90% confidence
Finding
Skill selects an external model or provider that may use a different account or billing plan than the operator expects. Undisclosed model switches can cause unexpected cost or quota consumption.

Credential Access

High
Category
Privilege Escalation
Content
**OAuth 2.1**:
- Use secure OAuth 2.1 with certificates from recognized authorities
- Validate access tokens before processing requests
- Only accept tokens specifically intended for your server

**API Keys**:
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
**OAuth 2.1**:
- Use secure OAuth 2.1 with certificates from recognized authorities
- Validate access tokens before processing requests
- Only accept tokens specifically intended for your server

**API Keys**:
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Token Refresh Handling

Access tokens are short-lived (typically 1 hour). Strategies:

1. **Client-side refresh:** Clients refresh tokens before expiration and reconnect
2. **Proactive server refresh:** Background task refreshes tokens expiring soon
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The 'When to Use' section explicitly limits activation to cases where the user asks or mentions the skill in English or Portuguese. This is a natural-language locale policy constraint, but the document does not offer opt-in language flexibility or explain why only these languages are permitted.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
The skill instructs users to run `npx @modelcontextprotocol/inspector` without pinning a version, which can pull whatever package version is current at execution time. That creates a supply-chain and reproducibility risk: a compromised, malicious, or simply breaking upstream release could be executed unexpectedly during testing.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
This is the same unpinned `npx` invocation in the testing section, again causing execution of a floating package version from the registry. In a skill about building MCP servers, this is more dangerous because readers are likely to copy-paste commands directly into development environments, increasing exposure to malicious or unstable upstream releases.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
{
            // Make API request using injected HttpClient
            var response = await http.GetAsync(
                $"https://api.example.com/v1/users/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}",
                ct);

            response.EnsureSuccessStatusCode();
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
dotnet pack -c Release
dotnet nuget push bin/Release/*.nupkg --api-key <key> --source https://api.nuget.org/v3/index.json
```

```json
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Content
After understanding the API and tools, USE the MCP server tools:
- Inspect content using READ-ONLY and NON-DESTRUCTIVE operations ONLY
- Goal: identify specific content (e.g., users, channels, messages, projects, tasks) for creating realistic questions
- Should NOT call any tools that modify state
- Will NOT read the code of the MCP server implementation itself
- Parallelize this step with individual sub-agents pursuing independent explorations
- Ensure each subagent is only performing READ-ONLY, NON-DESTRUCTIVE, and IDEMPOTENT operations
Confidence
80% confidence
Finding
Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The resource example constructs a local filesystem path directly from a user-controlled URI parameter (`name`) and reads it with `open()` without any sanitization, allowlist, or path canonicalization. In an MCP server-building guide, this pattern is especially risky because readers may copy it into production code, enabling path traversal such as `../` to access arbitrary local files exposed by the server process.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The Python example binds the MCP server to 0.0.0.0, making it reachable from all network interfaces, but provides no warning that this may expose tools, prompts, and resources to external clients. In the context of MCP servers that expose LLM-callable capabilities, this can unintentionally publish sensitive operations or data to a broader network than intended.

External Transmission

Medium
Category
Data Exfiltration
Content
[Description("City name")] string city,
        CancellationToken ct)
    {
        var data = await http.GetStringAsync($"https://api.weather.example/{city}", ct);
        return data;
    }
}
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This markdown file includes examples and recommendations for making HTTP requests to external APIs and running a streamable HTTP server, but it does not warn readers that user queries, identifiers, or other data may be transmitted to remote services. For documentation files, SQP-2 applies when the skill description omits warnings about behaviors that could affect privacy or system integrity.

Static analysis

No suspicious patterns detected.