UNPKG

okta-mcp-server

Version:

Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching

204 lines 6.42 kB
/** * PII (Personally Identifiable Information) masking utilities */ import { createHash } from 'crypto'; export class PIIMasker { config; salt; constructor(config, salt = process.env['AUDIT_SALT'] || 'default-salt') { this.config = config; this.salt = salt; } /** * Mask sensitive data in an object */ maskObject(obj) { if (!obj || typeof obj !== 'object') { return obj; } // Handle arrays if (Array.isArray(obj)) { return obj.map((item) => this.maskObject(item)); } // Deep clone to avoid mutating original const masked = JSON.parse(JSON.stringify(obj)); // Apply field-based masking for (const field of this.config.fields) { this.maskField(masked, field); } // Apply pattern-based masking this.applyPatternMasking(masked); return masked; } /** * Mask a specific field in an object */ maskField(obj, fieldPath) { const parts = fieldPath.split('.'); let current = obj; for (let i = 0; i < parts.length - 1; i++) { if (!current[parts[i]]) return; current = current[parts[i]]; } const fieldName = parts[parts.length - 1]; if (fieldName in current) { current[fieldName] = this.maskValue(current[fieldName], fieldName); } } /** * Apply pattern-based masking recursively */ applyPatternMasking(obj) { for (const key in obj) { if (!obj.hasOwnProperty(key)) continue; const value = obj[key]; if (typeof value === 'string') { obj[key] = this.maskStringByPatterns(value); } else if (typeof value === 'object' && value !== null) { this.applyPatternMasking(value); } } } /** * Mask a string value based on patterns */ maskStringByPatterns(value) { let masked = value; for (const pattern of this.config.patterns) { masked = masked.replace(pattern, (match) => { return this.maskValue(match, 'pattern'); }); } return masked; } /** * Mask a single value based on strategy */ maskValue(value, context) { if (value === null || value === undefined) { return value; } const stringValue = String(value); switch (this.config.maskingStrategy) { case 'hash': return this.hashValue(stringValue); case 'partial': return this.partialMask(stringValue, context); case 'remove': return '[REDACTED]'; case 'encrypt': // In production, use proper encryption return `[ENCRYPTED:${Buffer.from(stringValue).toString('base64')}]`; default: return '[MASKED]'; } } /** * Hash a value with salt */ hashValue(value) { const hash = createHash('sha256'); hash.update(this.salt + value); return `[HASH:${hash.digest('hex').substring(0, 16)}]`; } /** * Partially mask a value based on context */ partialMask(value, context) { // Email: show first letter and domain if (context === 'email' || value.includes('@')) { const [local, domain] = value.split('@'); if (domain) { return `${local[0]}***@${domain}`; } } // Phone: show area code only if (context === 'phone' || /^\+?\d{10,}$/.test(value)) { return value.substring(0, 3) + '*'.repeat(value.length - 3); } // SSN: show last 4 digits if (context === 'ssn' || /^\d{3}-?\d{2}-?\d{4}$/.test(value)) { const digits = value.replace(/\D/g, ''); return `***-**-${digits.substring(5)}`; } // Credit card: show last 4 digits if (context === 'creditCard' || /^\d{13,19}$/.test(value.replace(/\s/g, ''))) { const digits = value.replace(/\D/g, ''); return '*'.repeat(digits.length - 4) + digits.substring(digits.length - 4); } // Default: show first and last character if (value.length > 2) { return value[0] + '*'.repeat(value.length - 2) + value[value.length - 1]; } return '*'.repeat(value.length); } /** * Get default PII patterns */ static getDefaultPatterns() { return [ // Email addresses /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Phone numbers (various formats) /\b(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})\b/g, // SSN /\b\d{3}-\d{2}-\d{4}\b/g, // Credit card numbers /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, // IP addresses /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g, // API keys (generic pattern) /\b[A-Za-z0-9]{32,}\b/g, ]; } /** * Get default fields to mask */ static getDefaultFields() { return [ 'password', 'secret', 'token', 'apiKey', 'apiToken', 'accessToken', 'refreshToken', 'privateKey', 'ssn', 'socialSecurityNumber', 'creditCard', 'creditCardNumber', 'cvv', 'pin', 'email', 'emailAddress', 'phone', 'phoneNumber', 'mobile', 'mobileNumber', 'address', 'streetAddress', 'profile.email', 'profile.phone', 'profile.mobile', 'profile.ssn', 'credentials.password', 'credentials.token', ]; } } /** * Create a default PII masker instance */ export function createDefaultPIIMasker() { return new PIIMasker({ fields: PIIMasker.getDefaultFields(), patterns: PIIMasker.getDefaultPatterns(), maskingStrategy: 'partial', preserveFormat: true, }); } //# sourceMappingURL=pii-masker.js.map