opendoor-mcp
Version:
Production-grade MCP server with multi-language code execution, VS Code integration, and browser automation
304 lines • 11.4 kB
JavaScript
import { Logger } from '../utils/Logger.js';
import crypto from 'crypto';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import Joi from 'joi';
import NodeCache from 'node-cache';
export class SecurityManager {
logger = Logger.getInstance();
allowedOrigins;
apiKeys;
rateLimiter;
validationCache;
requestSchema;
codePatternCache = new Map();
constructor() {
this.allowedOrigins = new Set(process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:8080']);
this.apiKeys = new Set(process.env.API_KEYS?.split(',') || []);
// Enhanced rate limiting with different tiers
this.rateLimiter = new RateLimiterMemory({
points: parseInt(process.env.RATE_LIMIT_POINTS || '100'),
duration: 60, // Per 60 seconds
blockDuration: parseInt(process.env.RATE_LIMIT_BLOCK_DURATION || '300'), // 5 minutes block
});
// Cache for validation results
this.validationCache = new NodeCache({
stdTTL: 300, // 5 minutes
checkperiod: 60
});
// Joi schema for request validation
this.requestSchema = Joi.object({
id: Joi.alternatives().try(Joi.string(), Joi.number(), Joi.allow(null)).required(),
method: Joi.string().required().pattern(/^[a-zA-Z_\/]+$/),
params: Joi.object().unknown(true).optional()
});
// Pre-compile dangerous patterns for performance
this.initializeDangerousPatterns();
}
initializeDangerousPatterns() {
const patterns = [
'child_process',
'subprocess',
'os\\.system',
'eval\\s*\\(',
'exec\\s*\\(',
'shell_exec',
'__import__.*os',
'Runtime\\.getRuntime',
'ProcessBuilder'
];
patterns.forEach(pattern => {
this.codePatternCache.set(pattern, new RegExp(pattern, 'i'));
});
}
async validateConnection(request) {
try {
// Rate limiting
const clientId = this.getClientIdentifier(request);
await this.rateLimiter.consume(clientId);
// Origin validation
const origin = request.headers.origin;
if (origin && !this.allowedOrigins.has(origin)) {
this.logger.warn(`Blocked connection from unauthorized origin: ${origin}`);
return false;
}
// API key validation (if required)
if (this.apiKeys.size > 0) {
const apiKey = request.headers['x-api-key'];
if (!apiKey || !this.apiKeys.has(apiKey)) {
this.logger.warn(`Blocked connection with invalid API key`);
return false;
}
}
return true;
}
catch (error) {
if (error instanceof Error && error.message.includes('Rate limit')) {
this.logger.warn(`Rate limit exceeded for client`);
return false;
}
this.logger.error('Error validating connection:', error);
return false;
}
}
async validateRequest(request, headers) {
try {
// Rate limiting
const clientId = headers['x-forwarded-for'] || headers['x-real-ip'] || 'unknown';
await this.rateLimiter.consume(clientId);
// API key validation (if required)
if (this.apiKeys.size > 0) {
const apiKey = headers['x-api-key'];
if (!apiKey || !this.apiKeys.has(apiKey)) {
this.logger.warn(`Blocked request with invalid API key`);
return false;
}
}
// Request validation
if (!this.isValidMcpRequest(request)) {
this.logger.warn(`Blocked invalid MCP request`);
return false;
}
// Input sanitization
this.sanitizeRequest(request);
return true;
}
catch (error) {
if (error instanceof Error && error.message.includes('Rate limit')) {
this.logger.warn(`Rate limit exceeded for request`);
return false;
}
this.logger.error('Error validating request:', error);
return false;
}
}
validateCodeExecution(code, language) {
// Check validation cache first
const cacheKey = crypto.createHash('md5').update(code + language).digest('hex');
const cached = this.validationCache.get(cacheKey);
if (cached) {
return cached;
}
// Enhanced code validation with performance optimization
const result = this.performCodeValidation(code, language);
// Cache the result
this.validationCache.set(cacheKey, result);
return result;
}
performCodeValidation(code, language) {
// Use pre-compiled patterns for better performance
for (const [patternName, regex] of this.codePatternCache) {
if (regex.test(code)) {
return {
valid: false,
reason: `Dangerous pattern detected: ${patternName}`
};
}
}
// Additional pattern checks
const additionalPatterns = [
{ pattern: /\$\(/, reason: 'Shell command substitution not allowed' },
{ pattern: /`[^`]*`/, reason: 'Backtick execution not allowed' },
{ pattern: /file_get_contents\s*\(\s*['"]\/(?:proc|sys|etc)\//, reason: 'System file access not allowed' },
{ pattern: /open\s*\(\s*['"]\/(?:dev|proc|sys)\//, reason: 'System device access not allowed' }
];
for (const { pattern, reason } of additionalPatterns) {
if (pattern.test(code)) {
return { valid: false, reason };
}
}
// Language-specific validation with enhanced checks
switch (language.toLowerCase()) {
case 'python':
return this.validatePythonCode(code);
case 'javascript':
case 'typescript':
return this.validateJavaScriptCode(code);
case 'bash':
case 'shell':
case 'sh':
return { valid: false, reason: 'Shell execution not allowed' };
case 'java':
return this.validateJavaCode(code);
case 'c':
case 'cpp':
case 'c++':
return this.validateCCode(code);
default:
return { valid: true };
}
}
validateJavaCode(code) {
const javaPatterns = [
{ pattern: /Runtime\.getRuntime\(\)/, reason: 'Runtime access not allowed' },
{ pattern: /ProcessBuilder/, reason: 'Process creation not allowed' },
{ pattern: /System\.exit/, reason: 'System exit not allowed' },
{ pattern: /new\s+File\s*\(\s*['"]\//, reason: 'Absolute file paths not allowed' },
{ pattern: /FileInputStream.*\//, reason: 'System file access not allowed' }
];
for (const { pattern, reason } of javaPatterns) {
if (pattern.test(code)) {
return { valid: false, reason };
}
}
return { valid: true };
}
validateCCode(code) {
const cPatterns = [
{ pattern: /system\s*\(/, reason: 'System calls not allowed' },
{ pattern: /popen\s*\(/, reason: 'Process creation not allowed' },
{ pattern: /execv?e?\s*\(/, reason: 'Exec functions not allowed' },
{ pattern: /#include\s*<unistd\.h>/, reason: 'Unix system headers not allowed' },
{ pattern: /fork\s*\(/, reason: 'Process forking not allowed' }
];
for (const { pattern, reason } of cPatterns) {
if (pattern.test(code)) {
return { valid: false, reason };
}
}
return { valid: true };
}
generateSessionToken() {
return crypto.randomBytes(32).toString('hex');
}
hashApiKey(apiKey) {
return crypto.createHash('sha256').update(apiKey).digest('hex');
}
getClientIdentifier(request) {
return request.socket.remoteAddress ||
request.headers['x-forwarded-for'] ||
request.headers['x-real-ip'] ||
'unknown';
}
isValidMcpRequest(request) {
try {
const { error } = this.requestSchema.validate(request);
return !error;
}
catch (validationError) {
this.logger.warn('Request validation error:', validationError);
return false;
}
}
sanitizeRequest(request) {
// Recursively sanitize request parameters
if (request.params && typeof request.params === 'object') {
this.sanitizeObject(request.params);
}
}
sanitizeObject(obj) {
for (const key in obj) {
if (typeof obj[key] === 'string') {
// Remove potential script tags and other dangerous content
obj[key] = obj[key]
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/on\w+\s*=/gi, '');
}
else if (typeof obj[key] === 'object' && obj[key] !== null) {
this.sanitizeObject(obj[key]);
}
}
}
validatePythonCode(code) {
const pythonDangerousPatterns = [
/import\s+os/,
/from\s+os\s+import/,
/import\s+subprocess/,
/from\s+subprocess\s+import/,
/import\s+sys/,
/from\s+sys\s+import/,
/__builtins__/,
/compile\s*\(/,
/globals\s*\(\s*\)/,
/locals\s*\(\s*\)/,
/vars\s*\(\s*\)/,
/dir\s*\(\s*\)/,
/getattr\s*\(/,
/setattr\s*\(/,
/hasattr\s*\(/,
/delattr\s*\(/,
/open\s*\(/,
/file\s*\(/,
/input\s*\(/,
/raw_input\s*\(/,
];
for (const pattern of pythonDangerousPatterns) {
if (pattern.test(code)) {
return {
valid: false,
reason: `Dangerous Python pattern detected: ${pattern.source}`
};
}
}
return { valid: true };
}
validateJavaScriptCode(code) {
const jsDangerousPatterns = [
/require\s*\(/,
/import\s*\(/,
/new\s+Function\s*\(/,
/Function\s*\(/,
/setTimeout\s*\(/,
/setInterval\s*\(/,
/XMLHttpRequest/,
/fetch\s*\(/,
/window\./,
/document\./,
/global\./,
/process\./,
/Buffer\./,
/__dirname/,
/__filename/,
];
for (const pattern of jsDangerousPatterns) {
if (pattern.test(code)) {
return {
valid: false,
reason: `Dangerous JavaScript pattern detected: ${pattern.source}`
};
}
}
return { valid: true };
}
}
//# sourceMappingURL=SecurityManager.js.map