@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
907 lines (845 loc) • 27.2 kB
text/typescript
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
]
};
}