"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