Network Security · Zero Trust
MCP server zero trust network security
Most community MCP servers run as a plain Node.js process with full outbound network access — no egress filtering, no allowlist, no DNS restrictions. This makes supply chain compromise and SSRF exfiltration trivially easy: a compromised dependency or a prompt-injected tool call can reach any external endpoint. Zero trust network controls define the expected egress surface and block everything else.
Why default-open egress is dangerous for MCP servers
Three threat vectors specifically exploit unrestricted egress in MCP servers:
1. Supply chain exfiltration. A compromised npm dependency can make HTTP requests to an attacker-controlled endpoint on every process tick, exfiltrating credentials from process.env. Without egress filtering, these requests succeed silently.
2. SSRF via tool arguments. An MCP server with a fetch_url tool that accepts arbitrary URLs can be used by a prompt-injected agent to probe internal network services (cloud metadata endpoints, internal APIs, other MCP servers in the same network). SkillAudit's public scan found SSRF potential in 36.7% of community MCP servers.
3. Data exfiltration via LLM-directed tool use. A prompt injection attack that hijacks an agent's goal can use any available network-capable tool (email, webhook, fetch) to exfiltrate data the agent has retrieved. Egress allowlisting limits which destinations data can be sent to, even if the agent is compromised.
Layer 1: Application-level egress monitoring
The first layer is intercepting outbound HTTP/HTTPS requests at the Node.js level. This requires no infrastructure changes and can be added to any server in minutes:
// egress-monitor.js — add to server startup
const ALLOWED_EGRESS = new Set([
'api.anthropic.com',
'api.your-oauth-provider.com',
'your-database.internal',
'registry.npmjs.org' // only if server uses npm at runtime
]);
const origHttpsRequest = https.request.bind(https);
https.request = function(options, ...args) {
const host = typeof options === 'string'
? new URL(options).hostname
: (options.hostname || options.host || '').split(':')[0];
if (host && !ALLOWED_EGRESS.has(host)) {
const stack = new Error().stack.split('\n').slice(2, 6).join(' | ');
log.security({
event: 'unexpected_egress_blocked',
host,
callStack: stack,
ts: Date.now()
});
const err = new Error(`Egress to ${host} not in allowlist`);
err.code = 'EGRESS_BLOCKED';
throw err; // or: return a mock response that fails gracefully
}
return origHttpsRequest(options, ...args);
};
// Apply to http as well (HTTP often bypasses HTTPS intercepts)
const origHttpRequest = http.request.bind(http);
http.request = function(options, ...args) {
// same check as above
return origHttpRequest(options, ...args);
};
Application-level egress monitoring is bypassable by native code (C++ addons, child processes spawned via exec). It catches the common case of JavaScript-level HTTP requests but is not a security boundary — pair it with OS-level controls for production deployments.
Layer 2: Container network policy (Docker / Kubernetes)
For MCP servers deployed in containers, network policy provides OS-level enforcement that cannot be bypassed by JavaScript:
# docker-compose.yml — restrict MCP server to internal network only
services:
mcp-server:
build: .
networks:
- internal
# No ports exposed to external network
# Egress proxy that enforces allowlist
squid-proxy:
image: ubuntu/squid:latest
networks:
- internal
- external
volumes:
- ./squid.conf:/etc/squid/squid.conf:ro
networks:
internal:
internal: true # ← no route to internet
external: {}
---
# squid.conf — allowlist for MCP server egress
acl allowed_sites dstdomain api.anthropic.com your-db.internal
http_access allow allowed_sites
http_access deny all
# Kubernetes NetworkPolicy — restrict MCP pod egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mcp-server-egress
spec:
podSelector:
matchLabels:
app: mcp-server
policyTypes:
- Egress
egress:
# Allow DNS
- ports:
- port: 53
protocol: UDP
# Allow specific upstream services
- to:
- namespaceSelector:
matchLabels:
name: api-services
ports:
- port: 443
# Block all other egress (default deny)
Layer 3: DNS filtering
DNS filtering blocks connections to known malicious domains and can limit resolution to expected destinations only. For MCP servers processing user-provided URLs (SSRF vectors), DNS filtering provides a server-side control that catches attacks even when the application-level URL validation fails:
# Block DNS-rebinding attacks and SSRF via DNS # Using unbound as local resolver with response policy zone # unbound.conf snippet server: # Block resolution of RFC1918 addresses (internal network SSRF) deny-any: yes # Block known exfiltration CDN domains (example) local-data: "analytics.faststr-cdn.com CNAME ." # Private address space — reject A records pointing to these private-address: 10.0.0.0/8 private-address: 172.16.0.0/12 private-address: 192.168.0.0/16 private-address: 169.254.0.0/16 # AWS metadata endpoint private-address: 127.0.0.0/8
SSRF-specific controls for fetch tools
MCP servers with fetch or HTTP request tools need SSRF controls even if they have egress filtering, because the tool is designed to make outbound requests — the question is which URLs it should allow:
const SSRF_BLOCK_RANGES = [
/^10\.\d+\.\d+\.\d+$/, // RFC1918 class A
/^172\.(1[6-9]|2\d|3[01])\./, // RFC1918 class B
/^192\.168\./, // RFC1918 class C
/^127\./, // loopback
/^169\.254\./, // link-local / AWS metadata
/^0\./, // invalid
/^::1$/, // IPv6 loopback
/^fc00:/, // IPv6 unique local
];
async function safeFetch(urlStr) {
const url = new URL(urlStr);
// Block non-HTTP schemes
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error(`Blocked scheme: ${url.protocol}`);
}
// Resolve hostname and check against RFC1918 ranges
const { address } = await dns.promises.lookup(url.hostname);
if (SSRF_BLOCK_RANGES.some(re => re.test(address))) {
throw new Error(`Blocked address: ${address} (SSRF protection)`);
}
return fetch(urlStr, {
redirect: 'manual', // prevent redirect to internal address
signal: AbortSignal.timeout(5000)
});
}
SkillAudit findings
fetch_url or equivalent tool has no SSRF protection — arbitrary internal endpoints reachable via URL parameter
Run a SkillAudit scan to check your MCP server's network security posture. SkillAudit detects SSRF vulnerability patterns in fetch tools, validates URL scheme and IP range filtering, and checks for egress monitoring instrumentation. See also: supply chain risk and supply chain audit steps.