UNPKG

glassbox-ai

Version:

Enterprise-grade AI testing framework with reliability, observability, and comprehensive validation

197 lines (173 loc) 4.99 kB
/** * PII Detector for sensitive information * Detects SSNs, emails, phone numbers, credit cards, names, and addresses */ // Regex patterns for different PII types const PATTERNS = { ssn: /(\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b)/g, email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, phone: /(\b\d{3}[-.]?\d{3}[-.]?\d{4}\b|\b\(\d{3}\)\s*\d{3}[-.]?\d{4}\b|\b\d{10}\b)/g, creditCard: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, name: /\b[A-Z][a-z]+\s+[A-Z][a-z]+\b/g, address: /\b\d+\s+[A-Za-z\s]+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Court|Ct|Place|Pl|Way|Circle|Cir)\b/gi }; // Credit card issuer patterns const CREDIT_CARD_ISSUERS = { visa: /^4/, mastercard: /^5[1-5]/, amex: /^3[47]/, discover: /^6(?:011|5)/ }; /** * Luhn algorithm for credit card validation * @param {string} cardNumber * @returns {boolean} */ function luhnCheck(cardNumber) { const digits = cardNumber.replace(/\D/g, '').split('').map(Number); let sum = 0; let isEven = false; for (let i = digits.length - 1; i >= 0; i--) { let digit = digits[i]; if (isEven) { digit *= 2; if (digit > 9) { digit -= 9; } } sum += digit; isEven = !isEven; } return sum % 10 === 0; } /** * Determine credit card issuer * @param {string} cardNumber * @returns {string} */ function getCreditCardIssuer(cardNumber) { const cleanNumber = cardNumber.replace(/\D/g, ''); for (const [issuer, pattern] of Object.entries(CREDIT_CARD_ISSUERS)) { if (pattern.test(cleanNumber)) { return issuer; } } return 'unknown'; } /** * Calculate risk level based on PII type and context * @param {string} piiType * @param {string} value * @returns {string} */ function calculateRiskLevel(piiType, value) { const riskLevels = { ssn: 'HIGH', creditCard: 'HIGH', email: 'MEDIUM', phone: 'MEDIUM', name: 'LOW', address: 'LOW' }; return riskLevels[piiType] || 'MEDIUM'; } /** * Mask sensitive information for logging * @param {string} value * @param {string} type * @returns {string} */ function maskPII(value, type) { switch (type) { case 'ssn': return value.replace(/(\d{3})-(\d{2})-(\d{4})/, 'XXX-XX-$3'); case 'creditCard': return value.replace(/(\d{4})[- ]?(\d{4})[- ]?(\d{4})[- ]?(\d{4})/, '****-****-****-$4'); case 'phone': return value.replace(/(\d{3})[-.]?(\d{3})[-.]?(\d{4})/, '($1) ***-$3'); case 'email': const [local, domain] = value.split('@'); return `${local.charAt(0)}***@${domain}`; default: return value; } } /** * Main PII detection function * @param {string} text * @returns {object} PII detection results */ export function detectPII(text) { const results = { found: [], riskLevel: 'LOW', totalCount: 0, summary: {} }; const summary = {}; // Check each PII type for (const [type, pattern] of Object.entries(PATTERNS)) { const matches = text.match(pattern); if (matches) { const uniqueMatches = [...new Set(matches)]; const riskLevel = calculateRiskLevel(type, uniqueMatches[0]); const piiItems = uniqueMatches.map(match => ({ type, value: match, masked: maskPII(match, type), riskLevel })); results.found.push(...piiItems); summary[type] = { count: uniqueMatches.length, riskLevel, examples: piiItems.slice(0, 3).map(item => item.masked) }; } } // Special handling for credit cards (Luhn validation) const creditCardMatches = results.found.filter(item => item.type === 'creditCard'); for (const item of creditCardMatches) { const isValid = luhnCheck(item.value); if (isValid) { item.issuer = getCreditCardIssuer(item.value); item.valid = true; } else { item.valid = false; item.riskLevel = 'LOW'; // Invalid cards are less risky } } // Calculate overall risk level const riskScores = { HIGH: 3, MEDIUM: 2, LOW: 1 }; const totalRisk = results.found.reduce((sum, item) => sum + riskScores[item.riskLevel], 0); const avgRisk = results.found.length > 0 ? totalRisk / results.found.length : 1; if (avgRisk >= 2.5) results.riskLevel = 'HIGH'; else if (avgRisk >= 1.5) results.riskLevel = 'MEDIUM'; else results.riskLevel = 'LOW'; results.totalCount = results.found.length; results.summary = summary; return results; } /** * Check if text contains any PII * @param {string} text * @returns {boolean} */ export function hasPII(text) { const results = detectPII(text); return results.totalCount > 0; } /** * Get PII risk assessment * @param {string} text * @returns {object} */ export function assessPIIRisk(text) { const results = detectPII(text); return { hasPII: results.totalCount > 0, riskLevel: results.riskLevel, piiTypes: Object.keys(results.summary), totalItems: results.totalCount }; }