Securing MCP Servers
Audit your MCP server for security vulnerabilities in 10 minutes.
Why MCP Servers Need Security Auditing
Model Context Protocol (MCP) servers give AI assistants access to tools and data. A vulnerable MCP server can allow:
- Tool injection — Attacker-controlled tool descriptions that manipulate the AI
- Excessive permissions — Tools with unrestricted filesystem or network access
- Missing input validation — Tools that accept arbitrary input without checks
- Credential exposure — API keys or secrets hardcoded in tool implementations
1. Scan
# Scan your MCP server project
npx -y @inkog-io/cli scan ./my-mcp-server
# Or scan a specific directory
inkog scan ./src/toolsExample output:
tools/file-reader.ts:12:1: CRITICAL [code_injection]
Tool executes user input without validation
|
11 | const content = fs.readFileSync(args.path)
12 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
CWE-22 | OWASP LLM07
tools/search.ts:8:1: HIGH [missing_input_validation]
Tool parameter has no validation constraints
|
7 | inputSchema: {
8 | type: "object",
| ^^^^^^^^^^^^^^^^
9 | properties: { query: { type: "string" } }
|
OWASP LLM07
---------------------------------------------
2 findings (1 critical, 1 high)2. Fix
Fix 1: Validate tool inputs
Vulnerable
No path validation — directory traversal possible
server.tool("read_file", async ({ path }) => {
// Attacker can read any file: "../../etc/passwd"
const content = fs.readFileSync(path, "utf-8");
return { content: [{ type: "text", text: content }] };
});Secure
Path restricted to allowed directory
import path from "path";
const ALLOWED_DIR = "/app/data";
server.tool("read_file", async ({ filePath }) => {
const resolved = path.resolve(ALLOWED_DIR, filePath);
// Prevent directory traversal
if (!resolved.startsWith(ALLOWED_DIR)) {
throw new Error("Access denied: path outside allowed directory");
}
const content = fs.readFileSync(resolved, "utf-8");
return { content: [{ type: "text", text: content }] };
});Fix 2: Add input schemas with constraints
Vulnerable
Unconstrained string input
server.tool("search", {
description: "Search documents",
inputSchema: {
type: "object",
properties: {
query: { type: "string" }
}
}
}, async ({ query }) => {
return db.search(query);
});Secure
Input validated with max length and pattern
server.tool("search", {
description: "Search documents",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
maxLength: 200,
pattern: "^[a-zA-Z0-9 .,!?-]+$"
}
},
required: ["query"]
}
}, async ({ query }) => {
return db.search(query);
});Fix 3: Remove hardcoded credentials
Vulnerable
API key in source code
const client = new APIClient({
apiKey: "sk-proj-abc123...",
baseUrl: "https://api.example.com"
});Secure
Credentials from environment variables
const client = new APIClient({
apiKey: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL
});
if (!process.env.API_KEY) {
throw new Error("API_KEY environment variable required");
}3. Verify
inkog scan ./my-mcp-serverExpected:
---------------------------------------------
0 findings
Security Gate: PASSED4. Add to CI
# .github/workflows/security.yml
name: MCP Security
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: inkog-io/inkog-action@v1
with:
path: .
fail-on: critical,highCommon Fixes
| Finding | Fix |
|---|---|
code_injection | Validate and restrict tool inputs |
missing_input_validation | Add maxLength, pattern, enum constraints |
hardcoded_credentials | Use environment variables |
path_traversal | Resolve and validate paths against allowed directory |
excessive_permissions | Scope tools to minimum required access |
Next
Last updated on