{"version":3,"sources":["../../src/telemetry/redaction.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Attributes, SpanStatus } from '@opentelemetry/api';\nimport type { ReadableSpan, Span as SdkSpan, TimedEvent } from '@opentelemetry/sdk-trace-node';\nimport { ATTRIBUTE_REDACTION_ENABLED } from '../types.js';\nimport * as traceTypes from './trace_types.js';\n\n// Type-only imports on purpose: this module is pulled in near the top of the telemetry\n// barrel (via pino_otel_transport), which job.ts imports, so anything imported here starts\n// evaluating that far earlier inside the job <-> telemetry cycle.\n\nexport const REDACTED_EXCEPTION_MESSAGE = 'exception details redacted';\n\n/**\n * Mirrors the LiveKit Cloud collector's matcher: a whole dot-delimited `pii` segment,\n * case-insensitive (`lk.chatpii` does not match, `lk.PII.x` does).\n */\nconst PII_SEGMENT_RE = /(^|\\.)pii(\\.|$)/i;\n\n/**\n * GenAI attributes that carry content. Their names are fixed by the semantic convention,\n * so they cannot carry the `lk.pii.` marker and are enumerated here instead.\n */\nexport const GEN_AI_PII_ATTRIBUTES: ReadonlySet<string> = new Set([\n  // flagged \"likely to contain sensitive information including user/PII data\" by the spec\n  traceTypes.ATTR_GEN_AI_INPUT_MESSAGES,\n  traceTypes.ATTR_GEN_AI_OUTPUT_MESSAGES,\n  traceTypes.ATTR_GEN_AI_SYSTEM_INSTRUCTIONS,\n  traceTypes.ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,\n  traceTypes.ATTR_GEN_AI_TOOL_CALL_RESULT,\n  traceTypes.ATTR_GEN_AI_TOOL_DESCRIPTION,\n  traceTypes.ATTR_GEN_AI_TOOL_DEFINITIONS,\n  // free-form text the caller supplied or the model produced\n  traceTypes.ATTR_GEN_AI_RETRIEVAL_QUERY_TEXT,\n  traceTypes.ATTR_GEN_AI_RETRIEVAL_DOCUMENTS,\n  traceTypes.ATTR_GEN_AI_MEMORY_QUERY_TEXT,\n  traceTypes.ATTR_GEN_AI_MEMORY_RECORDS,\n  traceTypes.ATTR_GEN_AI_EVALUATION_EXPLANATION,\n]);\n\n/**\n * Events whose body rides on a generic attribute (`content`, `tool_calls`) that cannot be\n * marked, so the whole event is dropped rather than filtered.\n */\nexport const PII_EVENT_NAMES: ReadonlySet<string> = new Set([\n  traceTypes.EVENT_GEN_AI_SYSTEM_MESSAGE,\n  traceTypes.EVENT_GEN_AI_USER_MESSAGE,\n  traceTypes.EVENT_GEN_AI_ASSISTANT_MESSAGE,\n  traceTypes.EVENT_GEN_AI_TOOL_MESSAGE,\n  traceTypes.EVENT_GEN_AI_CHOICE,\n  traceTypes.EVENT_GEN_AI_CLIENT_INFERENCE_OPERATION_DETAILS,\n]);\n\n/**\n * Exception details are recorded by `recordException`, which resolves the project's redaction\n * setting; a third-party exporter must not see them either way.\n */\nexport const REDACTED_EXCEPTION_ATTRIBUTES: ReadonlySet<string> = new Set([\n  traceTypes.ATTR_EXCEPTION_MESSAGE,\n  traceTypes.ATTR_EXCEPTION_TRACE,\n]);\n\n/**\n * Whether `key` names an attribute that must be stripped: it carries a dot-delimited `pii`\n * segment, or it is one of the GenAI content attributes.\n */\nexport function isPIIAttribute(key: string): boolean {\n  if (PII_SEGMENT_RE.test(key)) return true;\n  if (GEN_AI_PII_ATTRIBUTES.has(key)) return true;\n  // gen_ai.prompt.variable.<key> holds the values interpolated into a prompt template\n  return key.startsWith(traceTypes.ATTR_GEN_AI_PROMPT_VARIABLE);\n}\n\nconst ALLOW_PII_ENV_VAR = 'LIVEKIT_TELEMETRY_ALLOW_PII';\nconst FALSY = new Set(['0', 'false', 'no', 'off']);\n\n/**\n * The `LIVEKIT_TELEMETRY_ALLOW_PII` setting, or `undefined` when unset.\n *\n * For integrators who let the framework adopt the ambient OpenTelemetry provider (a\n * NodeSDK-style setup) and so have nowhere to pass `allowPii`. Set it to `0` to withhold\n * conversational content from third-party exporters.\n */\n/**\n * Whether the record's own stamp says the project mandated redaction.\n *\n * Attribute-only on purpose: the modules that need this sit at the top of the telemetry\n * barrel, which job.ts imports, so they cannot reach the ambient job context. The stamp is\n * applied to every record by the metadata processor.\n */\nexport function redactionEnabledFromAttributes(\n  // loose on purpose: span attributes and log attributes are different OTel types\n  attributes: Record<string, unknown> | undefined,\n): boolean {\n  return Boolean(attributes?.[ATTRIBUTE_REDACTION_ENABLED]);\n}\n\nexport function allowPiiFromEnv(): boolean | undefined {\n  const raw = process.env[ALLOW_PII_ENV_VAR];\n  if (raw === undefined) return undefined;\n  return !FALSY.has(raw.trim().toLowerCase());\n}\n\nconst RAW_ATTRIBUTES = Symbol('lkRawAttributes');\nconst RAW_EVENTS = Symbol('lkRawEvents');\nconst RAW_STATUS = Symbol('lkRawStatus');\n\ninterface PiiStash {\n  [RAW_ATTRIBUTES]?: Attributes;\n  [RAW_EVENTS]?: TimedEvent[];\n  [RAW_STATUS]?: SpanStatus;\n}\n\n/** Keeps the pre-filter payload for {@link restorePii} to hand LiveKit Cloud. */\nexport function stashPii(span: SdkSpan): void {\n  const stash = span as unknown as PiiStash;\n  stash[RAW_ATTRIBUTES] = { ...span.attributes };\n  stash[RAW_EVENTS] = span.events.map((event) => ({\n    ...event,\n    attributes: { ...event.attributes },\n  }));\n  stash[RAW_STATUS] = { ...span.status };\n}\n\n/**\n * The span as it was before PII was stripped for third-party exporters.\n *\n * Only LiveKit Cloud's own exporter calls this; every other destination sees the redacted\n * span. Returns `span` unchanged when nothing was stashed.\n *\n * The view delegates to the span through its prototype so the getters `ReadableSpan` relies\n * on keep working, with only `attributes` and `events` shadowed.\n */\nexport function restorePii(span: ReadableSpan): ReadableSpan {\n  const stash = span as unknown as PiiStash;\n  const attributes = stash[RAW_ATTRIBUTES];\n  if (!attributes) return span;\n\n  const view = Object.create(span) as ReadableSpan;\n  Object.defineProperty(view, 'attributes', { value: attributes, enumerable: true });\n  Object.defineProperty(view, 'events', {\n    value: stash[RAW_EVENTS] ?? span.events,\n    enumerable: true,\n  });\n  Object.defineProperty(view, 'status', {\n    value: stash[RAW_STATUS] ?? span.status,\n    enumerable: true,\n  });\n  return view;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,mBAA4C;AAC5C,iBAA4B;AAMrB,MAAM,6BAA6B;AAM1C,MAAM,iBAAiB;AAMhB,MAAM,wBAA6C,oBAAI,IAAI;AAAA;AAAA,EAEhE,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA;AAAA,EAEX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AACb,CAAC;AAMM,MAAM,kBAAuC,oBAAI,IAAI;AAAA,EAC1D,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AACb,CAAC;AAMM,MAAM,gCAAqD,oBAAI,IAAI;AAAA,EACxE,WAAW;AAAA,EACX,WAAW;AACb,CAAC;AAMM,SAAS,eAAe,KAAsB;AACnD,MAAI,eAAe,KAAK,GAAG,EAAG,QAAO;AACrC,MAAI,sBAAsB,IAAI,GAAG,EAAG,QAAO;AAE3C,SAAO,IAAI,WAAW,WAAW,2BAA2B;AAC9D;AAEA,MAAM,oBAAoB;AAC1B,MAAM,QAAQ,oBAAI,IAAI,CAAC,KAAK,SAAS,MAAM,KAAK,CAAC;AAgB1C,SAAS,+BAEd,YACS;AACT,SAAO,QAAQ,yCAAa,yCAA4B;AAC1D;AAEO,SAAS,kBAAuC;AACrD,QAAM,MAAM,QAAQ,IAAI,iBAAiB;AACzC,MAAI,QAAQ,OAAW,QAAO;AAC9B,SAAO,CAAC,MAAM,IAAI,IAAI,KAAK,EAAE,YAAY,CAAC;AAC5C;AAEA,MAAM,iBAAiB,OAAO,iBAAiB;AAC/C,MAAM,aAAa,OAAO,aAAa;AACvC,MAAM,aAAa,OAAO,aAAa;AAShC,SAAS,SAAS,MAAqB;AAC5C,QAAM,QAAQ;AACd,QAAM,cAAc,IAAI,EAAE,GAAG,KAAK,WAAW;AAC7C,QAAM,UAAU,IAAI,KAAK,OAAO,IAAI,CAAC,WAAW;AAAA,IAC9C,GAAG;AAAA,IACH,YAAY,EAAE,GAAG,MAAM,WAAW;AAAA,EACpC,EAAE;AACF,QAAM,UAAU,IAAI,EAAE,GAAG,KAAK,OAAO;AACvC;AAWO,SAAS,WAAW,MAAkC;AAC3D,QAAM,QAAQ;AACd,QAAM,aAAa,MAAM,cAAc;AACvC,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,OAAO,OAAO,OAAO,IAAI;AAC/B,SAAO,eAAe,MAAM,cAAc,EAAE,OAAO,YAAY,YAAY,KAAK,CAAC;AACjF,SAAO,eAAe,MAAM,UAAU;AAAA,IACpC,OAAO,MAAM,UAAU,KAAK,KAAK;AAAA,IACjC,YAAY;AAAA,EACd,CAAC;AACD,SAAO,eAAe,MAAM,UAAU;AAAA,IACpC,OAAO,MAAM,UAAU,KAAK,KAAK;AAAA,IACjC,YAAY;AAAA,EACd,CAAC;AACD,SAAO;AACT;","names":[]}