UNPKG

autotel

Version:
337 lines (335 loc) 9.31 kB
import { propagation, context } from '@opentelemetry/api'; // src/business-baggage.ts var DEFAULT_MAX_KEY_LENGTH = 64; var DEFAULT_MAX_VALUE_LENGTH = 256; var DEFAULT_MAX_TOTAL_SIZE = 8192; var PII_PATTERNS = [ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, // Phone (US) /\b\d{3}[-]?\d{2}[-]?\d{4}\b/, // SSN /\b\d{16}\b/ // Credit card (basic) ]; var HIGH_CARDINALITY_PATTERNS = [ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, // UUID /^\d{13,}$/, // Timestamps /^[A-Za-z0-9+/]{20,}={0,2}$/ // Base64 ]; function createSafeBaggageSchema(schema, options = {}) { const { maxKeyLength = DEFAULT_MAX_KEY_LENGTH, maxValueLength = DEFAULT_MAX_VALUE_LENGTH, maxTotalSize = DEFAULT_MAX_TOTAL_SIZE, prefix = "", hashHighCardinality = false, redactPII = false, allowedKeys, onError } = options; const schemaKeys = new Set(Object.keys(schema)); if (allowedKeys) { for (const key of schemaKeys) { if (!allowedKeys.includes(key)) { throw new Error(`Key "${key}" not in allowedKeys whitelist`); } } } const prefixKey = (key) => prefix ? `${prefix}.${key}` : key; const hashValue = (value) => { let hash = 2166136261; for (let i = 0; i < value.length; i++) { hash ^= value.codePointAt(i) ?? 0; hash = hash * 16777619 >>> 0; } return `h_${hash.toString(16)}`; }; const containsPII = (value) => { return PII_PATTERNS.some((pattern) => pattern.test(value)); }; const isHighCardinality = (value) => { return HIGH_CARDINALITY_PATTERNS.some((pattern) => pattern.test(value)); }; const validateAndTransform = (key, value, fieldDef) => { const fullKey = prefixKey(key); if (fullKey.length > maxKeyLength) { onError?.({ type: "key_length", key, message: `Key "${key}" exceeds max length ${maxKeyLength}` }); return null; } if (value === void 0 || value === null) { if (fieldDef.required) { onError?.({ type: "validation", key, message: `Required field "${key}" is missing` }); return null; } if (fieldDef.defaultValue === void 0) { return null; } else { value = fieldDef.defaultValue; } } let stringValue; switch (fieldDef.type) { case "string": { if (typeof value !== "string") { onError?.({ type: "validation", key, message: `Field "${key}" expected string, got ${typeof value}`, value }); return null; } stringValue = value; break; } case "number": { if (typeof value !== "number" || Number.isNaN(value)) { onError?.({ type: "validation", key, message: `Field "${key}" expected number, got ${typeof value}`, value }); return null; } stringValue = String(value); break; } case "boolean": { if (typeof value !== "boolean") { onError?.({ type: "validation", key, message: `Field "${key}" expected boolean, got ${typeof value}`, value }); return null; } stringValue = String(value); break; } case "enum": { if (!fieldDef.values?.includes(String(value))) { onError?.({ type: "validation", key, message: `Field "${key}" value "${value}" not in allowed values: ${fieldDef.values?.join(", ")}`, value }); return null; } stringValue = String(value); break; } default: { stringValue = String(value); } } if (fieldDef.validate && !fieldDef.validate(value)) { onError?.({ type: "validation", key, message: `Field "${key}" failed custom validation`, value }); return null; } if (redactPII && containsPII(stringValue)) { onError?.({ type: "pii", key, message: `Field "${key}" contains PII pattern`, value: "[REDACTED]" }); stringValue = hashValue(stringValue); } if (fieldDef.hash || hashHighCardinality && isHighCardinality(stringValue)) { stringValue = hashValue(stringValue); } const maxLen = fieldDef.maxLength ?? maxValueLength; if (stringValue.length > maxLen) { onError?.({ type: "value_length", key, message: `Field "${key}" value exceeds max length ${maxLen}`, value: stringValue }); stringValue = stringValue.slice(0, maxLen); } return stringValue; }; const parseValue = (key, stringValue, fieldDef) => { switch (fieldDef.type) { case "number": { return Number.parseFloat(stringValue); } case "boolean": { return stringValue === "true"; } default: { return stringValue; } } }; return { get() { const baggage = propagation.getBaggage(context.active()); if (!baggage) { return {}; } const result = {}; for (const [key, fieldDef] of Object.entries(schema)) { const fullKey = prefixKey(key); const entry = baggage.getEntry(fullKey); if (entry) { result[key] = parseValue(key, entry.value, fieldDef); } else if (fieldDef.defaultValue !== void 0) { result[key] = fieldDef.defaultValue; } } return result; }, set(ctx, values) { let baggage = propagation.getBaggage(context.active()) ?? propagation.createBaggage(); let totalSize = 0; for (const [key, entry] of baggage.getAllEntries()) { totalSize += key.length + entry.value.length; } for (const [key, value] of Object.entries(values)) { const fieldDef = schema[key]; if (!fieldDef) continue; const fullKey = prefixKey(key); const stringValue = validateAndTransform(key, value, fieldDef); if (stringValue !== null) { const entrySize = fullKey.length + stringValue.length; if (totalSize + entrySize > maxTotalSize) { onError?.({ type: "size", key, message: `Adding "${key}" would exceed max baggage size ${maxTotalSize}`, value }); continue; } baggage = baggage.setEntry(fullKey, { value: stringValue }); totalSize += entrySize; } } const newContext = propagation.setBaggage(context.active(), baggage); propagation.setBaggage(newContext, baggage); }, getValue(key) { const baggage = propagation.getBaggage(context.active()); if (!baggage) return void 0; const fullKey = prefixKey(String(key)); const entry = baggage.getEntry(fullKey); const fieldDef = schema[String(key)]; if (!entry) { return fieldDef?.defaultValue; } if (!fieldDef) { return void 0; } return parseValue( String(key), entry.value, fieldDef ); }, setValue(key, value, ctx) { this.set(ctx, { [key]: value }); }, clear() { let baggage = propagation.getBaggage(context.active()); if (!baggage) return; for (const key of Object.keys(schema)) { const fullKey = prefixKey(key); baggage = baggage.removeEntry(fullKey); } propagation.setBaggage(context.active(), baggage); }, toHeaders() { const headers = {}; propagation.inject(context.active(), headers); return headers; }, fromHeaders(headers, ctx) { const extractedContext = propagation.extract(context.active(), headers); const baggage = propagation.getBaggage(extractedContext); if (baggage) { const values = {}; for (const [key, fieldDef] of Object.entries(schema)) { const fullKey = prefixKey(key); const entry = baggage.getEntry(fullKey); if (entry) { values[key] = parseValue(key, entry.value, fieldDef); } } this.set(ctx, values); } } }; } var BusinessBaggage = createSafeBaggageSchema( { tenantId: { type: "string", maxLength: 64 }, userId: { type: "string", hash: true, // Auto-hash for privacy maxLength: 64 }, correlationId: { type: "string", maxLength: 128 }, workflowId: { type: "string", maxLength: 128 }, priority: { type: "enum", values: ["low", "normal", "high", "critical"], defaultValue: "normal" }, region: { type: "string", maxLength: 32 }, channel: { type: "enum", values: [ "web", "mobile", "api", "internal", "webhook", "scheduled" ] } }, { prefix: "biz", redactPII: true, hashHighCardinality: true } ); export { BusinessBaggage, createSafeBaggageSchema }; //# sourceMappingURL=chunk-4IFSYQVX.js.map //# sourceMappingURL=chunk-4IFSYQVX.js.map