nd and environment variables. Automation pipelines must default to static-only.
4. Data Minimization: Scanners should never transmit raw configuration data to third parties. Analysis should be local or use anonymized metadata.
Implementation Example
The following TypeScript code demonstrates a secure scanning pipeline. This implementation uses distinct interfaces and logic from source examples, focusing on the separation of concerns between static and dynamic analysis.
import { execSync } from 'child_process';
import * as fs from 'fs';
// Interfaces for MCP Configuration
interface McpServerDefinition {
command: string[];
env?: Record<string, string>;
args?: string[];
}
interface McpConfig {
mcpServers: Record<string, McpServerDefinition>;
}
// Result types
interface StaticAuditResult {
passed: boolean;
violations: string[];
}
interface DynamicAuditResult {
passed: boolean;
behaviors: string[];
sandboxId: string;
}
// Static Analyzer: Parses config without execution
class StaticSecurityGate {
private readonly dangerousPatterns: RegExp[] = [
/rm\s+-rf/,
/curl.*\|.*sh/,
/eval\(/,
/process\.exit/,
];
audit(config: McpConfig): StaticAuditResult {
const violations: string[] = [];
for (const [serverName, serverDef] of Object.entries(config.mcpServers)) {
const commandStr = serverDef.command.join(' ');
// Check for known dangerous patterns
for (const pattern of this.dangerousPatterns) {
if (pattern.test(commandStr)) {
violations.push(`Server '${serverName}' contains dangerous pattern: ${pattern.source}`);
}
}
// Validate command array structure
if (!serverDef.command || serverDef.command.length === 0) {
violations.push(`Server '${serverName}' has empty command array.`);
}
}
return {
passed: violations.length === 0,
violations,
};
}
}
// Dynamic Sandbox: Executes in isolation
class DynamicSandbox {
private readonly networkPolicy: 'none' | 'restricted';
constructor(policy: 'none' | 'restricted' = 'none') {
this.networkPolicy = policy;
}
async executeAndInspect(
serverName: string,
serverDef: McpServerDefinition,
consent: boolean
): Promise<DynamicAuditResult> {
if (!consent) {
throw new Error('Dynamic execution requires explicit user consent.');
}
// In production, this would invoke a container or VM runner
// with network isolation and resource limits.
const sandboxId = `sandbox-${Date.now()}-${Math.random().toString(36).slice(2)}`;
console.log(`[Sandbox ${sandboxId}] Launching server: ${serverName}`);
console.log(`[Sandbox ${sandboxId}] Command: ${serverDef.command.join(' ')}`);
console.log(`[Sandbox ${sandboxId}] Network Policy: ${this.networkPolicy}`);
// Simulate inspection
const behaviors = ['Tool definitions retrieved', 'No network calls detected'];
return {
passed: true,
behaviors,
sandboxId,
};
}
}
// Orchestrator: Enforces pipeline order
class McpSecurityPipeline {
private staticGate: StaticSecurityGate;
private dynamicSandbox: DynamicSandbox;
constructor() {
this.staticGate = new StaticSecurityGate();
this.dynamicSandbox = new DynamicSandbox('none');
}
async runAudit(configPath: string, allowDynamic: boolean = false): Promise<void> {
console.log('Starting MCP Security Audit...');
// 1. Load Configuration
const rawConfig = fs.readFileSync(configPath, 'utf-8');
const config: McpConfig = JSON.parse(rawConfig);
// 2. Static Analysis (Always runs)
console.log('Running static analysis...');
const staticResult = this.staticGate.audit(config);
if (!staticResult.passed) {
console.error('Static analysis failed. Violations:', staticResult.violations);
process.exit(1);
}
console.log('Static analysis passed.');
// 3. Dynamic Analysis (Conditional)
if (allowDynamic) {
console.log('Dynamic analysis requested. Verifying consent...');
// Prompt user for consent (simplified)
const consent = this.promptConsent(config);
if (consent) {
for (const [name, def] of Object.entries(config.mcpServers)) {
console.log(`Inspecting server: ${name}`);
const dynamicResult = await this.dynamicSandbox.executeAndInspect(name, def, true);
console.log(`Dynamic inspection complete for ${name}. Behaviors:`, dynamicResult.behaviors);
}
} else {
console.log('Consent denied. Skipping dynamic analysis.');
}
} else {
console.log('Dynamic analysis disabled. Audit complete.');
}
}
private promptConsent(config: McpConfig): boolean {
// In a real CLI, this would use readline or a library like inquirer
console.log('\n--- CONSENT REQUIRED ---');
console.log('The following servers will be executed in a sandboxed environment.');
console.log('Environment variables will be redacted before execution.\n');
for (const [name, def] of Object.entries(config.mcpServers)) {
console.log(`- ${name}: ${def.command.join(' ')}`);
if (def.env) {
const keys = Object.keys(def.env).join(', ');
console.log(` Env Vars: [${keys}]`);
}
}
// Simulate user approval
const approved = true; // Replace with actual user input
console.log(approved ? 'Consent granted.' : 'Consent denied.');
return approved;
}
}
// Usage
const pipeline = new McpSecurityPipeline();
pipeline.runAudit('./mcp.json', false)
.then(() => console.log('Audit finished successfully.'))
.catch(err => console.error('Audit failed:', err));
Rationale
StaticSecurityGate: Uses regex patterns and structural validation to catch obvious threats. This runs instantly and safely. It prevents the pipeline from ever reaching execution if the config is malformed or contains known attack signatures.
DynamicSandbox: Encapsulates execution logic. In a production environment, this class would interface with container runtimes (e.g., Docker, gVisor) to enforce network isolation (networkPolicy: 'none'). This ensures that even if a server attempts to phone home, the sandbox blocks the connection.
McpSecurityPipeline: Orchestrates the flow. It enforces that static analysis always runs first. Dynamic analysis is gated by a consent mechanism and a configuration flag (allowDynamic). This prevents accidental execution in automated environments.
- Environment Redaction: The consent prompt explicitly lists environment variable keys. In practice, the pipeline should redact values before passing them to the sandbox to prevent credential leakage during execution.
Pitfall Guide
1. The "Safe Scanner" Fallacy
Explanation: Developers often assume security scanners are inherently safe sandboxes. However, dynamic scanners that execute servers to retrieve metadata run code on the host machine. If the scanner lacks proper isolation, a malicious MCP server can compromise the host.
Fix: Verify the scanner's architecture. Ensure dynamic execution occurs in a container or VM with no network access. Prefer static analysis for untrusted inputs.
2. CI/CD Automation Traps
Explanation: Using dynamic scanners in CI/CD pipelines often requires bypassing consent prompts via flags like --dangerously-run-mcp-servers. This removes safety checks and executes untrusted code in the build environment.
Fix: Restrict CI/CD pipelines to static analysis only. Use dynamic scanning only in local development or dedicated, isolated audit environments. Configure the pipeline to fail if dynamic execution is attempted.
3. Environment Variable Leakage
Explanation: MCP configurations frequently contain API keys and secrets in the env field. Dynamic scanners may transmit these values to third-party APIs or log them insecurely during execution.
Fix: Implement environment variable redaction in the scanning pipeline. Never pass raw secrets to external analysis services. Use local-only scanners for configs containing sensitive data.
4. Static Analysis Blind Spots
Explanation: Relying exclusively on static analysis can miss runtime-only behaviors, such as servers that download payloads dynamically or obfuscate malicious intent until execution.
Fix: Adopt a defense-in-depth strategy. Use static analysis for pre-deployment gating and trusted dynamic analysis in sandboxes for runtime behavioral verification.
5. Consent Fatigue
Explanation: Interactive consent prompts can lead to "click-through" behavior where developers approve execution without reviewing the command or environment variables.
Fix: Implement policy-based enforcement. Require explicit justification for dynamic execution. Log all consent decisions for audit trails. Use visual warnings for servers with network access or sensitive env vars.
6. Third-Party API Dependencies
Explanation: Scanners that send data to external APIs introduce data residency risks and compliance issues. Regulated environments may prohibit transmitting configuration data off-premise.
Fix: Choose scanners that support local analysis or self-hosted endpoints. Review the vendor's data handling policies. Ensure compliance with GDPR, HIPAA, or internal data governance standards.
7. Config Injection Attacks
Explanation: Malware or compromised dependencies can modify mcp.json files to inject malicious commands. If a scanner executes these commands, the attack succeeds.
Fix: Implement file integrity monitoring for MCP configurations. Use checksums or version control to detect unauthorized changes. Scan configs immediately after any modification.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Pre-commit Hook | Static Analysis | Fast, safe, prevents bad code from entering the repo. | Low |
| CI/CD Pipeline | Static Analysis | Eliminates execution risk in automated builds. | Low |
| Local Dev Audit | Dynamic (Sandboxed) | Provides deep behavioral insights for trusted servers. | Medium (Sandbox setup) |
| Regulated Environment | Static + Local Dynamic | Ensures compliance by keeping data on-premise. | High (Compliance overhead) |
| Registry Ingestion | Static Analysis | Safely gates new packages before they are distributed. | Low |
Configuration Template
Use this template to configure a secure scanning pipeline. This JSON structure defines policies for static and dynamic analysis.
{
"mcpSecurityPipeline": {
"staticAnalysis": {
"enabled": true,
"rules": [
"no_shell_execution",
"no_network_calls",
"no_eval_usage",
"validate_command_structure"
],
"failOnViolation": true
},
"dynamicAnalysis": {
"enabled": false,
"policy": {
"requireConsent": true,
"sandbox": {
"enabled": true,
"network": "none",
"cpuLimit": "0.5",
"memoryLimit": "256Mi"
},
"envRedaction": {
"enabled": true,
"patterns": ["API_KEY", "SECRET", "TOKEN", "PASSWORD"]
}
}
},
"dataHandling": {
"localOnly": true,
"telemetry": false
}
}
}
Quick Start Guide
- Install Static Scanner: Deploy a static analysis tool compatible with MCP configurations. Ensure it supports custom rule sets.
# Example installation
npm install -g mcp-static-scanner
- Run Initial Scan: Execute the static scanner against your configuration files to identify immediate risks.
mcp-static-scanner scan ./mcp.json --strict
- Review Findings: Analyze the output for violations. Remediate any issues before proceeding.
- Configure Sandbox (Optional): If dynamic analysis is required, set up an isolated environment. Configure network policies and resource limits.
- Execute Dynamic Audit: Run the dynamic scanner with explicit consent. Verify that the sandbox prevents network access and limits execution scope.
mcp-dynamic-scanner audit ./mcp.json --sandbox --consent