UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

907 lines (845 loc) 27.2 kB
import { JSONSchema7 } from 'json-schema'; import { randomUUID } from 'crypto'; import { createTool, createSuccessResult, createErrorResult } from '../../core/tool-framework.js'; import { ToolRegistration, RequestContext } from '../../core/types.js'; import { SecurityScanner } from './scanner.js'; import { SecurityManager } from '../../utils/security-manager.js'; import { SecurityConfig, SecurityScanResult, SecurityFinding, SecretFinding, VulnerabilityReport, SecuritySetupConfig, SecurityReport } from './types.js'; /** * Security Management Tools - 12-Factor MCP Implementation * * Implements Factor 2: Deterministic Execution with structured outputs * Implements Factor 3: Stateless Processes with RequestContext * Implements Factor 4: Structured Outputs for LLM consumption * Implements Factor 11: Human Approval for critical security operations */ // Input type interfaces interface GetSecurityStatusInput { // No parameters needed } interface GetSecurityEventsInput { timeRange?: '1h' | '24h' | '7d' | '30d'; eventTypes?: Array<'access_denied' | 'approval_required' | 'policy_violation' | 'suspicious_activity' | 'error'>; severity?: 'low' | 'medium' | 'high' | 'critical'; } interface ConfigureSecurityPolicyInput { requireApprovalFor?: string[]; roles?: Record<string, string[]>; riskThresholds?: Record<string, number>; logLevel?: 'debug' | 'info' | 'warn' | 'error'; } interface ProcessSecurityApprovalInput { approvalId: string; decision: 'approve' | 'deny'; reason?: string; } interface SecurityScanInput { scanType?: 'quick' | 'comprehensive' | 'deep'; includeFiles?: boolean; includeDependencies?: boolean; includeSecrets?: boolean; includePermissions?: boolean; outputFormat?: 'summary' | 'detailed' | 'json'; } interface CheckSecretsInput { scanPath?: string; excludePaths?: string[]; customPatterns?: string[]; } interface VulnerabilityCheckInput { checkNpm?: boolean; checkYarn?: boolean; severity?: 'low' | 'moderate' | 'high' | 'critical'; autoFix?: boolean; } interface SetupSecurityConfigInput { enablePreCommitScans?: boolean; enableSecretDetection?: boolean; enableVulnerabilityChecks?: boolean; createSecurityPolicy?: boolean; setupGitHooks?: boolean; } interface GenerateSecurityReportInput { includeOverview?: boolean; includeDetails?: boolean; includeRecommendations?: boolean; outputPath?: string; } interface CheckDataAccessInput { scanPath?: string; excludePaths?: string[]; includeWarnings?: boolean; includeSuggestions?: boolean; } // Security scanner and manager instances let securityScanner: SecurityScanner; let securityManager: SecurityManager; /** * Get security status */ const getSecurityStatusTool = createTool<GetSecurityStatusInput, any>({ name: 'get_security_status', description: 'Get comprehensive security status overview including recent events, policy status, and system health', category: 'security', readOnly: true, inputSchema: { type: 'object', properties: {}, required: [], additionalProperties: false } as JSONSchema7, async execute(input: GetSecurityStatusInput, context: RequestContext) { try { // Initialize managers if needed if (!securityManager) { securityManager = SecurityManager.getInstance(); } const status = await securityManager.getSecurityStatus(); return createSuccessResult({ status, generatedAt: new Date().toISOString() }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to get security status: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Get security events */ const getSecurityEventsTool = createTool<GetSecurityEventsInput, any>({ name: 'get_security_events', description: 'Retrieve security events and incidents with filtering options', category: 'security', readOnly: true, inputSchema: { type: 'object', properties: { timeRange: { type: 'string', enum: ['1h', '24h', '7d', '30d'], default: '24h', description: 'Time range for event retrieval' }, eventTypes: { type: 'array', items: { type: 'string', enum: ['access_denied', 'approval_required', 'policy_violation', 'suspicious_activity', 'error'] }, description: 'Filter events by type' }, severity: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], default: 'low', description: 'Minimum severity level to include' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: GetSecurityEventsInput, context: RequestContext) { try { if (!securityManager) { securityManager = SecurityManager.getInstance(); } const events = await securityManager.getSecurityEvents({ timeRange: input.timeRange || '24h', eventTypes: input.eventTypes, severity: input.severity || 'low' }); return createSuccessResult({ timeRange: input.timeRange || '24h', eventCount: events.length, events, generatedAt: new Date().toISOString() }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to get security events: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Configure security policy */ const configureSecurityPolicyTool = createTool<ConfigureSecurityPolicyInput, any>({ name: 'configure_security_policy', description: 'Configure security policies including approval requirements, role definitions, and risk thresholds', category: 'security', requiresApproval: true, inputSchema: { type: 'object', properties: { requireApprovalFor: { type: 'array', items: { type: 'string' }, description: 'List of operations requiring human approval' }, roles: { type: 'object', additionalProperties: { type: 'array', items: { type: 'string' } }, description: 'Role definitions with permissions' }, riskThresholds: { type: 'object', additionalProperties: { type: 'number' }, description: 'Risk threshold values for different operations' }, logLevel: { type: 'string', enum: ['debug', 'info', 'warn', 'error'], default: 'info', description: 'Security logging level' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: ConfigureSecurityPolicyInput, context: RequestContext) { try { if (!securityManager) { securityManager = SecurityManager.getInstance(); } await securityManager.configureSecurityPolicy({ requireApprovalFor: input.requireApprovalFor || [], roles: input.roles || {}, riskThresholds: input.riskThresholds || {}, logLevel: input.logLevel || 'info' }); // Log security event await context.db.run( `INSERT INTO security_events (id, event_type, tool_name, user_id, context, risk_level, details, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), 'policy_change', 'configure_security_policy', context.userId || 'system', JSON.stringify({ projectId: context.projectId }), 'high', JSON.stringify(input), Date.now() ] ); return createSuccessResult({ configuration: { requireApprovalFor: input.requireApprovalFor || [], roles: input.roles || {}, riskThresholds: input.riskThresholds || {}, logLevel: input.logLevel || 'info' }, updatedAt: new Date().toISOString() }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to configure security policy: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Process security approval */ const processSecurityApprovalTool = createTool<ProcessSecurityApprovalInput, any>({ name: 'process_security_approval', description: 'Process pending security approval requests', category: 'security', inputSchema: { type: 'object', properties: { approvalId: { type: 'string', description: 'Unique identifier for the approval request', minLength: 1 }, decision: { type: 'string', enum: ['approve', 'deny'], description: 'Decision for the approval request' }, reason: { type: 'string', description: 'Optional reason for the decision', maxLength: 1000 } }, required: ['approvalId', 'decision'], additionalProperties: false } as JSONSchema7, async execute(input: ProcessSecurityApprovalInput, context: RequestContext) { try { if (!securityManager) { securityManager = SecurityManager.getInstance(); } const result = await securityManager.processApproval({ approvalId: input.approvalId, decision: input.decision, reason: input.reason }); // Log security event await context.db.run( `INSERT INTO security_events (id, event_type, tool_name, user_id, context, risk_level, details, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), 'approval_processed', 'process_security_approval', context.userId || 'system', JSON.stringify({ projectId: context.projectId, approvalId: input.approvalId }), 'medium', JSON.stringify({ decision: input.decision, reason: input.reason }), Date.now() ] ); return createSuccessResult({ approvalId: input.approvalId, decision: input.decision, reason: input.reason, result, processedAt: new Date().toISOString() }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to process approval: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Perform security scan */ const securityScanTool = createTool<SecurityScanInput, any>({ name: 'security_scan', description: 'Perform comprehensive security scan of the project', category: 'security', readOnly: true, inputSchema: { type: 'object', properties: { scanType: { type: 'string', enum: ['quick', 'comprehensive', 'deep'], default: 'comprehensive', description: 'Type of security scan to perform' }, includeFiles: { type: 'boolean', default: true, description: 'Scan source files for security issues' }, includeDependencies: { type: 'boolean', default: true, description: 'Check dependencies for vulnerabilities' }, includeSecrets: { type: 'boolean', default: true, description: 'Scan for exposed secrets and credentials' }, includePermissions: { type: 'boolean', default: true, description: 'Check file permissions and access controls' }, outputFormat: { type: 'string', enum: ['summary', 'detailed', 'json'], default: 'detailed', description: 'Format for scan results' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: SecurityScanInput, context: RequestContext) { try { if (!securityScanner) { const { ConfigManager } = await import('../../config/config-manager.js'); securityScanner = new SecurityScanner(new ConfigManager()); } const config: SecurityConfig = { scanType: input.scanType || 'comprehensive', includeFiles: input.includeFiles !== false, includeDependencies: input.includeDependencies !== false, includeSecrets: input.includeSecrets !== false, includePermissions: input.includePermissions !== false, outputFormat: input.outputFormat || 'detailed' }; const scanResult = await securityScanner.performScan(config); // Log security event await context.db.run( `INSERT INTO security_events (id, event_type, tool_name, user_id, context, risk_level, details, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), 'security_scan', 'security_scan', context.userId || 'system', JSON.stringify({ projectId: context.projectId }), scanResult.summary.status === 'fail' ? 'high' : 'low', JSON.stringify({ scanType: input.scanType, summary: scanResult.summary }), Date.now() ] ); return createSuccessResult({ scanResult, timestamp: new Date().toISOString() }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Security scan failed: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Check for secrets */ const checkSecretsTool = createTool<CheckSecretsInput, any>({ name: 'check_secrets', description: 'Scan for exposed secrets, API keys, and credentials', category: 'security', readOnly: true, inputSchema: { type: 'object', properties: { scanPath: { type: 'string', default: '.', description: 'Path to scan for secrets' }, excludePaths: { type: 'array', items: { type: 'string' }, default: ['.git', 'node_modules', '.atlas'], description: 'Paths to exclude from scanning' }, customPatterns: { type: 'array', items: { type: 'string' }, description: 'Custom regex patterns to detect secrets' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: CheckSecretsInput, context: RequestContext) { try { if (!securityScanner) { const { ConfigManager } = await import('../../config/config-manager.js'); securityScanner = new SecurityScanner(new ConfigManager()); } const secretsFound = await securityScanner.scanForSecrets({ scanPath: input.scanPath || '.', excludePaths: input.excludePaths || ['.git', 'node_modules', '.atlas'], customPatterns: input.customPatterns || [] }); // Log security event if secrets found if (secretsFound.length > 0) { await context.db.run( `INSERT INTO security_events (id, event_type, tool_name, user_id, context, risk_level, details, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), 'secrets_detected', 'check_secrets', context.userId || 'system', JSON.stringify({ projectId: context.projectId }), 'critical', JSON.stringify({ count: secretsFound.length, types: [...new Set(secretsFound.map((s: SecretFinding) => s.type))] }), Date.now() ] ); } return createSuccessResult({ secretsFound, scanCompleted: true }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Secrets scan failed: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Check vulnerabilities */ const vulnerabilityCheckTool = createTool<VulnerabilityCheckInput, any>({ name: 'vulnerability_check', description: 'Check dependencies for known vulnerabilities', category: 'security', readOnly: true, inputSchema: { type: 'object', properties: { checkNpm: { type: 'boolean', default: true, description: 'Check npm dependencies' }, checkYarn: { type: 'boolean', default: true, description: 'Check yarn dependencies' }, severity: { type: 'string', enum: ['low', 'moderate', 'high', 'critical'], default: 'moderate', description: 'Minimum severity level to report' }, autoFix: { type: 'boolean', default: false, description: 'Automatically fix vulnerabilities when possible' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: VulnerabilityCheckInput, context: RequestContext) { try { if (!securityScanner) { const { ConfigManager } = await import('../../config/config-manager.js'); securityScanner = new SecurityScanner(new ConfigManager()); } const vulnerabilities = await securityScanner.checkVulnerabilities({ checkNpm: input.checkNpm !== false, checkYarn: input.checkYarn !== false, severity: input.severity || 'moderate', autoFix: input.autoFix || false }); // Log security event if vulnerabilities found if (vulnerabilities.total > 0) { await context.db.run( `INSERT INTO security_events (id, event_type, tool_name, user_id, context, risk_level, details, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), 'vulnerabilities_detected', 'vulnerability_check', context.userId || 'system', JSON.stringify({ projectId: context.projectId }), vulnerabilities.critical > 0 ? 'critical' : 'high', JSON.stringify({ total: vulnerabilities.total, critical: vulnerabilities.critical, high: vulnerabilities.high, moderate: vulnerabilities.moderate, low: vulnerabilities.low }), Date.now() ] ); } return createSuccessResult({ vulnerabilities, scanCompleted: true }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Vulnerability check failed: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Setup security configuration */ const setupSecurityConfigTool = createTool<SetupSecurityConfigInput, any>({ name: 'setup_security_config', description: 'Configure comprehensive security tools and policies', category: 'security', requiresApproval: true, inputSchema: { type: 'object', properties: { enablePreCommitScans: { type: 'boolean', default: true, description: 'Enable security scans in pre-commit hooks' }, enableSecretDetection: { type: 'boolean', default: true, description: 'Enable secret detection in CI/CD' }, enableVulnerabilityChecks: { type: 'boolean', default: true, description: 'Enable dependency vulnerability checks' }, createSecurityPolicy: { type: 'boolean', default: true, description: 'Create SECURITY.md policy file' }, setupGitHooks: { type: 'boolean', default: true, description: 'Setup Git hooks for security checks' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: SetupSecurityConfigInput, context: RequestContext) { try { if (!securityScanner) { const { ConfigManager } = await import('../../config/config-manager.js'); securityScanner = new SecurityScanner(new ConfigManager()); } const setupResult = await securityScanner.setupSecurityConfiguration({ enablePreCommitScans: input.enablePreCommitScans !== false, enableSecretDetection: input.enableSecretDetection !== false, enableVulnerabilityChecks: input.enableVulnerabilityChecks !== false, createSecurityPolicy: input.createSecurityPolicy !== false, setupGitHooks: input.setupGitHooks !== false }); // Log security event await context.db.run( `INSERT INTO security_events (id, event_type, tool_name, user_id, context, risk_level, details, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), 'security_setup', 'setup_security_config', context.userId || 'system', JSON.stringify({ projectId: context.projectId }), 'medium', JSON.stringify(input), Date.now() ] ); return createSuccessResult({ setupResult, configuration: { enablePreCommitScans: input.enablePreCommitScans !== false, enableSecretDetection: input.enableSecretDetection !== false, enableVulnerabilityChecks: input.enableVulnerabilityChecks !== false, createSecurityPolicy: input.createSecurityPolicy !== false, setupGitHooks: input.setupGitHooks !== false } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Security setup failed: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Generate security report */ const generateSecurityReportTool = createTool<GenerateSecurityReportInput, any>({ name: 'generate_security_report', description: 'Generate comprehensive security report for the project', category: 'security', readOnly: true, inputSchema: { type: 'object', properties: { includeOverview: { type: 'boolean', default: true, description: 'Include security overview section' }, includeDetails: { type: 'boolean', default: true, description: 'Include detailed findings' }, includeRecommendations: { type: 'boolean', default: true, description: 'Include security recommendations' }, outputPath: { type: 'string', default: 'SECURITY.md', description: 'Path to save the security report' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: GenerateSecurityReportInput, context: RequestContext) { try { if (!securityScanner) { const { ConfigManager } = await import('../../config/config-manager.js'); securityScanner = new SecurityScanner(new ConfigManager()); } const report = await securityScanner.generateSecurityReport({ includeOverview: input.includeOverview !== false, includeDetails: input.includeDetails !== false, includeRecommendations: input.includeRecommendations !== false }); // Save report to file const fsModule = await import('fs'); const pathModule = await import('path'); await fsModule.promises.writeFile( pathModule.join(process.cwd(), input.outputPath || 'SECURITY.md'), report.markdown ); return createSuccessResult({ reportGenerated: true, outputPath: input.outputPath || 'SECURITY.md', summary: report.summary }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Report generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Check data access */ const checkDataAccessTool = createTool<CheckDataAccessInput, any>({ name: 'check_data_access', description: 'Monitor and detect improper access to .atlas/ directory', category: 'security', readOnly: true, inputSchema: { type: 'object', properties: { scanPath: { type: 'string', default: '.', description: 'Path to scan for data access violations' }, excludePaths: { type: 'array', items: { type: 'string' }, default: ['.git', 'node_modules', 'dist', 'build'], description: 'Paths to exclude from scanning' }, includeWarnings: { type: 'boolean', default: true, description: 'Include warnings for potential violations' }, includeSuggestions: { type: 'boolean', default: true, description: 'Include suggestions for proper MCP tool usage' } }, required: [], additionalProperties: false } as JSONSchema7, async execute(input: CheckDataAccessInput, context: RequestContext) { try { if (!securityScanner) { const { ConfigManager } = await import('../../config/config-manager.js'); securityScanner = new SecurityScanner(new ConfigManager()); } const accessViolations = await securityScanner.checkDataAccess({ scanPath: input.scanPath || '.', excludePaths: input.excludePaths || ['.git', 'node_modules', 'dist', 'build'], includeWarnings: input.includeWarnings !== false, includeSuggestions: input.includeSuggestions !== false }); // Log security event if violations found if (accessViolations.length > 0) { await context.db.run( `INSERT INTO security_events (id, event_type, tool_name, user_id, context, risk_level, details, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), 'data_access_violation', 'check_data_access', context.userId || 'system', JSON.stringify({ projectId: context.projectId }), 'high', JSON.stringify({ violationCount: accessViolations.length, types: [...new Set(accessViolations.map((v: any) => v.type))] }), Date.now() ] ); } return createSuccessResult({ violations: accessViolations, scanCompleted: true }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Data access check failed: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Setup security management tools */ export async function setupSecurityTools(): Promise<ToolRegistration> { return { module: 'security', tools: [ getSecurityStatusTool, getSecurityEventsTool, configureSecurityPolicyTool, processSecurityApprovalTool, securityScanTool, checkSecretsTool, vulnerabilityCheckTool, setupSecurityConfigTool, generateSecurityReportTool, checkDataAccessTool ] }; }