UNPKG

knowledgegraph-mcp

Version:

MCP server for enabling persistent knowledge storage for Claude through a knowledge graph with multiple storage backends

244 lines 8.97 kB
/** * File attachment manager for the knowledge graph system */ import * as crypto from 'crypto'; import { DEFAULT_FILE_STORAGE_CONFIG } from '../types/file-attachments.js'; /** * File validator for security and type checking */ export class FileValidator { allowedMimeTypes; maxFileSize; constructor(config = DEFAULT_FILE_STORAGE_CONFIG) { this.allowedMimeTypes = new Set(config.allowedMimeTypes); this.maxFileSize = config.maxFileSize; } /** * Validate a file buffer and metadata */ async validateFile(file, originalFilename, providedMimeType) { try { // 1. Size validation if (file.length > this.maxFileSize) { return { isValid: false, mimeType: providedMimeType || 'application/octet-stream', hash: '', error: `File too large: ${file.length} bytes (max: ${this.maxFileSize})` }; } // 2. MIME type detection and validation const detectedMimeType = providedMimeType || this.detectMimeType(file, originalFilename); if (!this.allowedMimeTypes.has(detectedMimeType)) { return { isValid: false, mimeType: detectedMimeType, hash: '', error: `File type not allowed: ${detectedMimeType}` }; } // 3. Basic security checks const securityCheck = this.performSecurityChecks(file, originalFilename); if (!securityCheck.isValid) { return { isValid: false, mimeType: detectedMimeType, hash: '', error: securityCheck.error }; } // 4. Generate integrity hash const hash = crypto.createHash('sha256').update(file).digest('hex'); return { isValid: true, mimeType: detectedMimeType, hash }; } catch (error) { return { isValid: false, mimeType: providedMimeType || 'application/octet-stream', hash: '', error: `Validation failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Simple MIME type detection based on file extension and content */ detectMimeType(file, filename) { const ext = filename.toLowerCase().split('.').pop() || ''; // Check file signature (magic numbers) for common types if (file.length >= 4) { const signature = file.subarray(0, 4); // PDF if (signature.toString('ascii', 0, 4) === '%PDF') { return 'application/pdf'; } // PNG if (signature[0] === 0x89 && signature[1] === 0x50 && signature[2] === 0x4E && signature[3] === 0x47) { return 'image/png'; } // JPEG if (signature[0] === 0xFF && signature[1] === 0xD8) { return 'image/jpeg'; } // ZIP/Office documents if (signature[0] === 0x50 && signature[1] === 0x4B) { if (ext === 'docx') return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; if (ext === 'xlsx') return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; if (ext === 'pptx') return 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; return 'application/zip'; } } // Fallback to extension-based detection const mimeTypeMap = { 'txt': 'text/plain', 'md': 'text/markdown', 'json': 'application/json', 'xml': 'application/xml', 'html': 'text/html', 'css': 'text/css', 'js': 'text/javascript', 'ts': 'application/typescript', 'py': 'application/x-python', 'sh': 'application/x-sh', 'yaml': 'application/x-yaml', 'yml': 'application/x-yaml', 'csv': 'text/csv', 'pdf': 'application/pdf', 'doc': 'application/msword', 'xls': 'application/vnd.ms-excel', 'ppt': 'application/vnd.ms-powerpoint', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml', 'zip': 'application/zip', 'tar': 'application/x-tar', 'gz': 'application/gzip', }; return mimeTypeMap[ext] || 'application/octet-stream'; } /** * Perform basic security checks on the file */ performSecurityChecks(file, filename) { // Check for potentially dangerous file extensions const dangerousExtensions = ['.exe', '.bat', '.cmd', '.com', '.scr', '.pif', '.vbs', '.js', '.jar']; const ext = filename.toLowerCase(); for (const dangerous of dangerousExtensions) { if (ext.endsWith(dangerous)) { return { isValid: false, error: `Potentially dangerous file type: ${dangerous}` }; } } // Check for embedded executables in common file types if (file.length >= 2) { // Check for MZ header (Windows executable) if (file[0] === 0x4D && file[1] === 0x5A) { return { isValid: false, error: 'File contains executable code' }; } } // Check for script injection in text files if (filename.toLowerCase().endsWith('.txt') || filename.toLowerCase().endsWith('.md')) { const content = file.toString('utf8', 0, Math.min(file.length, 1024)); if (content.includes('<script') || content.includes('javascript:')) { return { isValid: false, error: 'Text file contains potentially malicious script content' }; } } return { isValid: true }; } } /** * File attachment manager */ export class FileAttachmentManager { validator; config; constructor(config = DEFAULT_FILE_STORAGE_CONFIG) { this.config = config; this.validator = new FileValidator(config); } /** * Process and validate a file upload */ async processFileUpload(file, entityName, project) { try { // Validate the file const validation = await this.validator.validateFile(file.buffer, file.originalname, file.mimetype); if (!validation.isValid) { return { error: validation.error || 'File validation failed' }; } // Generate unique file ID const fileId = crypto.randomUUID(); // Create file attachment metadata const fileAttachment = { fileId, originalFilename: file.originalname, mimeType: validation.mimeType, fileSize: file.buffer.length, fileHash: validation.hash, uploadDate: new Date(), metadata: { entityName, project, uploadedAt: new Date().toISOString() } }; return { fileAttachment, fileBuffer: file.buffer }; } catch (error) { return { error: `Failed to process file upload: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Validate file count limits for an entity */ validateFileCount(currentAttachments) { if (currentAttachments.length >= this.config.maxFilesPerEntity) { return { isValid: false, error: `Maximum number of files per entity exceeded (${this.config.maxFilesPerEntity})` }; } return { isValid: true }; } /** * Calculate total storage used by an entity */ calculateEntityStorageUsage(attachments) { return attachments.reduce((total, attachment) => total + attachment.fileSize, 0); } /** * Generate file metadata for storage */ createFileMetadata(entityName, project, originalFilename, mimeType) { return { entityName, project, originalFilename, mimeType, metadata: { uploadedAt: new Date().toISOString() } }; } } //# sourceMappingURL=file-manager.js.map