UNPKG

autotel

Version:
1 lines 25.6 kB
{"version":3,"file":"attribute-redacting-processor.cjs","names":[],"sources":["../src/attribute-redacting-processor.ts"],"sourcesContent":["/**\n * Attribute Redacting Processor\n *\n * Automatically redacts PII and sensitive data from span attributes before export.\n * This is critical for compliance (GDPR, PCI-DSS, HIPAA) and data security.\n *\n * @example Basic usage with preset\n * ```typescript\n * init({\n * service: 'my-app',\n * attributeRedactor: 'default'\n * })\n * ```\n *\n * @example Custom patterns\n * ```typescript\n * init({\n * service: 'my-app',\n * attributeRedactor: {\n * keyPatterns: [/password/i, /secret/i],\n * valuePatterns: [\n * { name: 'customerId', pattern: /CUST-\\d{8}/g, replacement: 'CUST-***' }\n * ]\n * }\n * })\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context, AttributeValue, Attributes } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Custom redactor function type\n */\nexport type AttributeRedactorFn = (\n key: string,\n value: AttributeValue,\n) => AttributeValue;\n\n/**\n * Built-in redactor preset names\n */\nexport type AttributeRedactorPreset = 'default' | 'strict' | 'pci-dss';\n\n/**\n * Masker function type - receives the matched string and returns a masked version\n */\nexport type MaskFn = (match: string) => string;\n\n/**\n * Value pattern configuration\n */\nexport interface ValuePatternConfig {\n /** Name for debugging/logging */\n name: string;\n /** Regex pattern to match in values */\n pattern: RegExp;\n /** Custom replacement (default: uses global replacement) */\n replacement?: string;\n /** Mask function for smart partial masking (overrides replacement) */\n mask?: MaskFn;\n}\n\n/**\n * Built-in PII pattern names\n */\nexport type BuiltinPatternName = keyof typeof builtinPatterns;\n\n/**\n * Attribute redactor configuration\n */\nexport interface AttributeRedactorConfig {\n /** Patterns to match against attribute keys (redacts entire value if key matches) */\n keyPatterns?: RegExp[];\n\n /** Patterns to match against attribute values (redacts matched portion) */\n valuePatterns?: ValuePatternConfig[];\n\n /** Dot-notation paths to redact (e.g. 'user.password', 'payment.card') */\n paths?: string[];\n\n /** Built-in PII patterns to enable. `true` enables all, `false` disables all, array selects specific ones. */\n builtins?: boolean | BuiltinPatternName[];\n\n /** Custom RegExp patterns for string-level redaction */\n patterns?: RegExp[];\n\n /** Default replacement string (default: '[REDACTED]') */\n replacement?: string;\n\n /** Custom redactor function for full control */\n redactor?: AttributeRedactorFn;\n}\n\n/**\n * Processor options\n */\nexport interface AttributeRedactingProcessorOptions {\n redactor: AttributeRedactorConfig | AttributeRedactorPreset;\n}\n\n/**\n * Built-in patterns for detecting sensitive data\n */\nexport const REDACTOR_PATTERNS = {\n // Value patterns (match content in attribute values)\n email: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/gi,\n phone: /\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b/g,\n ssn: /\\b\\d{3}[-]?\\d{2}[-]?\\d{4}\\b/g,\n creditCard: /\\b\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}\\b/g,\n bearerToken: /Bearer\\s+[A-Za-z0-9._~+/=-]+/gi,\n apiKeyInValue: /(?:api[_-]?key|apikey|api_secret)[=:][\\s\"']*[A-Za-z0-9_-]+/gi,\n jwt: /eyJ[A-Za-z0-9_-]*\\.eyJ[A-Za-z0-9_-]*\\.[A-Za-z0-9_-]*/g,\n\n // Key patterns (match attribute names - redacts entire value)\n sensitiveKey:\n /^(password|passwd|pwd|secret|token|api[_-]?key|auth|credential|private[_-]?key|authorization)$/i,\n} as const;\n\n/**\n * Built-in PII detection patterns with smart masking.\n * Each builtin preserves just enough signal for debugging while scrubbing PII.\n */\nexport const builtinPatterns = {\n /** Credit card numbers → ****1111 (PCI DSS: last 4 allowed) */\n creditCard: {\n pattern: /\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g,\n mask: (m: string) => `****${m.replace(/[\\s-]/g, '').slice(-4)}`,\n },\n /** Email addresses → a***@***.com */\n email: {\n pattern: /[\\w.+-]+@[\\w-]+\\.[\\w.]+/g,\n mask: (m: string) => {\n const at = m.indexOf('@');\n if (at < 1) return '***@***';\n const tld = m.slice(m.lastIndexOf('.'));\n return `${m[0]}***@***${tld}`;\n },\n },\n /** IPv4 addresses → ***.***.***.100 (last octet only) */\n ipv4: {\n pattern:\n /\\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,\n mask: (m: string) => `***.***.***.${m.split('.').pop()}`,\n },\n /**\n * International / formatted phone numbers.\n *\n * Matches:\n * - `+33 1 23 45 67 89` -> `+33******89`\n * - `(415) 555-1234` -> `********34`\n * - `555-123-4567` / `555.123.4567` / `5551234567` -> `********67`\n *\n * Bare short digit runs like `12345678` are intentionally not matched.\n */\n phone: {\n pattern:\n /(?:\\+\\d{1,3}[\\s.-]?\\(?\\d{1,4}\\)?(?:[\\s.-]?\\d{2,4}){2,4}|\\(\\d{1,4}\\)(?:[\\s.-]?\\d{2,4}){2,4}|\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b)/g,\n mask: (m: string) => {\n const digits = m.replace(/[^\\d]/g, '');\n const hasPlus = m.startsWith('+');\n if (hasPlus && digits.length > 4) {\n const ccMatch = m.match(/^\\+\\d{1,3}/);\n const cc = ccMatch ? ccMatch[0] : '+';\n return `${cc}******${digits.slice(-2)}`;\n }\n if (digits.length > 2) {\n return `${'*'.repeat(digits.length - 2)}${digits.slice(-2)}`;\n }\n return '***';\n },\n },\n /** JWT tokens → eyJ***.*** */\n jwt: {\n pattern: /\\beyJ[\\w-]*\\.[\\w-]*\\.[\\w-]*\\b/g,\n mask: () => 'eyJ***.***',\n },\n /** Bearer tokens → Bearer *** */\n bearer: {\n pattern: /\\bBearer\\s+[\\w\\-.~+/]{8,}=*/gi,\n mask: () => 'Bearer ***',\n },\n /** IBAN → FR76****189 (country + check digits + last 3) */\n iban: {\n pattern:\n /\\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,\n mask: (m: string) => {\n const clean = m.replace(/[\\s-]/g, '');\n return `${clean.slice(0, 4)}****${clean.slice(-3)}`;\n },\n },\n} as const;\n\nfunction cloneRegex(re: RegExp): RegExp {\n return new RegExp(re.source, re.flags);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction toRegExp(value: unknown): RegExp | undefined {\n if (value instanceof RegExp) return value;\n if (typeof value === 'string') return new RegExp(value, 'g');\n if (isPlainObject(value) && typeof value.source === 'string') {\n const flags = typeof value.flags === 'string' ? value.flags : 'g';\n return new RegExp(value.source, flags);\n }\n return undefined;\n}\n\nfunction toRegExpArray(value: unknown): RegExp[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const out: RegExp[] = [];\n for (const item of value) {\n const re = toRegExp(item);\n if (re) out.push(re);\n }\n return out.length > 0 ? out : [];\n}\n\nfunction builtinToValuePattern(name: BuiltinPatternName): ValuePatternConfig {\n const b = builtinPatterns[name];\n return { name, pattern: cloneRegex(b.pattern), mask: b.mask };\n}\n\n/**\n * Default value patterns for the 'default' preset\n */\nconst DEFAULT_VALUE_PATTERNS: ValuePatternConfig[] = [\n builtinToValuePattern('email'),\n builtinToValuePattern('phone'),\n { name: 'ssn', pattern: REDACTOR_PATTERNS.ssn },\n builtinToValuePattern('creditCard'),\n];\n\n/**\n * Built-in redactor presets\n */\nexport const REDACTOR_PRESETS: Record<\n AttributeRedactorPreset,\n AttributeRedactorConfig\n> = {\n /**\n * Default preset - covers common PII patterns with smart masking\n * Detects: emails (a***@***.com), phone numbers, SSNs, credit cards (****1111)\n * Redacts keys: password, secret, token, apiKey, auth, credential\n */\n default: {\n keyPatterns: [REDACTOR_PATTERNS.sensitiveKey],\n valuePatterns: DEFAULT_VALUE_PATTERNS,\n builtins: true,\n replacement: '[REDACTED]',\n },\n\n /**\n * Strict preset - more aggressive redaction for high-security environments\n * Includes everything in default plus: Bearer tokens, JWTs, IBAN, API keys in values\n */\n strict: {\n keyPatterns: [REDACTOR_PATTERNS.sensitiveKey, /bearer/i, /jwt/i],\n valuePatterns: [\n ...DEFAULT_VALUE_PATTERNS,\n builtinToValuePattern('jwt'),\n builtinToValuePattern('bearer'),\n builtinToValuePattern('iban'),\n { name: 'apiKeyInValue', pattern: REDACTOR_PATTERNS.apiKeyInValue },\n ],\n builtins: true,\n replacement: '[REDACTED]',\n },\n\n /**\n * PCI-DSS preset - focused on payment card industry compliance\n * Redacts: credit card numbers (****1111), CVV-like patterns, card-related keys\n */\n 'pci-dss': {\n keyPatterns: [/card/i, /cvv/i, /cvc/i, /pan/i, /expir/i, /ccn/i],\n valuePatterns: [builtinToValuePattern('creditCard')],\n builtins: ['creditCard'],\n replacement: '[REDACTED]',\n },\n};\n\n/**\n * Normalize redactor config that may have been deserialized from JSON/YAML.\n * Converts regex-like values back to RegExp instances.\n */\nexport function normalizeAttributeRedactorConfig(\n raw: AttributeRedactorConfig | AttributeRedactorPreset | unknown,\n): AttributeRedactorConfig | AttributeRedactorPreset | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (typeof raw === 'string') return raw as AttributeRedactorPreset;\n if (!isPlainObject(raw)) return undefined;\n\n const config: AttributeRedactorConfig = {};\n\n if (Array.isArray(raw.paths)) {\n config.paths = raw.paths.filter(\n (value): value is string => typeof value === 'string',\n );\n }\n\n if (typeof raw.replacement === 'string') {\n config.replacement = raw.replacement;\n }\n\n if (typeof raw.builtins === 'boolean') {\n config.builtins = raw.builtins;\n } else if (Array.isArray(raw.builtins)) {\n config.builtins = raw.builtins.filter(\n (name): name is BuiltinPatternName => typeof name === 'string',\n );\n }\n\n if (typeof raw.redactor === 'function') {\n config.redactor = raw.redactor as AttributeRedactorFn;\n }\n\n const keyPatterns = toRegExpArray(raw.keyPatterns);\n if (keyPatterns) config.keyPatterns = keyPatterns;\n\n const patterns = toRegExpArray(raw.patterns);\n if (patterns) config.patterns = patterns;\n\n if (Array.isArray(raw.valuePatterns)) {\n const valuePatterns: ValuePatternConfig[] = [];\n for (const item of raw.valuePatterns) {\n if (!isPlainObject(item) || typeof item.name !== 'string') continue;\n const pattern = toRegExp(item.pattern);\n if (!pattern) continue;\n valuePatterns.push({\n name: item.name,\n pattern,\n replacement:\n typeof item.replacement === 'string' ? item.replacement : undefined,\n mask:\n typeof item.mask === 'function' ? (item.mask as MaskFn) : undefined,\n });\n }\n config.valuePatterns = valuePatterns;\n }\n\n return config;\n}\n\n/**\n * Resolve config to a normalized form\n */\nfunction resolveConfig(\n config: AttributeRedactorConfig | AttributeRedactorPreset,\n): AttributeRedactorConfig {\n const normalized = normalizeAttributeRedactorConfig(config);\n if (!normalized) {\n throw new Error('Invalid attribute redactor config');\n }\n\n if (typeof normalized === 'string') {\n const preset = REDACTOR_PRESETS[normalized];\n if (!preset) {\n throw new Error(\n `Unknown attribute redactor preset: \"${normalized}\". ` +\n `Available presets: ${Object.keys(REDACTOR_PRESETS).join(', ')}`,\n );\n }\n return preset;\n }\n\n const resolvedConfig: AttributeRedactorConfig = {\n ...normalized,\n keyPatterns: normalized.keyPatterns\n ? [...normalized.keyPatterns]\n : undefined,\n valuePatterns: normalized.valuePatterns\n ? [...normalized.valuePatterns]\n : undefined,\n paths: normalized.paths ? [...normalized.paths] : undefined,\n patterns: normalized.patterns ? [...normalized.patterns] : undefined,\n };\n\n // Merge built-in patterns if enabled\n if (resolvedConfig.builtins !== false) {\n const builtinNames = Array.isArray(resolvedConfig.builtins)\n ? resolvedConfig.builtins\n : (Object.keys(builtinPatterns) as BuiltinPatternName[]);\n const builtinValuePatterns = builtinNames\n .filter((name) => name in builtinPatterns)\n .map(builtinToValuePattern);\n\n resolvedConfig.valuePatterns = [\n ...(resolvedConfig.valuePatterns ?? []),\n ...builtinValuePatterns,\n ];\n }\n\n return resolvedConfig;\n}\n\n/**\n * Create a redactor function from config\n */\nfunction createRedactorFromConfig(\n config: AttributeRedactorConfig,\n): AttributeRedactorFn {\n // If custom redactor provided, use it directly\n if (config.redactor) {\n return config.redactor;\n }\n\n const keyPatterns = config.keyPatterns ?? [];\n const valuePatterns = config.valuePatterns ?? [];\n const paths = config.paths ?? [];\n const pathSet = new Set(paths);\n const customPatterns = config.patterns ?? [];\n const defaultReplacement = config.replacement ?? '[REDACTED]';\n\n // Build masker list from valuePatterns that have mask functions\n const maskers: [RegExp, MaskFn][] = valuePatterns\n .filter((vp) => vp.mask)\n .map((vp) => [cloneRegex(vp.pattern), vp.mask!]);\n\n return (key: string, value: AttributeValue): AttributeValue => {\n // Key-pattern and path-based redaction only applies to string values.\n // Numbers, booleans and other non-string attributes are not credentials;\n // replacing them with the string '[REDACTED]' silently changes their\n // type and corrupts downstream consumers (LLM token counters etc.).\n if (typeof value === 'string') {\n for (const pattern of keyPatterns) {\n pattern.lastIndex = 0;\n if (pattern.test(key)) {\n return defaultReplacement;\n }\n }\n if (pathSet.has(key)) {\n return defaultReplacement;\n }\n }\n\n // For non-string values, return as-is\n if (typeof value !== 'string') {\n if (Array.isArray(value)) {\n return value.map((item) => {\n if (typeof item === 'string') {\n return redactStringValue(\n item,\n valuePatterns,\n maskers,\n customPatterns,\n defaultReplacement,\n ) as string;\n }\n return item;\n }) as AttributeValue;\n }\n return value;\n }\n\n // Three-tier strategy: path-based → masker-based → pattern-based\n return redactStringValue(\n value,\n valuePatterns,\n maskers,\n customPatterns,\n defaultReplacement,\n );\n };\n}\n\n/**\n * Apply three-tier redaction strategy to a string\n * 1. Masker-based: built-in patterns with smart partial masking\n * 2. Pattern-based: custom RegExp patterns replaced with replacement\n */\nfunction redactStringValue(\n value: string,\n patterns: ValuePatternConfig[],\n maskers: [RegExp, MaskFn][],\n customPatterns: RegExp[],\n defaultReplacement: string,\n): string {\n let result = value;\n\n // Tier 1: Apply maskers (smart partial masking)\n for (const [pattern, mask] of maskers) {\n pattern.lastIndex = 0;\n result = result.replace(pattern, mask);\n }\n\n // Tier 2: Apply value patterns without mask (full replacement)\n for (const { pattern, replacement, mask } of patterns) {\n if (mask) continue; // Already handled by maskers\n pattern.lastIndex = 0;\n result = result.replaceAll(pattern, replacement ?? defaultReplacement);\n }\n\n // Tier 3: Apply custom patterns\n for (const pattern of customPatterns) {\n pattern.lastIndex = 0;\n result = result.replaceAll(pattern, defaultReplacement);\n }\n\n return result;\n}\n\n/**\n * Create a proxy wrapper around ReadableSpan with redacted attributes\n *\n * Since ReadableSpan.attributes is readonly, we use a Proxy to intercept\n * attribute access and return the redacted version.\n */\nfunction createRedactedSpan(\n span: ReadableSpan,\n redactor: AttributeRedactorFn,\n): ReadableSpan {\n // Pre-compute redacted attributes (cached for efficiency)\n const redactedAttributes: Attributes = {};\n for (const [key, value] of Object.entries(span.attributes)) {\n if (value !== undefined) {\n redactedAttributes[key] = redactor(key, value);\n }\n }\n\n // Return a proxy that intercepts attribute access\n return new Proxy(span, {\n get(target, prop) {\n if (prop === 'attributes') {\n return redactedAttributes;\n }\n // For all other properties, delegate to the original span\n const value = Reflect.get(target, prop);\n // Bind methods to the original target\n if (typeof value === 'function') {\n return value.bind(target);\n }\n return value;\n },\n });\n}\n\n/**\n * Create an attribute redactor function from a config or preset.\n *\n * This is useful when you need to apply the same redaction logic\n * outside of the span processor pipeline (e.g., for canonical log lines).\n *\n * @example\n * ```typescript\n * const redactor = createAttributeRedactor('default');\n * const redactedValue = redactor('user.password', 'secret123');\n * // redactedValue === '[REDACTED]'\n * ```\n */\nexport function createAttributeRedactor(\n config: AttributeRedactorConfig | AttributeRedactorPreset,\n): AttributeRedactorFn {\n return createRedactorFromConfig(resolveConfig(config));\n}\n\n/**\n * Span processor that redacts sensitive data from span attributes.\n *\n * Redaction happens in onEnd() when all attributes are finalized.\n * Uses a Proxy wrapper to intercept attribute access since ReadableSpan\n * attributes are readonly.\n *\n * Common use cases:\n * - PII compliance (GDPR, CCPA)\n * - PCI-DSS compliance for payment data\n * - Preventing secrets from leaking to observability backends\n */\nexport class AttributeRedactingProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n private readonly redactor: AttributeRedactorFn;\n\n constructor(\n wrappedProcessor: SpanProcessor,\n options: AttributeRedactingProcessorOptions,\n ) {\n this.wrappedProcessor = wrappedProcessor;\n const config = resolveConfig(options.redactor);\n this.redactor = createRedactorFromConfig(config);\n }\n\n /**\n * Pass through onStart unchanged - attributes aren't finalized yet\n */\n onStart(span: Span, parentContext: Context): void {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n /**\n * Redact attributes and forward to wrapped processor\n */\n onEnd(span: ReadableSpan): void {\n try {\n const redactedSpan = createRedactedSpan(span, this.redactor);\n this.wrappedProcessor.onEnd(redactedSpan);\n } catch {\n // Fail-open: if redaction fails, forward original span\n // This ensures we don't lose telemetry due to redaction errors\n this.wrappedProcessor.onEnd(span);\n }\n }\n\n forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n}\n\n/**\n * Export createRedactedSpan for advanced users who want to use it directly\n */\nexport { createRedactedSpan };\n"],"mappings":"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"}