UNPKG

n8n-mcp

Version:

Integration between n8n workflow automation and Model Context Protocol (MCP)

240 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkflowSanitizer = void 0; const crypto_1 = require("crypto"); class WorkflowSanitizer { static sanitizeWorkflow(workflow) { const sanitized = JSON.parse(JSON.stringify(workflow)); if (sanitized.nodes && Array.isArray(sanitized.nodes)) { sanitized.nodes = sanitized.nodes.map((node) => this.sanitizeNode(node)); } if (sanitized.connections) { sanitized.connections = this.sanitizeConnections(sanitized.connections); } delete sanitized.settings?.errorWorkflow; delete sanitized.staticData; delete sanitized.pinData; delete sanitized.credentials; delete sanitized.sharedWorkflows; delete sanitized.ownedBy; delete sanitized.createdBy; delete sanitized.updatedBy; const nodeTypes = sanitized.nodes?.map((n) => n.type) || []; const uniqueNodeTypes = [...new Set(nodeTypes)]; const hasTrigger = nodeTypes.some((type) => type.includes('trigger') || type.includes('webhook')); const hasWebhook = nodeTypes.some((type) => type.includes('webhook')); const nodeCount = sanitized.nodes?.length || 0; let complexity = 'simple'; if (nodeCount > 20) { complexity = 'complex'; } else if (nodeCount > 10) { complexity = 'medium'; } const workflowStructure = JSON.stringify({ nodeTypes: uniqueNodeTypes.sort(), connections: sanitized.connections }); const workflowHash = (0, crypto_1.createHash)('sha256') .update(workflowStructure) .digest('hex') .substring(0, 16); return { nodes: sanitized.nodes || [], connections: sanitized.connections || {}, nodeCount, nodeTypes: uniqueNodeTypes, hasTrigger, hasWebhook, complexity, workflowHash }; } static sanitizeTelemetryObject(value) { if (value === null || value === undefined) { return value; } if (typeof value === 'string') { return this.sanitizeString(value); } return this.sanitizeObject(value); } static sanitizeNode(node) { const sanitized = { ...node }; delete sanitized.credentials; if (sanitized.parameters) { sanitized.parameters = this.sanitizeObject(sanitized.parameters); } return sanitized; } static sanitizeObject(obj) { if (!obj || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map(item => this.sanitizeObject(item)); } const sanitized = {}; for (const [key, value] of Object.entries(obj)) { const lowerKey = key.toLowerCase(); const isSensitive = this.isSensitiveField(key); const isUrlField = lowerKey.includes('url') || lowerKey.includes('endpoint') || lowerKey.includes('webhook'); if (isSensitive) { sanitized[key] = isUrlField ? '[REDACTED_URL]' : '[REDACTED]'; } else if (typeof value === 'object' && value !== null) { sanitized[key] = this.sanitizeObject(value); } else if (typeof value === 'string') { sanitized[key] = this.sanitizeString(value); } else { sanitized[key] = value; } } return sanitized; } static sanitizeString(value) { if (value.includes('/webhook/') || value.includes('/hook/')) { return 'https://[webhook-url]'; } let sanitized = value; for (const patternDef of this.SENSITIVE_PATTERNS) { if (patternDef.placeholder.includes('WEBHOOK')) { continue; } if (patternDef.placeholder === '[REDACTED_URL_WITH_AUTH]') { const matches = value.match(patternDef.pattern); if (matches) { for (const match of matches) { const fullUrlMatch = value.indexOf(match); if (fullUrlMatch !== -1) { const afterUrl = value.substring(fullUrlMatch + match.length); if (afterUrl && afterUrl.startsWith('/')) { const pathPart = afterUrl.split(/[\s?&#]/)[0]; sanitized = sanitized.replace(match + pathPart, patternDef.placeholder + pathPart); } else { sanitized = sanitized.replace(match, patternDef.placeholder); } } } } continue; } sanitized = sanitized.replace(patternDef.pattern, patternDef.placeholder); } return sanitized; } static isSensitiveField(fieldName) { const lowerFieldName = fieldName.toLowerCase(); return this.SENSITIVE_FIELDS.some(sensitive => lowerFieldName.includes(sensitive.toLowerCase())); } static sanitizeConnections(connections) { if (!connections || typeof connections !== 'object') { return connections; } const sanitized = {}; for (const [nodeId, nodeConnections] of Object.entries(connections)) { if (typeof nodeConnections === 'object' && nodeConnections !== null) { sanitized[nodeId] = {}; for (const [connType, connArray] of Object.entries(nodeConnections)) { if (Array.isArray(connArray)) { sanitized[nodeId][connType] = connArray.map((conns) => { if (Array.isArray(conns)) { return conns.map((conn) => ({ node: conn.node, type: conn.type, index: conn.index })); } return conns; }); } else { sanitized[nodeId][connType] = connArray; } } } else { sanitized[nodeId] = nodeConnections; } } return sanitized; } static generateWorkflowHash(workflow) { const sanitized = this.sanitizeWorkflow(workflow); return sanitized.workflowHash; } static sanitizeWorkflowRaw(workflow) { const sanitized = JSON.parse(JSON.stringify(workflow)); if (sanitized.nodes && Array.isArray(sanitized.nodes)) { sanitized.nodes = sanitized.nodes.map((node) => this.sanitizeNode(node)); } if (sanitized.connections) { sanitized.connections = this.sanitizeConnections(sanitized.connections); } delete sanitized.settings?.errorWorkflow; delete sanitized.staticData; delete sanitized.pinData; delete sanitized.credentials; delete sanitized.sharedWorkflows; delete sanitized.ownedBy; delete sanitized.createdBy; delete sanitized.updatedBy; return sanitized; } } exports.WorkflowSanitizer = WorkflowSanitizer; WorkflowSanitizer.SENSITIVE_PATTERNS = [ { pattern: /https?:\/\/[^\s/]+\/webhook\/[^\s]+/g, placeholder: '[REDACTED_WEBHOOK]' }, { pattern: /https?:\/\/[^\s/]+\/hook\/[^\s]+/g, placeholder: '[REDACTED_WEBHOOK]' }, { pattern: /https?:\/\/n8n\.[A-Za-z0-9.-]+\.[A-Za-z]{2,}(?:[/?#][^\s"'<>]*)?/gi, placeholder: '[REDACTED_N8N_HOST_URL]' }, { pattern: /https?:\/\/[a-z]{20}\.supabase\.co(?:[/?#][^\s"'<>]*)?/gi, placeholder: '[REDACTED_SUPABASE_URL]' }, { pattern: /https?:\/\/[^:]+:[^@]+@[^\s/]+/g, placeholder: '[REDACTED_URL_WITH_AUTH]' }, { pattern: /wss?:\/\/[^:]+:[^@]+@[^\s/]+/g, placeholder: '[REDACTED_URL_WITH_AUTH]' }, { pattern: /(?:postgres|mysql|mongodb|redis):\/\/[^:]+:[^@]+@[^\s]+/g, placeholder: '[REDACTED_URL_WITH_AUTH]' }, { pattern: /Bearer\s+[^\s'"`,;}\]]+/gi, placeholder: 'Bearer [REDACTED]' }, { pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, placeholder: '[REDACTED_JWT]' }, { pattern: /\bsb_(?:secret|publishable)_[A-Za-z0-9_-]{20,}\b/g, placeholder: '[REDACTED_SUPABASE_KEY]' }, { pattern: /\bsk-proj-[A-Za-z0-9_-]{40,}\b/g, placeholder: '[REDACTED_LLM_API_KEY]' }, { pattern: /\bsk-or-(?:v1-)?[A-Za-z0-9-]{40,}\b/g, placeholder: '[REDACTED_LLM_API_KEY]' }, { pattern: /\b(?:sk|rk)_(?:test|live)_[A-Za-z0-9]{24,}\b/g, placeholder: '[REDACTED_STRIPE_KEY]' }, { pattern: /\bgithub_pat_[A-Za-z0-9_]{50,}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bghp_[A-Za-z0-9]{36,}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bhf_[A-Za-z0-9]{30,}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bntn_[A-Za-z0-9]{40,}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bpit-[a-f0-9-]{36}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bxox[bpaors]-[A-Za-z0-9-]{10,}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bAKIA[A-Z0-9]{16}\b/g, placeholder: '[REDACTED_API_TOKEN]' }, { pattern: /\bsk-[A-Za-z0-9]{16,}\b/g, placeholder: '[REDACTED_LLM_API_KEY]' }, { pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, placeholder: '[REDACTED_EMAIL]' }, { pattern: /(?<![\d-])(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?![\d-])/g, placeholder: '[REDACTED_PHONE]' }, { pattern: /\b(?!REDACTED)[A-Za-z0-9_-]{32,}\b/g, placeholder: '[REDACTED_TOKEN]' }, { pattern: /\b(?!REDACTED)[A-Za-z0-9_-]{20,31}\b/g, placeholder: '[REDACTED]' }, ]; WorkflowSanitizer.SENSITIVE_FIELDS = [ 'apiKey', 'api_key', 'token', 'secret', 'password', 'credential', 'auth', 'authorization', 'webhook', 'webhookUrl', 'url', 'endpoint', 'host', 'server', 'database', 'connectionString', 'privateKey', 'publicKey', 'certificate', ]; //# sourceMappingURL=workflow-sanitizer.js.map