Repository navigation
Provide a DSC projection hook for inherited and frozen contexts before baggage propagation #25026
Description
In @sentry/core@11.4.0, a createDsc integration listener can project a newly created dynamic sampling context, but inherited scope, frozen-span and OpenTelemetry trace-state contexts return before that listener runs. An integration that removes a transaction field therefore cannot apply the same projection before getTraceData() or automatic outgoing baggage propagation. Envelope filtering runs after headers can already have been constructed.
Could a supported opt-in projection hook cover all DSC return paths before outgoing serialization? Reusing the existing hook on a cloned context is one possible approach, though its creation semantics and repeated invocation need deliberate API consideration. Trace identifiers, sampling decisions, account fields, external-context suppression and caller-owned state should remain unchanged when no projection is requested.
Merged #22797 deliberately preserves frozen incoming DSC under the propagation contract. The requested projection should be opt-in, operate on derived output, and preserve incoming state and sampling decisions. #20686 covers Replay enrichment of cached contexts, a separate concern.
Reproduction
Tested with Node 24.21.0. In an empty directory:
printf '{"private":true,"type":"module"}\n' > package.json
npm install --save-exact @sentry/core@11.4.0
node repro.mjsSave as repro.mjs:
// Public API reproduction of inherited DSC projection.
// No network, application source, private scope fields or account credentials.
import assert from 'node:assert/strict'
import * as Sentry from '@sentry/core'
const client = Sentry.initAndBind(Sentry.Client, {
dsn: 'https://public@localhost/1', stackParser: () => [], tracesSampleRate: 1,
integrations: [{ name: 'DscProjectionRepro', setup(client) { client.on('createDsc', dsc => { delete dsc.transaction }) } }],
transport: () => ({ send: async () => ({ statusCode: 200 }), flush: async () => true }),
})
Sentry.continueTrace({ sentryTrace: 'a'.repeat(32) + '-' + 'b'.repeat(16) + '-1', baggage: 'sentry-trace_id=' + 'a'.repeat(32) + ',sentry-transaction=credential-canary,sentry-sampled=true' }, () => {
const outgoing = Sentry.getTraceData()
assert(outgoing['sentry-trace'].startsWith('a'.repeat(32)))
assert(!decodeURIComponent(outgoing.baggage).includes('credential-canary'), 'Inherited scope DSC bypassed the lifecycle projection')
Sentry.startSpan({ name: 'inherited-parent' }, () => {
assert(!decodeURIComponent(Sentry.getTraceData().baggage).includes('credential-canary'), 'Frozen span DSC bypassed the lifecycle projection')
})
})
await client.close(1000)
console.log('Inherited DSC lifecycle projection passed')The trace-ID assertion passes, but the canary-exclusion assertion fails because the inherited transaction canary remains in outgoing baggage. The transport is synthetic and does not make network calls. This fixture requests a generic integration lifecycle seam; it does not claim a service vulnerability or a production incident.
Metadata
Metadata
Assignees
Projects
- StatusShow more project fieldsWaiting for: Product Owner
Activity
linear-code commentedon Oct 4, 2026
JS-3876
Lms24 commentedon Oct 5, 2026
Hey @rpinckne thanks for writing in! This issue looks heavily AI-generated and at this point, I cannot identify the concrete use case for such a hook. Besides that what you're asking sounds like a footgun for the majority of users, I need a concrete use case to determine if this would be added or not. At this point, without having more context, I'd rather avoid adding more API surface.
rpinckne commentedon Oct 5, 2026
Thanks for looking at this. The concrete case: our backend continues traces from another of our own services, so the incoming baggage carried that service's transaction name, and our backend then forwarded it in
baggageon outgoing requests to third-party APIs. We only wanted trace headers to stay inside our own services.That doesn't need a new hook. We're setting
tracePropagationTargets: []on our server SDKs instead, which stops the headers going out at all, and dropping our local patch. Closing this one.