UNPKG

autotel

Version:
349 lines (347 loc) 11.7 kB
'use strict'; // src/attribute-redacting-processor.ts var REDACTOR_PATTERNS = { // Value patterns (match content in attribute values) email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/gi, phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, ssn: /\b\d{3}[-]?\d{2}[-]?\d{4}\b/g, creditCard: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, bearerToken: /Bearer\s+[A-Za-z0-9._~+/=-]+/gi, apiKeyInValue: /(?:api[_-]?key|apikey|api_secret)[=:][\s"']*[A-Za-z0-9_-]+/gi, jwt: /eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*/g, // Key patterns (match attribute names - redacts entire value) sensitiveKey: /^(password|passwd|pwd|secret|token|api[_-]?key|auth|credential|private[_-]?key|authorization)$/i }; var builtinPatterns = { /** Credit card numbers → ****1111 (PCI DSS: last 4 allowed) */ creditCard: { pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, mask: (m) => `****${m.replace(/[\s-]/g, "").slice(-4)}` }, /** Email addresses → a***@***.com */ email: { pattern: /[\w.+-]+@[\w-]+\.[\w.]+/g, mask: (m) => { const at = m.indexOf("@"); if (at < 1) return "***@***"; const tld = m.slice(m.lastIndexOf(".")); return `${m[0]}***@***${tld}`; } }, /** IPv4 addresses → ***.***.***.100 (last octet only) */ ipv4: { pattern: /\b(?!0\.0\.0\.0\b)(?!127\.0\.0\.1\b)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, mask: (m) => `***.***.***.${m.split(".").pop()}` }, /** International phone numbers → +33******78 (country code + last 2 digits) */ phone: { pattern: /(?:\+\d{1,3}[\s.-]?)?\(?\d{1,4}\)?[\s.-]?\d{2,4}[\s.-]?\d{2,4}[\s.-]?\d{2,4}\b/g, mask: (m) => { const digits = m.replace(/[^\d]/g, ""); const hasPlus = m.startsWith("+"); if (hasPlus && digits.length > 4) { const ccMatch = m.match(/^\+\d{1,3}/); const cc = ccMatch ? ccMatch[0] : "+"; return `${cc}******${digits.slice(-2)}`; } if (digits.length > 2) { return `${"*".repeat(digits.length - 2)}${digits.slice(-2)}`; } return "***"; } }, /** JWT tokens → eyJ***.*** */ jwt: { pattern: /\beyJ[\w-]*\.[\w-]*\.[\w-]*\b/g, mask: () => "eyJ***.***" }, /** Bearer tokens → Bearer *** */ bearer: { pattern: /\bBearer\s+[\w\-.~+/]{8,}=*/gi, mask: () => "Bearer ***" }, /** IBAN → FR76****189 (country + check digits + last 3) */ iban: { pattern: /\b[A-Z]{2}\d{2}[\s-]?[\dA-Z]{4}[\s-]?[\dA-Z]{4}[\s-]?[\dA-Z]{4}[\s-]?[\dA-Z]{0,4}[\s-]?[\dA-Z]{0,4}[\s-]?[\dA-Z]{0,4}\b/g, mask: (m) => { const clean = m.replace(/[\s-]/g, ""); return `${clean.slice(0, 4)}****${clean.slice(-3)}`; } } }; function cloneRegex(re) { return new RegExp(re.source, re.flags); } function isPlainObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } function toRegExp(value) { if (value instanceof RegExp) return value; if (typeof value === "string") return new RegExp(value, "g"); if (isPlainObject(value) && typeof value.source === "string") { const flags = typeof value.flags === "string" ? value.flags : "g"; return new RegExp(value.source, flags); } return void 0; } function toRegExpArray(value) { if (!Array.isArray(value)) return void 0; const out = []; for (const item of value) { const re = toRegExp(item); if (re) out.push(re); } return out.length > 0 ? out : []; } function builtinToValuePattern(name) { const b = builtinPatterns[name]; return { name, pattern: cloneRegex(b.pattern), mask: b.mask }; } var DEFAULT_VALUE_PATTERNS = [ builtinToValuePattern("email"), builtinToValuePattern("phone"), { name: "ssn", pattern: REDACTOR_PATTERNS.ssn }, builtinToValuePattern("creditCard") ]; var REDACTOR_PRESETS = { /** * Default preset - covers common PII patterns with smart masking * Detects: emails (a***@***.com), phone numbers, SSNs, credit cards (****1111) * Redacts keys: password, secret, token, apiKey, auth, credential */ default: { keyPatterns: [REDACTOR_PATTERNS.sensitiveKey], valuePatterns: DEFAULT_VALUE_PATTERNS, builtins: true, replacement: "[REDACTED]" }, /** * Strict preset - more aggressive redaction for high-security environments * Includes everything in default plus: Bearer tokens, JWTs, IBAN, API keys in values */ strict: { keyPatterns: [REDACTOR_PATTERNS.sensitiveKey, /bearer/i, /jwt/i], valuePatterns: [ ...DEFAULT_VALUE_PATTERNS, builtinToValuePattern("jwt"), builtinToValuePattern("bearer"), builtinToValuePattern("iban"), { name: "apiKeyInValue", pattern: REDACTOR_PATTERNS.apiKeyInValue } ], builtins: true, replacement: "[REDACTED]" }, /** * PCI-DSS preset - focused on payment card industry compliance * Redacts: credit card numbers (****1111), CVV-like patterns, card-related keys */ "pci-dss": { keyPatterns: [/card/i, /cvv/i, /cvc/i, /pan/i, /expir/i, /ccn/i], valuePatterns: [builtinToValuePattern("creditCard")], builtins: ["creditCard"], replacement: "[REDACTED]" } }; function normalizeAttributeRedactorConfig(raw) { if (raw === void 0 || raw === null) return void 0; if (typeof raw === "string") return raw; if (!isPlainObject(raw)) return void 0; const config = {}; if (Array.isArray(raw.paths)) { config.paths = raw.paths.filter( (value) => typeof value === "string" ); } if (typeof raw.replacement === "string") { config.replacement = raw.replacement; } if (typeof raw.builtins === "boolean") { config.builtins = raw.builtins; } else if (Array.isArray(raw.builtins)) { config.builtins = raw.builtins.filter( (name) => typeof name === "string" ); } if (typeof raw.redactor === "function") { config.redactor = raw.redactor; } const keyPatterns = toRegExpArray(raw.keyPatterns); if (keyPatterns) config.keyPatterns = keyPatterns; const patterns = toRegExpArray(raw.patterns); if (patterns) config.patterns = patterns; if (Array.isArray(raw.valuePatterns)) { const valuePatterns = []; for (const item of raw.valuePatterns) { if (!isPlainObject(item) || typeof item.name !== "string") continue; const pattern = toRegExp(item.pattern); if (!pattern) continue; valuePatterns.push({ name: item.name, pattern, replacement: typeof item.replacement === "string" ? item.replacement : void 0, mask: typeof item.mask === "function" ? item.mask : void 0 }); } config.valuePatterns = valuePatterns; } return config; } function resolveConfig(config) { const normalized = normalizeAttributeRedactorConfig(config); if (!normalized) { throw new Error("Invalid attribute redactor config"); } if (typeof normalized === "string") { const preset = REDACTOR_PRESETS[normalized]; if (!preset) { throw new Error( `Unknown attribute redactor preset: "${normalized}". Available presets: ${Object.keys(REDACTOR_PRESETS).join(", ")}` ); } return preset; } const resolvedConfig = { ...normalized, keyPatterns: normalized.keyPatterns ? [...normalized.keyPatterns] : void 0, valuePatterns: normalized.valuePatterns ? [...normalized.valuePatterns] : void 0, paths: normalized.paths ? [...normalized.paths] : void 0, patterns: normalized.patterns ? [...normalized.patterns] : void 0 }; if (resolvedConfig.builtins !== false) { const builtinNames = Array.isArray(resolvedConfig.builtins) ? resolvedConfig.builtins : Object.keys(builtinPatterns); const builtinValuePatterns = builtinNames.filter((name) => name in builtinPatterns).map(builtinToValuePattern); resolvedConfig.valuePatterns = [ ...resolvedConfig.valuePatterns ?? [], ...builtinValuePatterns ]; } return resolvedConfig; } function createRedactorFromConfig(config) { if (config.redactor) { return config.redactor; } const keyPatterns = config.keyPatterns ?? []; const valuePatterns = config.valuePatterns ?? []; const paths = config.paths ?? []; const pathSet = new Set(paths); const customPatterns = config.patterns ?? []; const defaultReplacement = config.replacement ?? "[REDACTED]"; const maskers = valuePatterns.filter((vp) => vp.mask).map((vp) => [cloneRegex(vp.pattern), vp.mask]); return (key, value) => { for (const pattern of keyPatterns) { pattern.lastIndex = 0; if (pattern.test(key)) { return defaultReplacement; } } if (pathSet.has(key)) { return defaultReplacement; } if (typeof value !== "string") { if (Array.isArray(value)) { return value.map((item) => { if (typeof item === "string") { return redactStringValue( item, valuePatterns, maskers, customPatterns, defaultReplacement ); } return item; }); } return value; } return redactStringValue( value, valuePatterns, maskers, customPatterns, defaultReplacement ); }; } function redactStringValue(value, patterns, maskers, customPatterns, defaultReplacement) { let result = value; for (const [pattern, mask] of maskers) { pattern.lastIndex = 0; result = result.replace(pattern, mask); } for (const { pattern, replacement, mask } of patterns) { if (mask) continue; pattern.lastIndex = 0; result = result.replaceAll(pattern, replacement ?? defaultReplacement); } for (const pattern of customPatterns) { pattern.lastIndex = 0; result = result.replaceAll(pattern, defaultReplacement); } return result; } function createRedactedSpan(span, redactor) { const redactedAttributes = {}; for (const [key, value] of Object.entries(span.attributes)) { if (value !== void 0) { redactedAttributes[key] = redactor(key, value); } } return new Proxy(span, { get(target, prop) { if (prop === "attributes") { return redactedAttributes; } const value = Reflect.get(target, prop); if (typeof value === "function") { return value.bind(target); } return value; } }); } function createAttributeRedactor(config) { return createRedactorFromConfig(resolveConfig(config)); } var AttributeRedactingProcessor = class { wrappedProcessor; redactor; constructor(wrappedProcessor, options) { this.wrappedProcessor = wrappedProcessor; const config = resolveConfig(options.redactor); this.redactor = createRedactorFromConfig(config); } /** * Pass through onStart unchanged - attributes aren't finalized yet */ onStart(span, parentContext) { this.wrappedProcessor.onStart(span, parentContext); } /** * Redact attributes and forward to wrapped processor */ onEnd(span) { try { const redactedSpan = createRedactedSpan(span, this.redactor); this.wrappedProcessor.onEnd(redactedSpan); } catch { this.wrappedProcessor.onEnd(span); } } forceFlush() { return this.wrappedProcessor.forceFlush(); } shutdown() { return this.wrappedProcessor.shutdown(); } }; exports.AttributeRedactingProcessor = AttributeRedactingProcessor; exports.REDACTOR_PATTERNS = REDACTOR_PATTERNS; exports.REDACTOR_PRESETS = REDACTOR_PRESETS; exports.builtinPatterns = builtinPatterns; exports.createAttributeRedactor = createAttributeRedactor; exports.createRedactedSpan = createRedactedSpan; exports.normalizeAttributeRedactorConfig = normalizeAttributeRedactorConfig; //# sourceMappingURL=chunk-CMNGGTQL.cjs.map //# sourceMappingURL=chunk-CMNGGTQL.cjs.map