From ad609f8f01dd4cc91f14bed44cab6769241dd3b1 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Mon, 11 May 2026 12:09:11 +0200 Subject: [PATCH] feat(core-shared): wire PII scrub processors FIRST in OTel pipeline --- .../instrumentation/otel/init-server-node.ts | 20 +++++++++++-------- .../src/instrumentation/sentry/scrub.ts | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/core-shared/src/instrumentation/otel/init-server-node.ts b/packages/core-shared/src/instrumentation/otel/init-server-node.ts index d151f3c..b3e6416 100644 --- a/packages/core-shared/src/instrumentation/otel/init-server-node.ts +++ b/packages/core-shared/src/instrumentation/otel/init-server-node.ts @@ -5,6 +5,7 @@ import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici"; import { PgInstrumentation } from "@opentelemetry/instrumentation-pg"; import { buildResource } from "./resource"; import { createSentryOtelBridge } from "./sentry-bridge"; +import { PiiScrubSpanProcessor, PiiScrubLogRecordProcessor } from "./pii-scrub-processor"; const { BatchSpanProcessor } = tracing; @@ -21,11 +22,11 @@ export type InitOtelServerNodeOpts = { /** * Initializes the OpenTelemetry NodeSDK for a server-side app. * - Configures Resource attributes per OTel semantic conventions. + * - Registers PII scrub processors FIRST so all downstream exporters see clean data. * - Registers Sentry span processor (via createSentryOtelBridge) when DSN is set. * - Registers Sentry log record processor when DSN is set. * - Registers an in-process MeterProvider (no exporter — Sentry metrics not yet * wired; a future phase or vendor-specific exporter can add a MetricReader). - * - PII scrub processors land in Phase 5. * * Caller is responsible for `sdk.shutdown()` on process exit. */ @@ -38,14 +39,17 @@ export function initOtelServerNode(opts: InitOtelServerNodeOpts): NodeSDK { }); const bridge = createSentryOtelBridge({ dsn: opts.dsn }); + + // PiiScrubSpanProcessor runs FIRST so the Sentry exporter never sees raw PII. const spanProcessors = bridge.spanProcessor - ? [new BatchSpanProcessor(bridge.spanProcessor as never)] - : []; - // Register SentryLogRecordForwarder directly as a processor — it implements - // LogRecordProcessor (onEmit/forceFlush/shutdown) and forwards synchronously, - // so no batching wrapper is needed (unlike span processors that need batching - // to avoid blocking the hot path). - const logRecordProcessors = bridge.logRecordProcessor ? [bridge.logRecordProcessor] : []; + ? [new PiiScrubSpanProcessor(), new BatchSpanProcessor(bridge.spanProcessor as never)] + : [new PiiScrubSpanProcessor()]; + + // PiiScrubLogRecordProcessor runs FIRST for the same reason. + // SentryLogRecordForwarder forwards synchronously (no batching wrapper needed). + const logRecordProcessors = bridge.logRecordProcessor + ? [new PiiScrubLogRecordProcessor(), bridge.logRecordProcessor] + : [new PiiScrubLogRecordProcessor()]; // In-process MeterProvider with no reader/exporter. Sentry metrics ingestion // is experimental in @sentry/opentelemetry 10.x and not wired here; metrics diff --git a/packages/core-shared/src/instrumentation/sentry/scrub.ts b/packages/core-shared/src/instrumentation/sentry/scrub.ts index f438ef2..ab6f3a0 100644 --- a/packages/core-shared/src/instrumentation/sentry/scrub.ts +++ b/packages/core-shared/src/instrumentation/sentry/scrub.ts @@ -9,7 +9,7 @@ import { REDACTED_VALUE, keyContainsPii, queryParamContainsPii, -} from "./pii-fields"; +} from "../otel/pii-fields"; // Minimal structural types matching Sentry's event shape used in scrubbers. // Using index signatures broad enough to satisfy both ErrorEvent and TransactionEvent.