UNPKG

logstack-zee

Version:

Complete Node.js logging solution with 6 integration methods, S3 bidirectional operations, advanced analytics, and multi-cloud storage support for enterprise-scale applications.

290 lines (241 loc) 8.16 kB
import { Config } from '../types/config'; // Sensitive data patterns for masking const SENSITIVE_PATTERNS = { // Password fields password: /password/i, passwd: /passwd/i, pwd: /pwd/i, // Email addresses email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Credit card numbers (various formats) creditCard: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g, // Phone numbers (US and international formats) phone: /\b(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})\b/g, // Social Security Numbers ssn: /\b\d{3}-?\d{2}-?\d{4}\b/g, // API keys and tokens (common patterns) apiKey: /\b[A-Za-z0-9]{32,}\b/g, jwt: /\beyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*\b/g, // IP addresses (when configured as sensitive) ipAddress: /\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/g, // Database connection strings connectionString: /(mongodb|postgresql|mysql|redis):\/\/[^\/\s]+/gi }; // Field names that should be masked (case insensitive) const SENSITIVE_FIELD_NAMES = [ 'password', 'passwd', 'pwd', 'secret', 'token', 'key', 'auth', 'credit_card', 'creditcard', 'ccnum', 'card_number', 'ssn', 'social_security', 'social_security_number', 'phone', 'phone_number', 'mobile', 'telephone', 'email', 'email_address', 'mail', 'api_key', 'apikey', 'access_key', 'secret_key', 'jwt', 'bearer', 'authorization', 'pin', 'cvv', 'cvc', 'security_code' ]; export interface MaskingConfig { enabled?: boolean; // Enable data masking (default: true) maskingChar?: string; // Character to use for masking (default: '*') preserveLength?: boolean; // Preserve original field length (default: false) showLastChars?: number; // Show last N characters (default: 0) customPatterns?: { [key: string]: RegExp }; // Custom patterns to mask customFields?: string[]; // Custom field names to mask exemptFields?: string[]; // Fields to exclude from masking maskEmails?: boolean; // Mask email addresses (default: true) maskIPs?: boolean; // Mask IP addresses (default: false) maskConnectionStrings?: boolean; // Mask database connection strings (default: true) } /** * Mask sensitive data in API logs and other data */ export function maskSensitiveData(data: any, config?: MaskingConfig): any { const maskingConfig: Required<MaskingConfig> = { enabled: true, maskingChar: '*', preserveLength: false, showLastChars: 0, customPatterns: {}, customFields: [], exemptFields: [], maskEmails: true, maskIPs: false, maskConnectionStrings: true, ...config }; if (!maskingConfig.enabled) { return data; } if (typeof data === 'string') { return maskString(data, maskingConfig); } if (Array.isArray(data)) { return data.map(item => maskSensitiveData(item, config)); } if (typeof data === 'object' && data !== null) { return maskObject(data, maskingConfig); } return data; } /** * Mask sensitive patterns in a string */ function maskString(str: string, config: Required<MaskingConfig>): string { let maskedStr = str; // Apply built-in patterns if (config.maskEmails) { maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.email, (match) => maskValue(match, config) ); } if (config.maskIPs) { maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.ipAddress, (match) => maskValue(match, config) ); } if (config.maskConnectionStrings) { maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.connectionString, (match) => maskValue(match, config) ); } // Apply credit card masking maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.creditCard, (match) => maskValue(match, config) ); // Apply phone number masking maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.phone, (match) => maskValue(match, config) ); // Apply SSN masking maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.ssn, (match) => maskValue(match, config) ); // Apply JWT token masking maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.jwt, (match) => maskValue(match, config) ); // Apply API key masking maskedStr = maskedStr.replace(SENSITIVE_PATTERNS.apiKey, (match) => maskValue(match, config) ); // Apply custom patterns Object.entries(config.customPatterns).forEach(([name, pattern]) => { maskedStr = maskedStr.replace(pattern, (match) => maskValue(match, config) ); }); return maskedStr; } /** * Mask sensitive fields in an object */ function maskObject(obj: any, config: Required<MaskingConfig>): any { const masked: any = {}; for (const [key, value] of Object.entries(obj)) { const lowerKey = key.toLowerCase(); // Check if field should be exempted if (config.exemptFields.some(field => lowerKey.includes(field.toLowerCase()) )) { masked[key] = value; continue; } // Check if field is sensitive const isSensitiveField = SENSITIVE_FIELD_NAMES.some(fieldName => lowerKey.includes(fieldName.toLowerCase()) ) || config.customFields.some(fieldName => lowerKey.includes(fieldName.toLowerCase()) ); if (isSensitiveField) { masked[key] = typeof value === 'string' ? maskValue(value, config) : '[MASKED]'; } else { // Recursively mask nested objects and arrays masked[key] = maskSensitiveData(value, config); } } return masked; } /** * Mask a single value */ function maskValue(value: string, config: Required<MaskingConfig>): string { if (!value) return value; const { maskingChar, preserveLength, showLastChars } = config; if (preserveLength) { if (showLastChars > 0 && value.length > showLastChars) { const maskedPart = maskingChar.repeat(value.length - showLastChars); const visiblePart = value.slice(-showLastChars); return maskedPart + visiblePart; } return maskingChar.repeat(value.length); } if (showLastChars > 0 && value.length > showLastChars) { const visiblePart = value.slice(-showLastChars); return '[MASKED]' + visiblePart; } return '[MASKED]'; } /** * Create a masking configuration for different environments */ export function createMaskingConfig(environment: 'development' | 'staging' | 'production'): MaskingConfig { const baseConfig: MaskingConfig = { enabled: true, maskingChar: '*', preserveLength: false, showLastChars: 0, maskEmails: true, maskIPs: false, maskConnectionStrings: true }; switch (environment) { case 'development': return { ...baseConfig, enabled: false, // Disable masking in development for easier debugging maskIPs: false }; case 'staging': return { ...baseConfig, enabled: true, showLastChars: 4, // Show last 4 characters for debugging maskIPs: false }; case 'production': return { ...baseConfig, enabled: true, showLastChars: 0, // Full masking in production maskIPs: true, // Mask IPs in production for privacy preserveLength: false }; default: return baseConfig; } } /** * Validate masking configuration */ export function validateMaskingConfig(config: MaskingConfig): { isValid: boolean; errors: string[] } { const errors: string[] = []; if (config.showLastChars && config.showLastChars < 0) { errors.push('showLastChars must be non-negative'); } if (config.maskingChar && config.maskingChar.length !== 1) { errors.push('maskingChar must be a single character'); } if (config.customPatterns) { Object.entries(config.customPatterns).forEach(([name, pattern]) => { if (!(pattern instanceof RegExp)) { errors.push(`Custom pattern '${name}' must be a RegExp`); } }); } return { isValid: errors.length === 0, errors }; }