Skip to main content
Glama
SecurityConfig.ts8.22 kB
/** * Security configuration and constants for MCP WordPress */ import { randomBytes } from "crypto"; import * as path from "path"; import { LoggerFactory } from "@/utils/logger.js"; const logger = LoggerFactory.security(); export const SecurityConfig = { // Rate limiting rateLimiting: { default: { windowMs: 60 * 1000, // 1 minute maxRequests: 60, }, authentication: { windowMs: 5 * 60 * 1000, // 5 minutes maxAttempts: 5, }, upload: { windowMs: 60 * 1000, // 1 minute maxRequests: 10, }, }, // File upload restrictions fileUpload: { maxSizeMB: 10, allowedMimeTypes: [ "image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/plain", "text/csv", ], // Dangerous file extensions to block blockedExtensions: [ ".exe", ".bat", ".cmd", ".com", ".pif", ".scr", ".vbs", ".js", ".jar", ".zip", ".rar", ".tar", ".php", ".php3", ".php4", ".php5", ".phtml", ".sh", ".bash", ".zsh", ".fish", ".ps1", ".psm1", ], }, // Input validation validation: { maxStringLength: 1000, maxTitleLength: 200, maxContentLength: 50000, maxExcerptLength: 500, maxUrlLength: 2048, maxUsernameLength: 60, minUsernameLength: 3, maxPasswordLength: 128, minPasswordLength: 8, }, // Request timeouts (milliseconds) timeouts: { default: 30000, // 30 seconds upload: 600000, // 10 minutes auth: 10000, // 10 seconds }, // Security headers headers: { "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "X-XSS-Protection": "1; mode=block", "Strict-Transport-Security": "max-age=31536000; includeSubDomains", "Content-Security-Policy": "default-src 'self'", }, // Error messages (generic to avoid information disclosure) errorMessages: { authentication: "Authentication failed. Please check your credentials.", authorization: "You do not have permission to perform this action.", validation: "Invalid input provided.", rateLimit: "Too many requests. Please try again later.", serverError: "An error occurred processing your request.", notFound: "The requested resource was not found.", }, // Logging configuration logging: { // Fields to exclude from logs excludeFields: [ "password", "appPassword", "app_password", "token", "secret", "authorization", "cookie", "session", "key", "apiKey", "api_key", ], // Patterns to redact in log messages redactPatterns: [ /password["\s:=]+["']?([^"'\s]+)["']?/gi, /token["\s:=]+["']?([^"'\s]+)["']?/gi, /secret["\s:=]+["']?([^"'\s]+)["']?/gi, /key["\s:=]+["']?([^"'\s]+)["']?/gi, ], }, // Cache configuration cache: { // Default cache settings enabled: true, maxSize: 1000, // Maximum number of cached entries defaultTTL: 15 * 60 * 1000, // 15 minutes default TTL enableLRU: true, enableStats: true, // TTL presets by data type (milliseconds) ttlPresets: { static: 4 * 60 * 60 * 1000, // 4 hours - site settings, user roles semiStatic: 2 * 60 * 60 * 1000, // 2 hours - categories, tags, user profiles dynamic: 15 * 60 * 1000, // 15 minutes - posts, pages, comments session: 30 * 60 * 1000, // 30 minutes - authentication, current user realtime: 60 * 1000, // 1 minute - real-time data }, // Cache-Control headers by data type cacheHeaders: { static: "public, max-age=14400", // 4 hours semiStatic: "public, max-age=7200", // 2 hours dynamic: "public, max-age=900", // 15 minutes session: "private, max-age=1800", // 30 minutes realtime: "public, max-age=60", // 1 minute }, // Invalidation settings invalidation: { enabled: true, batchSize: 100, // Max events to process in one batch queueTimeout: 5000, // Max time to wait before processing queue (ms) enableCascading: true, // Allow cascading invalidations }, // Memory management cleanup: { interval: 60 * 1000, // Cleanup interval in milliseconds (1 minute) maxMemoryMB: 50, // Maximum memory usage for cache evictionThreshold: 0.8, // Start evicting when 80% full }, }, }; /** * Security utility functions */ export class SecurityUtils { /** * Redact sensitive information from objects */ static redactSensitiveData(obj: unknown): unknown { if (typeof obj !== "object" || obj === null) { return obj; } const working: Record<string, unknown> | unknown[] = Array.isArray(obj) ? [...obj] : { ...(obj as Record<string, unknown>) }; if (Array.isArray(working)) { return working.map((val) => (typeof val === "object" ? SecurityUtils.redactSensitiveData(val) : val)); } for (const key in working) { if (SecurityConfig.logging.excludeFields.some((field) => key.toLowerCase().includes(field.toLowerCase()))) { working[key] = "[REDACTED]"; } else if (typeof working[key] === "object" && working[key] !== null) { working[key] = SecurityUtils.redactSensitiveData(working[key]); } } return working; } /** * Redact sensitive patterns from strings */ static redactString(str: string): string { let redacted = str; for (const pattern of SecurityConfig.logging.redactPatterns) { redacted = redacted.replace(pattern, (match, value) => { return match.replace(value, "[REDACTED]"); }); } return redacted; } /** * Generate secure random strings */ static generateSecureToken(length: number = 32): string { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const array = new Uint8Array(length); if (typeof crypto !== "undefined" && crypto.getRandomValues) { crypto.getRandomValues(array); } else { // Fallback for Node.js const buffer = randomBytes(length); array.set(buffer); } return Array.from(array, (byte) => chars[byte % chars.length]).join(""); } /** * Check if a file extension is allowed */ static isFileExtensionAllowed(filename: string): boolean { const ext = path.extname(filename).toLowerCase(); return !SecurityConfig.fileUpload.blockedExtensions.includes(ext); } /** * Sanitize log output */ static sanitizeForLog(data: unknown): unknown { if (typeof data === "string") { return SecurityUtils.redactString(data); } if (typeof data === "object") { return SecurityUtils.redactSensitiveData(data); } return data; } } /** * Secure error handler that prevents information disclosure */ export function createSecureError( error: unknown, fallbackMessage: string = SecurityConfig.errorMessages.serverError, ): Error { // Log the actual error for debugging (with sanitization) if (process.env.NODE_ENV !== "production") { logger.error("Secure Error", { error: SecurityUtils.sanitizeForLog(error) }); } // Return generic error to prevent information disclosure const secureError = new Error(fallbackMessage); // Preserve error code if it's safe if ( error && typeof (error as { code?: unknown }).code === "string" && !(error as { code: string }).code.includes("_") ) { (secureError as unknown as Record<string, unknown>).code = (error as { code: string }).code; } return secureError; } /** * Environment-specific security settings */ export function getEnvironmentSecurity(): { strictMode: boolean; verboseErrors: boolean; enforceHttps: boolean; } { const isProduction = process.env.NODE_ENV === "production"; return { strictMode: isProduction, verboseErrors: !isProduction, enforceHttps: isProduction, }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server