This document outlines the security measures implemented in boilerplate-mcp-server and provides guidance for secure deployment.
Implementation: Origin header validation middleware (src/index.ts)
// Validates Origin header on all HTTP requests
app.use((req, res, next) => {
const origin = req.headers.origin;
// Allow requests without Origin (direct API calls)
if (!origin) {
return next();
}
// Validate Origin matches localhost patterns
const allowedOrigins = [
'http://localhost',
'http://127.0.0.1',
'https://localhost',
'https://127.0.0.1',
];
const isAllowed = allowedOrigins.some(
(allowed) => origin === allowed || origin.startsWith(`${allowed}:`)
);
if (!isAllowed) {
res.status(403).json({ error: 'Forbidden' });
return;
}
next();
});What it prevents:
- DNS rebinding attacks where malicious websites attempt to make requests to your localhost MCP server
- Cross-origin requests from untrusted domains
- Remote exploitation via browser-based attacks
Configuration:
To allow additional origins (e.g., development environments), modify the allowedOrigins array in src/index.ts.
Implementation: Explicit hostname binding (src/index.ts)
const HOST = '127.0.0.1'; // Explicit localhost binding
app.listen(PORT, HOST, () => {
serverLogger.info(`HTTP transport listening on http://${HOST}:${PORT}`);
});What it prevents:
- Network exposure - server is NOT accessible from other machines
- Accidental exposure on public networks (coffee shops, etc.)
- Remote attacks even if firewall is misconfigured
Note: The server will ONLY accept connections from the local machine. This is the recommended configuration for MCP servers.
Implementation: Typed error responses with isError field (src/utils/error.util.ts)
export function formatErrorForMcpTool(error: unknown): {
content: Array<{ type: 'text'; text: string }>;
isError: true; // Explicit error flag
metadata?: {
errorType: ErrorType;
statusCode?: number;
errorDetails?: unknown;
};
}What it provides:
- MCP clients can reliably detect error states
- Prevents sensitive error details from leaking
- Structured error context for debugging
The boilerplate does not implement authentication by default because:
- Server binds to
127.0.0.1(localhost-only) - DNS rebinding protection prevents browser-based attacks
- MCP clients on the same machine are trusted
This is secure for local development and personal use.
Implement authentication if:
- ❌ You expose the server to a network (beyond localhost)
- ❌ Multiple users share the same machine
- ❌ You deploy to a remote server
- ❌ You handle sensitive data or privileged operations
Use case: Single-user remote deployment
// Add to src/index.ts before other middleware
app.use((req, res, next) => {
const authHeader = req.headers.authorization;
const expectedToken = process.env.MCP_AUTH_TOKEN;
if (!expectedToken) {
// Authentication disabled
return next();
}
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.substring(7);
if (token !== expectedToken) {
return res.status(401).json({ error: 'Invalid token' });
}
next();
});Configuration:
# .env.local
MCP_AUTH_TOKEN=your-secret-token-hereClient configuration:
{
"mcpServers": {
"boilerplate": {
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer your-secret-token-here"
}
}
}
}Use case: Multiple users with different permissions
// API key validation middleware
app.use(async (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key required' });
}
// Validate against database or key store
const user = await validateApiKey(apiKey);
if (!user) {
return res.status(401).json({ error: 'Invalid API key' });
}
// Attach user to request for authorization checks
req.user = user;
next();
});Use case: Integration with existing identity providers
import passport from 'passport';
import { OAuth2Strategy } from 'passport-oauth2';
// Configure OAuth strategy
passport.use(new OAuth2Strategy({
authorizationURL: process.env.OAUTH_AUTH_URL,
tokenURL: process.env.OAUTH_TOKEN_URL,
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
callbackURL: 'http://localhost:3000/auth/callback'
}, (accessToken, refreshToken, profile, done) => {
// Validate user
return done(null, profile);
}));
// Protect routes
app.use('/mcp', passport.authenticate('oauth2'), mcpHandler);Use case: Cryptographic authentication for production systems
import https from 'https';
import fs from 'fs';
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
ca: fs.readFileSync('ca-cert.pem'),
requestCert: true,
rejectUnauthorized: true
};
https.createServer(options, app).listen(PORT, HOST);- Server binds to localhost (127.0.0.1)
- DNS rebinding protection enabled
- CORS configured for localhost
- Error handling doesn't leak sensitive info
- Authentication: NOT REQUIRED (localhost-only)
- Implement authentication (Bearer/API Key/OAuth)
- Use HTTPS/TLS encryption
- Configure firewall rules
- Implement rate limiting
- Add audit logging
- Set up monitoring and alerting
- Review and restrict tool permissions
- Validate and sanitize all inputs
- Use mTLS or OAuth 2.0
- Deploy behind reverse proxy (Nginx/Caddy)
- Implement comprehensive audit logging
- Set up intrusion detection
- Configure automated security scanning
- Establish incident response procedures
- Regular security audits and penetration testing
- Secrets management (AWS Secrets Manager, Vault, etc.)
Never commit secrets to version control:
# .env.local (gitignored)
MCP_AUTH_TOKEN=secret-token
API_KEY=api-key-value
DATABASE_URL=mongodb://...Always validate tool inputs with Zod:
const ToolSchema = z.object({
param: z.string().min(1).max(100),
number: z.number().int().positive(),
});Prevent abuse with rate limiting:
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/mcp', limiter);Log authentication failures and suspicious activity:
logger.warn('Failed authentication attempt', {
ip: req.ip,
userAgent: req.headers['user-agent'],
timestamp: new Date(),
});Keep dependencies up-to-date:
npm run update:check
npm run update:deps- DNS Rebinding Attacks - Origin header validation
- Network Exposure - Localhost-only binding
- Error Information Disclosure - Structured error handling
- CORS Attacks - Explicit CORS configuration
- DoS/DDoS - Add rate limiting
- Brute Force - Add authentication + rate limiting
- Man-in-the-Middle - Use HTTPS/TLS for network deployment
- Privilege Escalation - Implement authorization checks
- Injection Attacks - Validate inputs with Zod schemas
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email security concerns privately to the maintainer
- Include detailed reproduction steps
- Allow time for patching before public disclosure
Last Updated: February 4, 2026
Security Review: Recommended quarterly