Chapter 9: Security and Permission Control
Haiyue
45min
Chapter 9: Security and Permission Control
Learning Objectives
- Understand MCP security model and threat protection
- Implement authentication and authorization mechanisms
- Master resource access permission control
- Learn input validation and data sanitization
- Implement security auditing and monitoring
1. MCP Security Threat Model
1.1 Potential Security Threats
enum SecurityThreat {
UNAUTHORIZED_ACCESS = "unauthorized_access", // Unauthorized access
PRIVILEGE_ESCALATION = "privilege_escalation", // Privilege escalation
DATA_INJECTION = "data_injection", // Data injection
RESOURCE_EXHAUSTION = "resource_exhaustion", // Resource exhaustion
INFORMATION_DISCLOSURE = "info_disclosure", // Information disclosure
REPLAY_ATTACK = "replay_attack", // Replay attack
MALICIOUS_PAYLOAD = "malicious_payload", // Malicious payload
SYSTEM_COMPROMISE = "system_compromise" // System compromise
}
interface ThreatAssessment {
threat: SecurityThreat;
severity: "low" | "medium" | "high" | "critical";
likelihood: "rare" | "unlikely" | "possible" | "likely";
impact: string;
mitigations: string[];
}
class SecurityThreatAnalyzer {
private threats: ThreatAssessment[] = [
{
threat: SecurityThreat.UNAUTHORIZED_ACCESS,
severity: "high",
likelihood: "likely",
impact: "Unauthorized users could access sensitive resources and tools",
mitigations: [
"Implement strong authentication",
"Use role-based access control",
"Enable audit logging"
]
},
{
threat: SecurityThreat.DATA_INJECTION,
severity: "critical",
likelihood: "possible",
impact: "Malicious data could be injected into system operations",
mitigations: [
"Input validation and sanitization",
"Parameterized queries",
"Output encoding"
]
}
// More threat assessments...
];
getThreatAssessments(): ThreatAssessment[] {
return this.threats;
}
getHighRiskThreats(): ThreatAssessment[] {
return this.threats.filter(t =>
t.severity === "high" || t.severity === "critical"
);
}
}
1.2 Security Architecture Design
interface SecurityArchitecture {
layers: {
authentication: AuthenticationLayer;
authorization: AuthorizationLayer;
validation: ValidationLayer;
encryption: EncryptionLayer;
auditing: AuditingLayer;
};
policies: SecurityPolicy[];
controls: SecurityControl[];
}
interface SecurityPolicy {
name: string;
description: string;
rules: PolicyRule[];
enforcement: "strict" | "lenient" | "advisory";
}
interface SecurityControl {
id: string;
type: "preventive" | "detective" | "corrective";
description: string;
implementation: () => Promise<boolean>;
}
2. Authentication System
2.1 Multiple Authentication Methods Support
enum AuthenticationMethod {
API_KEY = "api_key",
JWT = "jwt",
OAUTH2 = "oauth2",
CERTIFICATE = "certificate",
BASIC_AUTH = "basic_auth"
}
interface AuthenticationCredential {
method: AuthenticationMethod;
value: string;
metadata?: Record<string, any>;
}
interface AuthenticationResult {
success: boolean;
user?: AuthenticatedUser;
error?: string;
expiresAt?: Date;
}
interface AuthenticatedUser {
id: string;
username: string;
roles: string[];
permissions: string[];
metadata: Record<string, any>;
}
abstract class AuthenticationProvider {
abstract authenticate(credential: AuthenticationCredential): Promise<AuthenticationResult>;
abstract validateToken(token: string): Promise<AuthenticationResult>;
abstract refreshToken?(token: string): Promise<AuthenticationResult>;
}
class ApiKeyAuthProvider extends AuthenticationProvider {
private apiKeys: Map<string, AuthenticatedUser> = new Map();
private hasher: PasswordHasher;
constructor(hasher: PasswordHasher) {
super();
this.hasher = hasher;
}
async authenticate(credential: AuthenticationCredential): Promise<AuthenticationResult> {
if (credential.method !== AuthenticationMethod.API_KEY) {
return { success: false, error: "Invalid authentication method" };
}
const apiKey = credential.value;
const hashedKey = await this.hasher.hash(apiKey);
const user = this.apiKeys.get(hashedKey);
if (!user) {
return { success: false, error: "Invalid API key" };
}
return {
success: true,
user,
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
};
}
async validateToken(token: string): Promise<AuthenticationResult> {
// API Keys typically don't expire, but can be checked for revocation
return this.authenticate({ method: AuthenticationMethod.API_KEY, value: token });
}
addApiKey(apiKey: string, user: AuthenticatedUser): void {
const hashedKey = this.hasher.hashSync(apiKey);
this.apiKeys.set(hashedKey, user);
}
revokeApiKey(apiKey: string): void {
const hashedKey = this.hasher.hashSync(apiKey);
this.apiKeys.delete(hashedKey);
}
}
class JWTAuthProvider extends AuthenticationProvider {
private secretKey: string;
private algorithm: string = "HS256";
constructor(secretKey: string) {
super();
this.secretKey = secretKey;
}
async authenticate(credential: AuthenticationCredential): Promise<AuthenticationResult> {
if (credential.method !== AuthenticationMethod.JWT) {
return { success: false, error: "Invalid authentication method" };
}
try {
const decoded = this.verifyJWT(credential.value);
const user: AuthenticatedUser = {
id: decoded.sub,
username: decoded.username,
roles: decoded.roles || [],
permissions: decoded.permissions || [],
metadata: decoded.metadata || {}
};
return {
success: true,
user,
expiresAt: new Date(decoded.exp * 1000)
};
} catch (error) {
return { success: false, error: "Invalid JWT token" };
}
}
async validateToken(token: string): Promise<AuthenticationResult> {
return this.authenticate({ method: AuthenticationMethod.JWT, value: token });
}
private verifyJWT(token: string): any {
// Simplified JWT verification implementation
// In production, use a proper JWT library like jsonwebtoken
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error("Invalid JWT format");
}
const header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
// Verify signature
const signature = parts[2];
const expectedSignature = this.signJWT(parts[0] + '.' + parts[1]);
if (signature !== expectedSignature) {
throw new Error("Invalid JWT signature");
}
// Check expiration time
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
throw new Error("JWT token expired");
}
return payload;
}
private signJWT(data: string): string {
// Simplified signature implementation
const crypto = require('crypto');
return crypto.createHmac('sha256', this.secretKey)
.update(data)
.digest('base64url');
}
}
2.2 Password Security Handling
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
interface PasswordPolicy {
minLength: number;
requireUppercase: boolean;
requireLowercase: boolean;
requireNumbers: boolean;
requireSpecialChars: boolean;
maxAge: number; // days
historyCount: number; // prevent reusing last N passwords
}
class PasswordHasher {
private saltRounds: number = 12;
async hash(password: string): Promise<string> {
return bcrypt.hash(password, this.saltRounds);
}
hashSync(password: string): string {
return bcrypt.hashSync(password, this.saltRounds);
}
async verify(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
verifySync(password: string, hash: string): boolean {
return bcrypt.compareSync(password, hash);
}
}
class PasswordValidator {
private policy: PasswordPolicy;
constructor(policy: PasswordPolicy) {
this.policy = policy;
}
validate(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < this.policy.minLength) {
errors.push(`Password must be at least ${this.policy.minLength} characters long`);
}
if (this.policy.requireUppercase && !/[A-Z]/.test(password)) {
errors.push("Password must contain at least one uppercase letter");
}
if (this.policy.requireLowercase && !/[a-z]/.test(password)) {
errors.push("Password must contain at least one lowercase letter");
}
if (this.policy.requireNumbers && !/\d/.test(password)) {
errors.push("Password must contain at least one number");
}
if (this.policy.requireSpecialChars && !/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
errors.push("Password must contain at least one special character");
}
return {
valid: errors.length === 0,
errors
};
}
generateSecurePassword(length: number = 16): string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
let password = "";
for (let i = 0; i < length; i++) {
const randomIndex = crypto.randomInt(0, charset.length);
password += charset[randomIndex];
}
return password;
}
}
3. Authorization and Permission Control
3.1 Role-Based Access Control (RBAC)
interface Permission {
resource: string; // Resource type, such as "tools", "resources", "prompts"
action: string; // Action type, such as "read", "write", "execute"
conditions?: string[]; // Additional conditions
}
interface Role {
name: string;
description: string;
permissions: Permission[];
inheritFrom?: string[]; // Inherit from other roles
}
interface AccessRequest {
user: AuthenticatedUser;
resource: string;
action: string;
context?: Record<string, any>;
}
class RoleBasedAccessControl {
private roles: Map<string, Role> = new Map();
private userRoles: Map<string, string[]> = new Map();
defineRole(role: Role): void {
this.roles.set(role.name, role);
}
assignRole(userId: string, roleName: string): void {
const currentRoles = this.userRoles.get(userId) || [];
if (!currentRoles.includes(roleName)) {
currentRoles.push(roleName);
this.userRoles.set(userId, currentRoles);
}
}
revokeRole(userId: string, roleName: string): void {
const currentRoles = this.userRoles.get(userId) || [];
const updatedRoles = currentRoles.filter(role => role !== roleName);
this.userRoles.set(userId, updatedRoles);
}
checkAccess(request: AccessRequest): boolean {
const userRoles = this.getUserEffectiveRoles(request.user.id);
const requiredPermission: Permission = {
resource: request.resource,
action: request.action
};
return userRoles.some(role =>
this.roleHasPermission(role, requiredPermission, request.context)
);
}
private getUserEffectiveRoles(userId: string): Role[] {
const roleNames = this.userRoles.get(userId) || [];
const effectiveRoles: Role[] = [];
for (const roleName of roleNames) {
const role = this.roles.get(roleName);
if (role) {
effectiveRoles.push(role);
// Recursively add inherited roles
this.addInheritedRoles(role, effectiveRoles);
}
}
return effectiveRoles;
}
private addInheritedRoles(role: Role, roles: Role[]): void {
if (!role.inheritFrom) return;
for (const inheritedRoleName of role.inheritFrom) {
const inheritedRole = this.roles.get(inheritedRoleName);
if (inheritedRole && !roles.includes(inheritedRole)) {
roles.push(inheritedRole);
this.addInheritedRoles(inheritedRole, roles);
}
}
}
private roleHasPermission(role: Role, required: Permission, context?: Record<string, any>): boolean {
return role.permissions.some(permission => {
if (permission.resource !== required.resource || permission.action !== required.action) {
return false;
}
// Check additional conditions
if (permission.conditions && context) {
return this.evaluateConditions(permission.conditions, context);
}
return true;
});
}
private evaluateConditions(conditions: string[], context: Record<string, any>): boolean {
// Simplified condition evaluation implementation
return conditions.every(condition => {
// Example: "user.department == 'engineering'"
const [left, operator, right] = condition.split(' ');
const leftValue = this.getValueFromContext(left, context);
const rightValue = right.replace(/['"]/g, '');
switch (operator) {
case '==':
return leftValue === rightValue;
case '!=':
return leftValue !== rightValue;
case 'in':
return Array.isArray(leftValue) && leftValue.includes(rightValue);
default:
return false;
}
});
}
private getValueFromContext(path: string, context: Record<string, any>): any {
return path.split('.').reduce((obj, key) => obj?.[key], context);
}
}
3.2 Fine-grained Permission Control
interface ResourceAccessPolicy {
resourceType: string;
resourceId?: string;
policy: {
allow: Permission[];
deny: Permission[];
conditions?: AccessCondition[];
};
}
interface AccessCondition {
type: "time" | "ip" | "user_attribute" | "resource_attribute";
operator: "equals" | "not_equals" | "in" | "not_in" | "greater_than" | "less_than";
value: any;
}
class FinegrainedAccessControl {
private policies: Map<string, ResourceAccessPolicy[]> = new Map();
private rbac: RoleBasedAccessControl;
constructor(rbac: RoleBasedAccessControl) {
this.rbac = rbac;
}
addResourcePolicy(policy: ResourceAccessPolicy): void {
const key = policy.resourceId || policy.resourceType;
const policies = this.policies.get(key) || [];
policies.push(policy);
this.policies.set(key, policies);
}
checkResourceAccess(request: AccessRequest & { resourceId?: string }): boolean {
// First check RBAC permissions
if (!this.rbac.checkAccess(request)) {
return false;
}
// Then check resource-specific policies
const resourcePolicies = this.getApplicablePolicies(request.resource, request.resourceId);
if (resourcePolicies.length === 0) {
return true; // No specific policies, rely on RBAC result
}
return this.evaluateResourcePolicies(resourcePolicies, request);
}
private getApplicablePolicies(resourceType: string, resourceId?: string): ResourceAccessPolicy[] {
const policies: ResourceAccessPolicy[] = [];
// Get resource type level policies
const typePolicies = this.policies.get(resourceType) || [];
policies.push(...typePolicies);
// Get specific resource instance policies
if (resourceId) {
const instancePolicies = this.policies.get(resourceId) || [];
policies.push(...instancePolicies);
}
return policies;
}
private evaluateResourcePolicies(policies: ResourceAccessPolicy[], request: AccessRequest): boolean {
for (const policy of policies) {
// Check deny rules (highest priority)
if (this.matchesPermissions(policy.policy.deny, request)) {
if (this.evaluateAccessConditions(policy.policy.conditions, request)) {
return false;
}
}
}
// Check allow rules
for (const policy of policies) {
if (this.matchesPermissions(policy.policy.allow, request)) {
if (this.evaluateAccessConditions(policy.policy.conditions, request)) {
return true;
}
}
}
return false;
}
private matchesPermissions(permissions: Permission[], request: AccessRequest): boolean {
return permissions.some(permission =>
permission.resource === request.resource && permission.action === request.action
);
}
private evaluateAccessConditions(conditions: AccessCondition[] | undefined, request: AccessRequest): boolean {
if (!conditions || conditions.length === 0) {
return true;
}
return conditions.every(condition => this.evaluateCondition(condition, request));
}
private evaluateCondition(condition: AccessCondition, request: AccessRequest): boolean {
let contextValue: any;
switch (condition.type) {
case "time":
contextValue = new Date().getHours();
break;
case "ip":
contextValue = request.context?.clientIp;
break;
case "user_attribute":
contextValue = request.user.metadata[condition.value];
break;
case "resource_attribute":
contextValue = request.context?.[condition.value];
break;
default:
return false;
}
switch (condition.operator) {
case "equals":
return contextValue === condition.value;
case "not_equals":
return contextValue !== condition.value;
case "in":
return Array.isArray(condition.value) && condition.value.includes(contextValue);
case "not_in":
return Array.isArray(condition.value) && !condition.value.includes(contextValue);
case "greater_than":
return contextValue > condition.value;
case "less_than":
return contextValue < condition.value;
default:
return false;
}
}
}
4. Input Validation and Data Sanitization
4.1 Input Validation Framework
import { z } from 'zod';
interface ValidationRule {
field: string;
type: "string" | "number" | "boolean" | "array" | "object";
required: boolean;
constraints?: ValidationConstraint[];
}
interface ValidationConstraint {
type: "min_length" | "max_length" | "pattern" | "range" | "enum" | "custom";
value: any;
message?: string;
}
class InputValidator {
private schemas: Map<string, z.ZodSchema> = new Map();
registerSchema(name: string, schema: z.ZodSchema): void {
this.schemas.set(name, schema);
}
validate(schemaName: string, data: any): { valid: boolean; errors: string[]; data?: any } {
const schema = this.schemas.get(schemaName);
if (!schema) {
return { valid: false, errors: [`Schema not found: ${schemaName}`] };
}
try {
const validData = schema.parse(data);
return { valid: true, errors: [], data: validData };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.errors.map(err =>
`${err.path.join('.')}: ${err.message}`
);
return { valid: false, errors };
}
return { valid: false, errors: [error.message] };
}
}
// Create common validation schemas
createToolCallSchema(): z.ZodSchema {
return z.object({
name: z.string().min(1).max(100).regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/),
arguments: z.record(z.any()).optional()
});
}
createResourceRequestSchema(): z.ZodSchema {
return z.object({
uri: z.string().url().or(z.string().regex(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//)),
method: z.enum(["GET", "POST", "PUT", "DELETE"]).optional()
});
}
createPromptRequestSchema(): z.ZodSchema {
return z.object({
name: z.string().min(1).max(100),
arguments: z.record(z.union([
z.string(),
z.number(),
z.boolean(),
z.array(z.any()),
z.object({}).passthrough()
])).optional()
});
}
}
4.2 Data Sanitization System
interface SanitizationConfig {
allowedTags?: string[];
allowedAttributes?: Record<string, string[]>;
maxStringLength?: number;
maxArrayLength?: number;
maxObjectDepth?: number;
}
class DataSanitizer {
private config: SanitizationConfig;
constructor(config: SanitizationConfig = {}) {
this.config = {
allowedTags: ['b', 'i', 'em', 'strong', 'code', 'pre'],
allowedAttributes: {
'a': ['href'],
'img': ['src', 'alt']
},
maxStringLength: 10000,
maxArrayLength: 1000,
maxObjectDepth: 10,
...config
};
}
sanitize(data: any, depth: number = 0): any {
if (depth > this.config.maxObjectDepth!) {
throw new Error("Maximum object depth exceeded");
}
if (typeof data === 'string') {
return this.sanitizeString(data);
}
if (Array.isArray(data)) {
return this.sanitizeArray(data, depth);
}
if (typeof data === 'object' && data !== null) {
return this.sanitizeObject(data, depth);
}
return data;
}
private sanitizeString(str: string): string {
if (str.length > this.config.maxStringLength!) {
str = str.substring(0, this.config.maxStringLength!);
}
// Remove potentially malicious content
str = str.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
str = str.replace(/javascript:/gi, '');
str = str.replace(/on\w+\s*=/gi, '');
// HTML tag sanitization
if (this.config.allowedTags && this.config.allowedTags.length > 0) {
str = this.sanitizeHtml(str);
} else {
// If no tags are allowed, escape HTML
str = str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
return str;
}
private sanitizeArray(arr: any[], depth: number): any[] {
if (arr.length > this.config.maxArrayLength!) {
arr = arr.slice(0, this.config.maxArrayLength!);
}
return arr.map(item => this.sanitize(item, depth + 1));
}
private sanitizeObject(obj: Record<string, any>, depth: number): Record<string, any> {
const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(obj)) {
// Sanitize key name
const sanitizedKey = this.sanitizeString(key);
sanitized[sanitizedKey] = this.sanitize(value, depth + 1);
}
return sanitized;
}
private sanitizeHtml(html: string): string {
// Simplified HTML sanitization implementation
// In production, use a professional library like DOMPurify
const allowedTags = this.config.allowedTags!;
const allowedAttributes = this.config.allowedAttributes!;
return html.replace(/<(\/?[^>]+)>/g, (match, tag) => {
const tagMatch = tag.match(/^\/?([\w-]+)/);
if (!tagMatch) return '';
const tagName = tagMatch[1].toLowerCase();
if (!allowedTags.includes(tagName)) return '';
// Check attributes
if (allowedAttributes[tagName]) {
const allowedAttrs = allowedAttributes[tagName];
tag = tag.replace(/(\w+)=["']?([^"'\s>]+)["']?/g, (attrMatch, name, value) => {
return allowedAttrs.includes(name.toLowerCase()) ? attrMatch : '';
});
}
return `<${tag}>`;
});
}
}
5. Security Auditing and Monitoring
5.1 Security Event Recording
enum SecurityEventType {
AUTHENTICATION_SUCCESS = "auth_success",
AUTHENTICATION_FAILURE = "auth_failure",
AUTHORIZATION_DENIED = "authz_denied",
PRIVILEGE_ESCALATION_ATTEMPT = "privilege_escalation",
SUSPICIOUS_ACTIVITY = "suspicious_activity",
DATA_ACCESS = "data_access",
CONFIGURATION_CHANGE = "config_change",
SECURITY_POLICY_VIOLATION = "policy_violation"
}
interface SecurityEvent {
id: string;
type: SecurityEventType;
timestamp: Date;
userId?: string;
sessionId?: string;
clientIp?: string;
userAgent?: string;
resource?: string;
action?: string;
result: "success" | "failure" | "blocked";
details: Record<string, any>;
severity: "low" | "medium" | "high" | "critical";
}
class SecurityAuditor {
private events: SecurityEvent[] = [];
private alertThresholds: Map<SecurityEventType, number> = new Map();
private alertCallbacks: Map<SecurityEventType, (events: SecurityEvent[]) => void> = new Map();
constructor() {
this.setupDefaultThresholds();
}
private setupDefaultThresholds(): void {
this.alertThresholds.set(SecurityEventType.AUTHENTICATION_FAILURE, 5);
this.alertThresholds.set(SecurityEventType.AUTHORIZATION_DENIED, 10);
this.alertThresholds.set(SecurityEventType.PRIVILEGE_ESCALATION_ATTEMPT, 1);
this.alertThresholds.set(SecurityEventType.SUSPICIOUS_ACTIVITY, 3);
}
recordEvent(event: Omit<SecurityEvent, 'id' | 'timestamp'>): void {
const fullEvent: SecurityEvent = {
...event,
id: this.generateEventId(),
timestamp: new Date()
};
this.events.push(fullEvent);
this.checkAlertConditions(fullEvent);
// Limit events in memory
if (this.events.length > 10000) {
this.events.shift();
}
}
private generateEventId(): string {
return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private checkAlertConditions(event: SecurityEvent): void {
const threshold = this.alertThresholds.get(event.type);
if (!threshold) return;
const recentEvents = this.getRecentEvents(event.type, 15 * 60 * 1000); // Within 15 minutes
if (recentEvents.length >= threshold) {
this.triggerAlert(event.type, recentEvents);
}
}
private getRecentEvents(type: SecurityEventType, timeWindowMs: number): SecurityEvent[] {
const cutoff = new Date(Date.now() - timeWindowMs);
return this.events.filter(event =>
event.type === type && event.timestamp >= cutoff
);
}
private triggerAlert(type: SecurityEventType, events: SecurityEvent[]): void {
const callback = this.alertCallbacks.get(type);
if (callback) {
callback(events);
} else {
console.error(`Security alert: ${events.length} ${type} events in the last 15 minutes`);
}
}
setAlertCallback(type: SecurityEventType, callback: (events: SecurityEvent[]) => void): void {
this.alertCallbacks.set(type, callback);
}
getEvents(filter?: {
type?: SecurityEventType;
userId?: string;
startTime?: Date;
endTime?: Date;
severity?: string;
}): SecurityEvent[] {
let filtered = this.events;
if (filter) {
if (filter.type) {
filtered = filtered.filter(e => e.type === filter.type);
}
if (filter.userId) {
filtered = filtered.filter(e => e.userId === filter.userId);
}
if (filter.startTime) {
filtered = filtered.filter(e => e.timestamp >= filter.startTime!);
}
if (filter.endTime) {
filtered = filtered.filter(e => e.timestamp <= filter.endTime!);
}
if (filter.severity) {
filtered = filtered.filter(e => e.severity === filter.severity);
}
}
return filtered.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
}
generateSecurityReport(timeRange: { start: Date; end: Date }): string {
const events = this.getEvents({
startTime: timeRange.start,
endTime: timeRange.end
});
const report = [
"=== Security Audit Report ===",
`Time Range: ${timeRange.start.toISOString()} to ${timeRange.end.toISOString()}`,
`Total Events: ${events.length}`,
""
];
// Statistics by type
const eventsByType: Record<string, number> = {};
events.forEach(event => {
eventsByType[event.type] = (eventsByType[event.type] || 0) + 1;
});
report.push("Events by Type:");
for (const [type, count] of Object.entries(eventsByType)) {
report.push(` ${type}: ${count}`);
}
report.push("");
// Statistics by severity
const eventsBySeverity: Record<string, number> = {};
events.forEach(event => {
eventsBySeverity[event.severity] = (eventsBySeverity[event.severity] || 0) + 1;
});
report.push("Events by Severity:");
for (const [severity, count] of Object.entries(eventsBySeverity)) {
report.push(` ${severity}: ${count}`);
}
report.push("");
// High-risk events
const criticalEvents = events.filter(e => e.severity === "critical");
if (criticalEvents.length > 0) {
report.push("Critical Events:");
criticalEvents.forEach(event => {
report.push(` ${event.timestamp.toISOString()}: ${event.type} - ${event.details.description || 'No description'}`);
});
}
return report.join("\n");
}
}
5.2 Real-time Threat Detection
interface ThreatPattern {
name: string;
description: string;
conditions: ThreatCondition[];
severity: "low" | "medium" | "high" | "critical";
action: "log" | "alert" | "block";
}
interface ThreatCondition {
field: string;
operator: "equals" | "contains" | "greater_than" | "pattern";
value: any;
timeWindow?: number; // milliseconds
threshold?: number;
}
class ThreatDetectionEngine {
private patterns: ThreatPattern[] = [];
private eventHistory: SecurityEvent[] = [];
private blockedIPs: Set<string> = new Set();
private blockedUsers: Set<string> = new Set();
constructor() {
this.initializeDefaultPatterns();
}
private initializeDefaultPatterns(): void {
// Brute force detection
this.addPattern({
name: "brute_force_attack",
description: "Multiple authentication failures from same IP",
conditions: [
{
field: "type",
operator: "equals",
value: SecurityEventType.AUTHENTICATION_FAILURE
},
{
field: "clientIp",
operator: "equals",
value: "{{clientIp}}",
timeWindow: 5 * 60 * 1000, // 5 minutes
threshold: 5
}
],
severity: "high",
action: "block"
});
// Privilege escalation attempt
this.addPattern({
name: "privilege_escalation",
description: "Attempt to access unauthorized resources",
conditions: [
{
field: "type",
operator: "equals",
value: SecurityEventType.AUTHORIZATION_DENIED
},
{
field: "userId",
operator: "equals",
value: "{{userId}}",
timeWindow: 10 * 60 * 1000, // 10 minutes
threshold: 3
}
],
severity: "critical",
action: "alert"
});
}
addPattern(pattern: ThreatPattern): void {
this.patterns.push(pattern);
}
analyzeEvent(event: SecurityEvent): ThreatDetectionResult[] {
const results: ThreatDetectionResult[] = [];
this.eventHistory.push(event);
// Limit history size
if (this.eventHistory.length > 5000) {
this.eventHistory.shift();
}
for (const pattern of this.patterns) {
const result = this.evaluatePattern(pattern, event);
if (result.matched) {
results.push(result);
this.handleThreatDetection(result);
}
}
return results;
}
private evaluatePattern(pattern: ThreatPattern, currentEvent: SecurityEvent): ThreatDetectionResult {
const matchedConditions: boolean[] = [];
for (const condition of pattern.conditions) {
if (condition.threshold && condition.timeWindow) {
// Time window threshold detection
const matched = this.evaluateThresholdCondition(condition, currentEvent);
matchedConditions.push(matched);
} else {
// Single event condition detection
const matched = this.evaluateEventCondition(condition, currentEvent);
matchedConditions.push(matched);
}
}
const allMatched = matchedConditions.every(Boolean);
return {
patternName: pattern.name,
matched: allMatched,
severity: pattern.severity,
action: pattern.action,
event: currentEvent,
matchedConditions: matchedConditions.length,
totalConditions: pattern.conditions.length
};
}
private evaluateThresholdCondition(condition: ThreatCondition, currentEvent: SecurityEvent): boolean {
const timeWindow = condition.timeWindow!;
const threshold = condition.threshold!;
const cutoff = new Date(currentEvent.timestamp.getTime() - timeWindow);
// Get relevant events within time window
const relevantEvents = this.eventHistory.filter(event => {
if (event.timestamp < cutoff) return false;
// Match events based on condition field
const fieldValue = this.getFieldValue(condition.field, event);
const currentFieldValue = this.getFieldValue(condition.field, currentEvent);
return this.compareValues(fieldValue, condition.operator, condition.value) &&
fieldValue === currentFieldValue;
});
return relevantEvents.length >= threshold;
}
private evaluateEventCondition(condition: ThreatCondition, event: SecurityEvent): boolean {
const fieldValue = this.getFieldValue(condition.field, event);
return this.compareValues(fieldValue, condition.operator, condition.value);
}
private getFieldValue(field: string, event: SecurityEvent): any {
const fields = field.split('.');
let value: any = event;
for (const f of fields) {
value = value?.[f];
}
return value;
}
private compareValues(actual: any, operator: string, expected: any): boolean {
switch (operator) {
case "equals":
return actual === expected;
case "contains":
return typeof actual === 'string' && actual.includes(expected);
case "greater_than":
return typeof actual === 'number' && actual > expected;
case "pattern":
return typeof actual === 'string' && new RegExp(expected).test(actual);
default:
return false;
}
}
private handleThreatDetection(result: ThreatDetectionResult): void {
switch (result.action) {
case "log":
console.log(`Threat detected: ${result.patternName}`, result);
break;
case "alert":
this.sendAlert(result);
break;
case "block":
this.blockSource(result);
break;
}
}
private sendAlert(result: ThreatDetectionResult): void {
console.error(`SECURITY ALERT: ${result.patternName}`, {
severity: result.severity,
event: result.event,
timestamp: new Date().toISOString()
});
// Here you can integrate external alerting systems
}
private blockSource(result: ThreatDetectionResult): void {
const event = result.event;
if (event.clientIp) {
this.blockedIPs.add(event.clientIp);
console.error(`Blocked IP: ${event.clientIp} due to ${result.patternName}`);
}
if (event.userId) {
this.blockedUsers.add(event.userId);
console.error(`Blocked user: ${event.userId} due to ${result.patternName}`);
}
}
isBlocked(clientIp?: string, userId?: string): boolean {
if (clientIp && this.blockedIPs.has(clientIp)) {
return true;
}
if (userId && this.blockedUsers.has(userId)) {
return true;
}
return false;
}
unblock(clientIp?: string, userId?: string): void {
if (clientIp) {
this.blockedIPs.delete(clientIp);
}
if (userId) {
this.blockedUsers.delete(userId);
}
}
}
interface ThreatDetectionResult {
patternName: string;
matched: boolean;
severity: "low" | "medium" | "high" | "critical";
action: "log" | "alert" | "block";
event: SecurityEvent;
matchedConditions: number;
totalConditions: number;
}
6. Integrated Security Middleware
6.1 Security Middleware System
class SecurityMiddleware {
private authProviders: Map<AuthenticationMethod, AuthenticationProvider> = new Map();
private accessControl: FinegrainedAccessControl;
private validator: InputValidator;
private sanitizer: DataSanitizer;
private auditor: SecurityAuditor;
private threatDetector: ThreatDetectionEngine;
constructor(
accessControl: FinegrainedAccessControl,
validator: InputValidator,
sanitizer: DataSanitizer,
auditor: SecurityAuditor,
threatDetector: ThreatDetectionEngine
) {
this.accessControl = accessControl;
this.validator = validator;
this.sanitizer = sanitizer;
this.auditor = auditor;
this.threatDetector = threatDetector;
}
addAuthProvider(method: AuthenticationMethod, provider: AuthenticationProvider): void {
this.authProviders.set(method, provider);
}
createSecureHandler<T>(
schemaName: string,
handler: (request: any, user: AuthenticatedUser) => Promise<T>
) {
return async (request: any): Promise<T> => {
const startTime = Date.now();
let user: AuthenticatedUser | undefined;
try {
// 1. Extract client information
const clientInfo = this.extractClientInfo(request);
// 2. Check if blocked
if (this.threatDetector.isBlocked(clientInfo.ip, clientInfo.userId)) {
throw new Error("Access blocked due to security policy");
}
// 3. Authentication
user = await this.authenticate(request);
// 4. Input validation
const validation = this.validator.validate(schemaName, request);
if (!validation.valid) {
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
}
// 5. Data sanitization
const sanitizedRequest = this.sanitizer.sanitize(validation.data || request);
// 6. Permission check
const accessRequest: AccessRequest = {
user,
resource: this.extractResourceFromRequest(sanitizedRequest),
action: this.extractActionFromRequest(sanitizedRequest),
context: { ...clientInfo, originalRequest: request }
};
if (!this.accessControl.checkResourceAccess(accessRequest)) {
// Record permission denied event
this.auditor.recordEvent({
type: SecurityEventType.AUTHORIZATION_DENIED,
userId: user.id,
sessionId: clientInfo.sessionId,
clientIp: clientInfo.ip,
userAgent: clientInfo.userAgent,
resource: accessRequest.resource,
action: accessRequest.action,
result: "blocked",
details: { reason: "Insufficient permissions" },
severity: "medium"
});
throw new Error("Access denied");
}
// 7. Execute business logic
const result = await handler(sanitizedRequest, user);
// 8. Record success event
this.auditor.recordEvent({
type: SecurityEventType.DATA_ACCESS,
userId: user.id,
sessionId: clientInfo.sessionId,
clientIp: clientInfo.ip,
userAgent: clientInfo.userAgent,
resource: accessRequest.resource,
action: accessRequest.action,
result: "success",
details: {
responseTime: Date.now() - startTime,
method: request.method
},
severity: "low"
});
return result;
} catch (error) {
// Record error event
const securityEvent: Omit<SecurityEvent, 'id' | 'timestamp'> = {
type: error.message.includes("Access denied")
? SecurityEventType.AUTHORIZATION_DENIED
: SecurityEventType.SUSPICIOUS_ACTIVITY,
userId: user?.id,
sessionId: this.extractClientInfo(request).sessionId,
clientIp: this.extractClientInfo(request).ip,
userAgent: this.extractClientInfo(request).userAgent,
resource: this.extractResourceFromRequest(request),
action: this.extractActionFromRequest(request),
result: "failure",
details: {
error: error.message,
responseTime: Date.now() - startTime
},
severity: "medium"
};
this.auditor.recordEvent(securityEvent);
// Threat detection
const threats = this.threatDetector.analyzeEvent({
...securityEvent,
id: 'temp',
timestamp: new Date()
});
throw error;
}
};
}
private async authenticate(request: any): Promise<AuthenticatedUser> {
const authHeader = request.headers?.authorization;
if (!authHeader) {
throw new Error("Authentication required");
}
// Try different authentication methods
for (const [method, provider] of this.authProviders) {
try {
const credential = this.extractCredential(authHeader, method);
if (credential) {
const result = await provider.authenticate(credential);
if (result.success && result.user) {
return result.user;
}
}
} catch (error) {
continue; // Try next authentication provider
}
}
throw new Error("Authentication failed");
}
private extractCredential(authHeader: string, method: AuthenticationMethod): AuthenticationCredential | null {
switch (method) {
case AuthenticationMethod.API_KEY:
if (authHeader.startsWith('ApiKey ')) {
return {
method: AuthenticationMethod.API_KEY,
value: authHeader.substring(7)
};
}
break;
case AuthenticationMethod.JWT:
if (authHeader.startsWith('Bearer ')) {
return {
method: AuthenticationMethod.JWT,
value: authHeader.substring(7)
};
}
break;
}
return null;
}
private extractClientInfo(request: any): { ip?: string; userAgent?: string; sessionId?: string; userId?: string } {
return {
ip: request.headers?.['x-forwarded-for'] || request.connection?.remoteAddress,
userAgent: request.headers?.['user-agent'],
sessionId: request.headers?.['x-session-id'],
userId: request.headers?.['x-user-id']
};
}
private extractResourceFromRequest(request: any): string {
// Extract resource type from request
if (request.method?.startsWith('tools/')) return 'tools';
if (request.method?.startsWith('resources/')) return 'resources';
if (request.method?.startsWith('prompts/')) return 'prompts';
return 'unknown';
}
private extractActionFromRequest(request: any): string {
// Extract action type from request
const method = request.method || '';
if (method.includes('/list')) return 'list';
if (method.includes('/read')) return 'read';
if (method.includes('/call')) return 'execute';
if (method.includes('/get')) return 'read';
return 'unknown';
}
}
7. Best Practices
7.1 Security Development Principles
- Defense in Depth - Multiple layers of security controls
- Least Privilege - Grant only the minimum necessary permissions
- Zero Trust - Don’t trust any user or system
- Fail-Safe - Default to denying access on failure
- Continuous Monitoring - Real-time monitoring of security status
7.2 Configuration and Deployment Security
- Key Management - Use professional key management systems
- Environment Isolation - Separate development, testing, and production environments
- Regular Updates - Timely updates of dependencies and security patches
- Security Configuration - Use secure default configurations
- Backup and Recovery - Regular backups and disaster recovery testing
7.3 Monitoring and Response
- Security Dashboard - Visualize security status
- Automated Response - Automated threat response mechanisms
- Event Correlation - Correlate and analyze security events
- Threat Intelligence - Integrate external threat intelligence
- Regular Assessment - Regular security assessments and penetration testing
Summary
Through this chapter, we have mastered:
- MCP security threat model and protection strategies
- Implementation of authentication and authorization systems
- Fine-grained permission control mechanisms
- Input validation and data sanitization techniques
- Security auditing and threat detection systems
Security is an important foundation of MCP Server, requiring consideration of security issues from the design phase, implementing multi-level security protection mechanisms to ensure secure and reliable system operation.