Skip to content

Provide a DSC projection hook for inherited and frozen contexts before baggage propagation #25026

Closed as not planned

Description

@rpinckne

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.mjs

Save 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.

Activity

  1. linear-code commented on Oct 4, 2026

    @linear-code
  2. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Oct 4, 2026
  3. Lms24 commented on Oct 5, 2026

    @Lms24
    Member

    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.

  4. rpinckne commented on Oct 5, 2026

    @rpinckne
    Author

    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 baggage on 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions