Data Masking#
Overview#
Langfuse provides two complementary masking layers for protecting sensitive LLM trace data :
| Layer | Where it runs | Who configures it |
|---|---|---|
| Client-side masking | In the SDK, before transmission | Application developers |
| Server-side ingestion masking (EE) | In the Worker, during ingestion | Platform administrators |
For maximum security, both can be used together. Client-side masking ensures data never leaves the application; server-side masking is a centralized safety net for data that bypasses client controls.
Client-Side Masking#
Configured directly in the Langfuse SDK. Two hooks are available for Python :
mask_otel_spans(recommended) — runs at export stage, after the SDK decides which spans to export. Receives a read-only batch of OTel spans and returns sparse patches (delete_attributes/set_attributes). Affects only spans exported by this Langfuse client. If the hook raises or returns an invalid result, the whole export batch is dropped.mask(legacy) — runs synchronously when Langfuse SDK attributes are created. Only covers data set via Langfuse SDK APIs (start_observation(),update(),set_trace_io()); does not see raw OTel span attributes from third-party instrumentations.
For JavaScript/TypeScript, masking is configured on the LangfuseSpanProcessor via a mask callback that receives and returns the stringified JSON of each attribute value . LangChain spans flow through the same processor automatically.
See the full SDK masking docs at langfuse.com/docs/tracing-features/masking.
Server-Side Ingestion Masking (Enterprise)#
What it does#
When enabled, the Langfuse Worker sends raw OTel span data to an external HTTP callback before writing to ClickHouse. The callback returns the masked data, which is then processed normally .
Important scope limitation: Server-side masking only applies to events ingested via the OTel endpoint (/api/public/otel). This covers Python SDK v3+, TypeScript SDK v4+, and third-party OTel libraries. Events sent via the legacy /api/public/ingestion endpoint are not masked server-side .
Note: Events are written to blob storage (S3) before the Worker calls the masking callback. The callback masks data before it reaches ClickHouse and downstream Langfuse views .
Code entry points#
| File | Purpose |
|---|---|
applyIngestionMasking.ts | Core masking logic: HTTP callback, retry, fail-open/closed |
types.ts | IngestionMaskingConfig, ApplyIngestionMaskingParams, MaskingResult interfaces |
otelIngestionQueue.ts | Masking invocation point in the OTel queue processor |
The masking call site in the OTel queue processor downloads spans from S3, applies masking if enabled, then hands the (potentially masked) spans to OtelIngestionProcessor.
License gate#
isIngestionMaskingEnabled() returns true only when both the callback URL env var is set and an Enterprise license is available. Without a license, masking is disabled even if the URL is configured .
Configuration (Worker container)#
| Environment Variable | Default | Description |
|---|---|---|
LANGFUSE_INGESTION_MASKING_CALLBACK_URL | — (required to enable) | URL of your masking callback endpoint |
LANGFUSE_INGESTION_MASKING_CALLBACK_TIMEOUT_MS | 500 | Per-request timeout in ms |
LANGFUSE_INGESTION_MASKING_CALLBACK_FAIL_CLOSED | false | true = drop event on failure; false = process unmasked |
LANGFUSE_INGESTION_MASKING_MAX_RETRIES | 1 | Max retry attempts (exponential backoff, capped at 1 s) |
On the Web container: LANGFUSE_INGESTION_MASKING_PROPAGATED_HEADERS (comma-separated list of request headers to forward to the callback) .
Fail-open vs. fail-closed behavior#
After all retries are exhausted :
- Fail-open (
LANGFUSE_INGESTION_MASKING_CALLBACK_FAIL_CLOSED=false, default): Event is processed with the original unmasked data; a warning is logged. - Fail-closed (
LANGFUSE_INGESTION_MASKING_CALLBACK_FAIL_CLOSED=true): The masking result issuccess: false; the queue processor drops the event entirely .
This applies to all failure scenarios: timeout, HTTP 4xx/5xx, invalid JSON response, and network errors .
Callback interface#
The Worker POSTs the raw OTel trace object to the callback URL with headers X-Langfuse-Org-Id and X-Langfuse-Project-Id . The callback must return HTTP 200 with the masked object in the exact same OTel schema — only modify values, do not add, remove, or rename fields .
Retry behavior#
applyIngestionMasking retries up to maxRetries + 1 total attempts with exponential backoff: min(100ms × 2^(attempt-1), 1000ms) . Metrics are emitted as langfuse.ingestion.masking.callback_duration_ms, langfuse.ingestion.masking.success, and langfuse.ingestion.masking.failure.
Related Resources#
- Server-side masking docs — configuration, callback contract, FastAPI example implementation
- Client-side masking docs — SDK masking hooks with code examples
- Data Retention — complementary data lifecycle control