mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-04-05 09:03:07 +00:00
chore: add pre-built dist folder for npx usage
This commit is contained in:
committed by
Romuald Członkowski
parent
a70d96a373
commit
5057481e70
242
dist/telemetry/workflow-sanitizer.js
vendored
Normal file
242
dist/telemetry/workflow-sanitizer.js
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
"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 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 isSensitive = this.isSensitiveField(key);
|
||||
const isUrlField = key.toLowerCase().includes('url') ||
|
||||
key.toLowerCase().includes('endpoint') ||
|
||||
key.toLowerCase().includes('webhook');
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (isSensitive && !isUrlField) {
|
||||
sanitized[key] = '[REDACTED]';
|
||||
}
|
||||
else {
|
||||
sanitized[key] = this.sanitizeObject(value);
|
||||
}
|
||||
}
|
||||
else if (typeof value === 'string') {
|
||||
if (isSensitive && !isUrlField) {
|
||||
sanitized[key] = '[REDACTED]';
|
||||
}
|
||||
else {
|
||||
sanitized[key] = this.sanitizeString(value, key);
|
||||
}
|
||||
}
|
||||
else if (isSensitive) {
|
||||
sanitized[key] = '[REDACTED]';
|
||||
}
|
||||
else {
|
||||
sanitized[key] = value;
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
static sanitizeString(value, fieldName) {
|
||||
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 (sanitized.includes('[REDACTED')) {
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (fieldName.toLowerCase().includes('url') ||
|
||||
fieldName.toLowerCase().includes('endpoint')) {
|
||||
if (sanitized.startsWith('http://') || sanitized.startsWith('https://')) {
|
||||
if (sanitized.includes('[REDACTED_URL_WITH_AUTH]')) {
|
||||
return sanitized;
|
||||
}
|
||||
if (sanitized.includes('[REDACTED]')) {
|
||||
return sanitized;
|
||||
}
|
||||
const urlParts = sanitized.split('/');
|
||||
if (urlParts.length > 2) {
|
||||
urlParts[2] = '[domain]';
|
||||
sanitized = urlParts.join('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
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?:\/\/[^:]+:[^@]+@[^\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: /sk-[a-zA-Z0-9]{16,}/g, placeholder: '[REDACTED_APIKEY]' },
|
||||
{ pattern: /Bearer\s+[^\s]+/gi, placeholder: 'Bearer [REDACTED]', preservePrefix: true },
|
||||
{ pattern: /\b[a-zA-Z0-9_-]{32,}\b/g, placeholder: '[REDACTED_TOKEN]' },
|
||||
{ pattern: /\b[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
|
||||
Reference in New Issue
Block a user