Security và Best Practices
Overview
Security là critical aspect của MCP servers vì chúng act as bridge giữa AI agents và external systems. Tutorial này covers security considerations, best practices, và production deployment guidelines.
Security Model
Threat Model
MCP servers face các threats sau:
Unauthorized Access
- AI agents accessing restricted resources
- Privilege escalation
- Data exfiltration
Malicious Input
- SQL injection
- Path traversal
- Command injection
Resource Abuse
- Denial of service
- Resource exhaustion
- Rate limiting bypass
Data Exposure
- Sensitive data leakage
- Log exposure
- Memory dumps
Security Layers
┌─────────────────────────────────────┐
│ AI Agent │
└─────────────┬───────────────────────┘
│ Authentication
┌─────────────▼───────────────────────┐
│ MCP Server │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ AuthZ │ │ Validation │ │
│ └─────────────┘ └─────────────┘ │
└─────────────┬───────────────────────┘
│ Authorization
┌─────────────▼───────────────────────┐
│ External Systems │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Database │ │ File System │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘Authentication
1. JWT-based Authentication
typescript
// JWT Authentication middleware
import jwt from 'jsonwebtoken';
interface JWTPayload {
userId: string;
permissions: string[];
expiresAt: number;
}
class JWTAuthenticator {
private secretKey: string;
private algorithm: jwt.Algorithm = 'HS256';
constructor(secretKey: string) {
this.secretKey = secretKey;
}
generateToken(payload: Omit<JWTPayload, 'expiresAt'>): string {
const tokenPayload: JWTPayload = {
...payload,
expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
};
return jwt.sign(tokenPayload, this.secretKey, {
algorithm: this.algorithm,
expiresIn: '24h'
});
}
verifyToken(token: string): JWTPayload {
try {
const decoded = jwt.verify(token, this.secretKey, {
algorithms: [this.algorithm]
}) as JWTPayload;
// Check expiration
if (Date.now() > decoded.expiresAt) {
throw new Error('Token expired');
}
return decoded;
} catch (error) {
throw new Error('Invalid authentication token');
}
}
middleware() {
return (req: any, res: any, next: any) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Authentication required' });
}
try {
const payload = this.verifyToken(token);
req.user = payload;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
}
}2. API Key Authentication
typescript
// API Key authentication
import crypto from 'crypto';
interface APIKey {
id: string;
keyHash: string;
permissions: string[];
createdAt: Date;
lastUsed?: Date;
}
class APIKeyAuthenticator {
private keys: Map<string, APIKey> = new Map();
generateAPIKey(permissions: string[]): { id: string; key: string } {
const id = crypto.randomUUID();
const key = crypto.randomBytes(32).toString('hex');
const keyHash = crypto.createHash('sha256').update(key).digest('hex');
this.keys.set(id, {
id,
keyHash,
permissions,
createdAt: new Date()
});
return { id, key };
}
verifyAPIKey(key: string): APIKey {
const keyHash = crypto.createHash('sha256').update(key).digest('hex');
for (const apiKey of this.keys.values()) {
if (apiKey.keyHash === keyHash) {
apiKey.lastUsed = new Date();
return apiKey;
}
}
throw new Error('Invalid API key');
}
revokeAPIKey(id: string) {
this.keys.delete(id);
}
}3. OAuth 2.0 Integration
typescript
// OAuth 2.0 authentication
import { AuthorizationCode } from 'simple-oauth2';
class OAuth2Authenticator {
private oauth2client: AuthorizationCode;
constructor(config: {
clientId: string;
clientSecret: string;
authHost: string;
tokenHost: string;
}) {
this.oauth2client = new AuthorizationCode({
client: {
id: config.clientId,
secret: config.clientSecret,
},
auth: {
tokenHost: config.tokenHost,
authorizeHost: config.authHost,
},
});
}
getAuthorizationURL(scopes: string[]): string {
return this.oauth2client.authorizeURL({
scope: scopes.join(' '),
state: crypto.randomBytes(16).toString('hex')
});
}
async exchangeCodeForToken(code: string) {
const result = await this.oauth2client.getToken({
code,
redirect_uri: 'http://localhost:3000/callback'
});
return result.token;
}
}Authorization
1. Role-Based Access Control (RBAC)
typescript
// RBAC implementation
interface Role {
name: string;
permissions: string[];
}
interface User {
id: string;
roles: string[];
customPermissions?: string[];
}
class RBACAuthorizer {
private roles: Map<string, Role> = new Map();
private users: Map<string, User> = new Map();
constructor() {
this.initializeDefaultRoles();
}
private initializeDefaultRoles() {
// Define default roles
this.roles.set('admin', {
name: 'admin',
permissions: ['*'] // All permissions
});
this.roles.set('developer', {
name: 'developer',
permissions: [
'file:read',
'file:write',
'database:read',
'database:write',
'api:call'
]
});
this.roles.set('analyst', {
name: 'analyst',
permissions: [
'file:read',
'database:read',
'api:call'
]
});
this.roles.set('readonly', {
name: 'readonly',
permissions: [
'file:read',
'database:read'
]
});
}
hasPermission(userId: string, resource: string, action: string): boolean {
const user = this.users.get(userId);
if (!user) {
return false;
}
// Check custom permissions first
if (user.customPermissions) {
const customPerm = `${resource}:${action}`;
const wildcardPerm = `${resource}:*`;
const globalPerm = '*';
if (user.customPermissions.includes(customPerm) ||
user.customPermissions.includes(wildcardPerm) ||
user.customPermissions.includes(globalPerm)) {
return true;
}
}
// Check role permissions
for (const roleName of user.roles) {
const role = this.roles.get(roleName);
if (!role) continue;
const rolePerm = `${resource}:${action}`;
const roleWildcard = `${resource}:*`;
const roleGlobal = '*';
if (role.permissions.includes(rolePerm) ||
role.permissions.includes(roleWildcard) ||
role.permissions.includes(roleGlobal)) {
return true;
}
}
return false;
}
assignRole(userId: string, roleName: string) {
const user = this.users.get(userId) || { id: userId, roles: [] };
if (!user.roles.includes(roleName)) {
user.roles.push(roleName);
this.users.set(userId, user);
}
}
grantCustomPermission(userId: string, permission: string) {
const user = this.users.get(userId) || { id: userId, roles: [], customPermissions: [] };
user.customPermissions = user.customPermissions || [];
if (!user.customPermissions.includes(permission)) {
user.customPermissions.push(permission);
this.users.set(userId, user);
}
}
}2. Attribute-Based Access Control (ABAC)
typescript
// ABAC implementation
interface Policy {
id: string;
name: string;
conditions: PolicyCondition[];
effect: 'allow' | 'deny';
priority: number;
}
interface PolicyCondition {
attribute: string;
operator: 'eq' | 'ne' | 'in' | 'contains' | 'startsWith' | 'endsWith';
value: any;
}
interface AccessRequest {
userId: string;
resource: string;
action: string;
context: {
time: Date;
ip: string;
userAgent?: string;
[key: string]: any;
};
}
class ABACAuthorizer {
private policies: Map<string, Policy> = new Map();
addPolicy(policy: Policy) {
this.policies.set(policy.id, policy);
}
evaluate(request: AccessRequest): boolean {
// Sort policies by priority (higher priority first)
const sortedPolicies = Array.from(this.policies.values())
.sort((a, b) => b.priority - a.priority);
for (const policy of sortedPolicies) {
if (this.evaluateConditions(policy.conditions, request)) {
return policy.effect === 'allow';
}
}
// Default deny
return false;
}
private evaluateConditions(conditions: PolicyCondition[], request: AccessRequest): boolean {
return conditions.every(condition => {
const attributeValue = this.getAttributeValue(condition.attribute, request);
switch (condition.operator) {
case 'eq':
return attributeValue === condition.value;
case 'ne':
return attributeValue !== condition.value;
case 'in':
return Array.isArray(condition.value) && condition.value.includes(attributeValue);
case 'contains':
return typeof attributeValue === 'string' && attributeValue.includes(condition.value);
case 'startsWith':
return typeof attributeValue === 'string' && attributeValue.startsWith(condition.value);
case 'endsWith':
return typeof attributeValue === 'string' && attributeValue.endsWith(condition.value);
default:
return false;
}
});
}
private getAttributeValue(attribute: string, request: AccessRequest): any {
const parts = attribute.split('.');
let value: any = request;
for (const part of parts) {
value = value?.[part];
}
return value;
}
}Input Validation
1. Path Validation
typescript
// Path traversal protection
import * as path from 'path';
class PathValidator {
private allowedPaths: string[];
constructor(allowedPaths: string[]) {
this.allowedPaths = allowedPaths.map(p => path.resolve(p));
}
validatePath(inputPath: string, basePath?: string): string {
// Resolve the input path
const resolvedPath = path.resolve(basePath || process.cwd(), inputPath);
// Check if path is within allowed paths
const isAllowed = this.allowedPaths.some(allowed =>
resolvedPath.startsWith(allowed)
);
if (!isAllowed) {
throw new Error(`Access denied: path outside allowed directories`);
}
// Check for path traversal attempts
if (inputPath.includes('..') || inputPath.includes('~')) {
throw new Error(`Invalid path: traversal characters not allowed`);
}
// Normalize path
return path.normalize(resolvedPath);
}
validateFileName(fileName: string): void {
// Check for invalid characters
const invalidChars = /[<>:"|?*\x00-\x1f]/;
if (invalidChars.test(fileName)) {
throw new Error(`Invalid file name: contains invalid characters`);
}
// Check for reserved names (Windows)
const reservedNames = [
'CON', 'PRN', 'AUX', 'NUL',
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
];
const nameWithoutExt = path.parse(fileName).name.toUpperCase();
if (reservedNames.includes(nameWithoutExt)) {
throw new Error(`Invalid file name: reserved name`);
}
// Check length
if (fileName.length > 255) {
throw new Error(`Invalid file name: too long`);
}
}
}2. SQL Injection Prevention
typescript
// SQL injection protection
interface QueryOptions {
allowedTables: string[];
allowedOperations: string[];
maxQueryLength: number;
}
class SQLValidator {
private options: QueryOptions;
constructor(options: QueryOptions) {
this.options = options;
}
validateQuery(query: string): void {
// Check query length
if (query.length > this.options.maxQueryLength) {
throw new Error(`Query too long: maximum ${this.options.maxQueryLength} characters`);
}
// Check for dangerous patterns
const dangerousPatterns = [
/DROP\s+TABLE/i,
/DELETE\s+FROM/i,
/UPDATE\s+.*\s+SET/i,
/INSERT\s+INTO/i,
/EXEC\s*\(/i,
/EXECUTE\s*\(/i,
/UNION\s+SELECT/i,
/--/,
/\/\*/,
/\*\//
];
for (const pattern of dangerousPatterns) {
if (pattern.test(query)) {
throw new Error(`Dangerous SQL pattern detected`);
}
}
// Parse and validate tables
this.validateTables(query);
}
private validateTables(query: string): void {
const tableRegex = /FROM\s+(\w+)|JOIN\s+(\w+)/gi;
let match;
while ((match = tableRegex.exec(query)) !== null) {
const tableName = match[1] || match[2];
if (!this.options.allowedTables.includes(tableName)) {
throw new Error(`Access denied: table '${tableName}' not allowed`);
}
}
}
sanitizeQuery(query: string, params: any[]): { query: string; params: any[] } {
// Convert to parameterized query
let paramIndex = 0;
const sanitizedQuery = query.replace(/'([^']*)'/g, (match) => {
return `$${++paramIndex}`;
});
return {
query: sanitizedQuery,
params: params
};
}
}3. Command Injection Prevention
typescript
// Command injection protection
class CommandValidator {
private allowedCommands: string[];
constructor(allowedCommands: string[]) {
this.allowedCommands = allowedCommands;
}
validateCommand(command: string, args: string[]): void {
// Check if command is allowed
if (!this.allowedCommands.includes(command)) {
throw new Error(`Command not allowed: ${command}`);
}
// Validate arguments
for (const arg of args) {
this.validateArgument(arg);
}
}
private validateArgument(arg: string): void {
// Check for dangerous characters
const dangerousChars = /[;&|`$(){}[\]<>*?~]/;
if (dangerousChars.test(arg)) {
throw new Error(`Invalid argument: contains dangerous characters`);
}
// Check for file paths
if (arg.includes('/') || arg.includes('\\')) {
throw new Error(`Invalid argument: file paths not allowed`);
}
// Check length
if (arg.length > 1000) {
throw new Error(`Invalid argument: too long`);
}
}
sanitizeArgs(args: string[]): string[] {
return args.map(arg =>
arg.replace(/[;&|`$(){}[\]<>*?~]/g, '')
.replace(/\s+/g, ' ')
.trim()
).filter(arg => arg.length > 0);
}
}Rate Limiting
1. Token Bucket Algorithm
typescript
// Token bucket rate limiter
class TokenBucketRateLimiter {
private buckets: Map<string, TokenBucket> = new Map();
private refillRate: number; // tokens per second
private bucketSize: number; // maximum tokens
constructor(refillRate: number, bucketSize: number) {
this.refillRate = refillRate;
this.bucketSize = bucketSize;
}
isAllowed(clientId: string, tokens: number = 1): boolean {
const bucket = this.getOrCreateBucket(clientId);
this.refill(bucket);
if (bucket.tokens >= tokens) {
bucket.tokens -= tokens;
return true;
}
return false;
}
private getOrCreateBucket(clientId: string): TokenBucket {
let bucket = this.buckets.get(clientId);
if (!bucket) {
bucket = {
tokens: this.bucketSize,
lastRefill: Date.now()
};
this.buckets.set(clientId, bucket);
}
return bucket;
}
private refill(bucket: TokenBucket): void {
const now = Date.now();
const timePassed = (now - bucket.lastRefill) / 1000;
const tokensToAdd = Math.floor(timePassed * this.refillRate);
if (tokensToAdd > 0) {
bucket.tokens = Math.min(bucket.tokens + tokensToAdd, this.bucketSize);
bucket.lastRefill = now;
}
}
cleanup(): void {
const now = Date.now();
const expireTime = 5 * 60 * 1000; // 5 minutes
for (const [clientId, bucket] of this.buckets.entries()) {
if (now - bucket.lastRefill > expireTime) {
this.buckets.delete(clientId);
}
}
}
}
interface TokenBucket {
tokens: number;
lastRefill: number;
}2. Sliding Window Rate Limiter
typescript
// Sliding window rate limiter
class SlidingWindowRateLimiter {
private windows: Map<string, number[]> = new Map();
private windowSize: number; // in milliseconds
private maxRequests: number;
constructor(windowSize: number, maxRequests: number) {
this.windowSize = windowSize;
this.maxRequests = maxRequests;
}
isAllowed(clientId: string): boolean {
const now = Date.now();
const windowStart = now - this.windowSize;
let requests = this.windows.get(clientId) || [];
// Remove old requests
requests = requests.filter(timestamp => timestamp > windowStart);
// Check if under limit
if (requests.length < this.maxRequests) {
requests.push(now);
this.windows.set(clientId, requests);
return true;
}
return false;
}
getRemainingRequests(clientId: string): number {
const now = Date.now();
const windowStart = now - this.windowSize;
const requests = this.windows.get(clientId) || [];
const validRequests = requests.filter(timestamp => timestamp > windowStart);
return Math.max(0, this.maxRequests - validRequests.length);
}
cleanup(): void {
const now = Date.now();
const expireTime = this.windowSize * 2;
for (const [clientId, requests] of this.windows.entries()) {
const windowStart = now - expireTime;
const validRequests = requests.filter(timestamp => timestamp > windowStart);
if (validRequests.length === 0) {
this.windows.delete(clientId);
} else {
this.windows.set(clientId, validRequests);
}
}
}
}Audit Logging
1. Comprehensive Audit Logger
typescript
// Audit logging system
interface AuditEvent {
id: string;
timestamp: Date;
userId: string;
action: string;
resource: string;
result: 'success' | 'failure';
details: any;
ip: string;
userAgent?: string;
sessionId?: string;
}
class AuditLogger {
private logFile: string;
private encryptionKey: string;
constructor(logFile: string, encryptionKey: string) {
this.logFile = logFile;
this.encryptionKey = encryptionKey;
}
async log(event: Omit<AuditEvent, 'id' | 'timestamp'>): Promise<void> {
const auditEvent: AuditEvent = {
id: crypto.randomUUID(),
timestamp: new Date(),
...event
};
// Encrypt sensitive data
const encryptedEvent = await this.encryptEvent(auditEvent);
// Write to log file
await this.writeToLog(encryptedEvent);
// Also send to external monitoring
await this.sendToMonitoring(auditEvent);
}
private async encryptEvent(event: AuditEvent): Promise<string> {
const sensitiveFields = ['details', 'userAgent'];
const sanitized = { ...event };
for (const field of sensitiveFields) {
if (sanitized[field]) {
sanitized[field] = await this.encrypt(JSON.stringify(sanitized[field]));
}
}
return JSON.stringify(sanitized);
}
private async encrypt(data: string): Promise<string> {
const algorithm = 'aes-256-gcm';
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(algorithm, this.encryptionKey);
cipher.setAAD(Buffer.from('audit-log'));
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
}
private async writeToLog(event: string): Promise<void> {
const logEntry = `${event}\n`;
await fs.appendFile(this.logFile, logEntry);
}
private async sendToMonitoring(event: AuditEvent): Promise<void> {
// Send to monitoring service
// This could be Splunk, ELK, etc.
}
async searchEvents(query: {
userId?: string;
action?: string;
resource?: string;
startDate?: Date;
endDate?: Date;
}): Promise<AuditEvent[]> {
// Search through audit logs
// This is a simplified implementation
const events: AuditEvent[] = [];
// In production, use proper indexing and search
return events;
}
}Production Deployment
1. Docker Security
dockerfile
# Secure Dockerfile
FROM node:20-alpine AS builder
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy source code
COPY --chown=nextjs:nodejs . .
# Build application
RUN npm run build
# Production stage
FROM node:20-alpine AS runner
# Install security updates
RUN apk update && apk upgrade && apk add --no-cache dumb-init
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Set working directory
WORKDIR /app
# Copy built application
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
# Set permissions
RUN chmod -R 755 /app/dist
# Switch to non-root user
USER nextjs
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node dist/health-check.js
# Use dumb-init as PID 1
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]2. Kubernetes Security
yaml
# Kubernetes deployment with security
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
replicas: 3
selector:
matchLabels:
app: mcp-server
template:
metadata:
labels:
app: mcp-server
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
containers:
- name: mcp-server
image: mcp-server:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
env:
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: mcp-secrets
key: jwt-secret
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: mcp-secrets
key: database-url
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 53. Network Security
typescript
// Network security middleware
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
import cors from 'cors';
class SecurityMiddleware {
static apply(app: Express) {
// Security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
// CORS
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false,
});
app.use(limiter);
// Request size limit
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// IP filtering
app.use((req, res, next) => {
const allowedIPs = process.env.ALLOWED_IPS?.split(',') || [];
if (allowedIPs.length > 0 && !allowedIPs.includes(req.ip)) {
return res.status(403).json({ error: 'IP not allowed' });
}
next();
});
}
}Monitoring và Alerting
1. Metrics Collection
typescript
// Metrics collection
import { register, Counter, Histogram, Gauge } from 'prom-client';
class MetricsCollector {
private requestCounter: Counter;
private requestDuration: Histogram;
private activeConnections: Gauge;
private errorCounter: Counter;
constructor() {
this.requestCounter = new Counter({
name: 'mcp_requests_total',
help: 'Total number of MCP requests',
labelNames: ['method', 'status']
});
this.requestDuration = new Histogram({
name: 'mcp_request_duration_seconds',
help: 'Duration of MCP requests',
labelNames: ['method'],
buckets: [0.1, 0.5, 1, 2, 5, 10]
});
this.activeConnections = new Gauge({
name: 'mcp_active_connections',
help: 'Number of active MCP connections'
});
this.errorCounter = new Counter({
name: 'mcp_errors_total',
help: 'Total number of MCP errors',
labelNames: ['type', 'method']
});
register.registerMetric(this.requestCounter);
register.registerMetric(this.requestDuration);
register.registerMetric(this.activeConnections);
register.registerMetric(this.errorCounter);
}
recordRequest(method: string, status: number, duration: number) {
this.requestCounter.labels(method, status.toString()).inc();
this.requestDuration.labels(method).observe(duration / 1000);
}
recordError(type: string, method: string) {
this.errorCounter.labels(type, method).inc();
}
incrementConnections() {
this.activeConnections.inc();
}
decrementConnections() {
this.activeConnections.dec();
}
getMetrics() {
return register.metrics();
}
}2. Health Checks
typescript
// Health check system
interface HealthCheck {
name: string;
check(): Promise<boolean>;
timeout?: number;
}
class HealthChecker {
private checks: Map<string, HealthCheck> = new Map();
addCheck(check: HealthCheck) {
this.checks.set(check.name, check);
}
async runChecks(): Promise<{
status: 'healthy' | 'unhealthy';
checks: { [name: string]: boolean };
timestamp: Date;
}> {
const results: { [name: string]: boolean } = {};
let allHealthy = true;
for (const [name, check] of this.checks) {
try {
const timeout = check.timeout || 5000;
const result = await Promise.race([
check.check(),
new Promise<boolean>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
results[name] = result;
if (!result) allHealthy = false;
} catch (error) {
results[name] = false;
allHealthy = false;
}
}
return {
status: allHealthy ? 'healthy' : 'unhealthy',
checks: results,
timestamp: new Date()
};
}
}
// Example health checks
const healthChecker = new HealthChecker();
healthChecker.addCheck({
name: 'database',
async check() {
// Check database connectivity
return true;
}
});
healthChecker.addCheck({
name: 'memory',
async check() {
const usage = process.memoryUsage();
return usage.heapUsed < 500 * 1024 * 1024; // 500MB limit
}
});Summary
Security Best Practices:
- ✅ Authentication (JWT, API Keys, OAuth)
- ✅ Authorization (RBAC, ABAC)
- ✅ Input Validation (Path, SQL, Command)
- ✅ Rate Limiting (Token Bucket, Sliding Window)
- ✅ Audit Logging (Encrypted, Searchable)
- ✅ Production Security (Docker, K8s, Network)
- ✅ Monitoring (Metrics, Health Checks)
Key Principles:
- Defense in Depth: Multiple security layers
- Least Privilege: Minimal required permissions
- Zero Trust: Verify everything
- Fail Secure: Default deny
- Audit Everything: Log all actions
Production Considerations:
- Container security
- Network segmentation
- Secrets management
- Monitoring and alerting
- Regular security updates
- Penetration testing
Tiếp theo: 8. MCP Ecosystem → Khám phá community servers