VYPR
Medium severityOSV Advisory· Published Nov 25, 2025· Updated Apr 15, 2026

CVE-2025-65944

CVE-2025-65944

Description

Sentry-Javascript is an official Sentry SDKs for JavaScript. From version 10.11.0 to before 10.27.0, when a Node.js application using the Sentry SDK has sendDefaultPii: true it is possible to inadvertently send certain sensitive HTTP headers, including the Cookie header, to Sentry. Those headers would be stored within a Sentry organization as part of the associated trace. A person with access to the Sentry organization could then view and use these sensitive values to impersonate or escalate their privileges within the application. This issue has been patched in version 10.27.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@sentry/nodenpm
>= 10.11.0, < 10.27.010.27.0
@sentry/astronpm
>= 10.11.0, < 10.27.010.27.0
@sentry/aws-serverlessnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/bunnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/google-cloud-serverlessnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/nestjsnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/nextjsnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/node-corenpm
>= 10.11.0, < 10.27.010.27.0
@sentry/nuxtnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/remixnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/solidstartnpm
>= 10.11.0, < 10.27.010.27.0
@sentry/sveltekitnpm
>= 10.11.0, < 10.27.010.27.0

Affected products

1

Patches

1
a820fa2891fd

feat(node): Add incoming request headers as OTel span attributes (#17475)

https://github.com/getsentry/sentry-javascriptSigrid HuemerSep 9, 2025via ghsa
59 files changed · +1020 13
  • dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts+25 0 modified
    @@ -78,6 +78,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
                 'sentry.sample_rate': 1,
                 'sentry.source': 'route',
                 url: expect.stringContaining('/test-ssr'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
               op: 'http.server',
               origin: 'auto.http.astro',
    @@ -223,6 +228,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/user-page/myUsername123'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    @@ -256,6 +266,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/api/user/myUsername123.json'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate',
    +            'http.request.header.accept_language': '*',
    +            'http.request.header.sec_fetch_mode': 'cors',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    @@ -308,6 +323,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/catchAll/hell0/whatever-do'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    @@ -360,6 +380,11 @@ test.describe('parametrized vs static paths', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/user-page/settings'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    
  • dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts+25 0 modified
    @@ -79,6 +79,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
                 'sentry.sample_rate': 1,
                 'sentry.source': 'route',
                 url: expect.stringContaining('/test-ssr'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
               op: 'http.server',
               origin: 'auto.http.astro',
    @@ -226,6 +231,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/user-page/myUsername123'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    @@ -259,6 +269,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/api/user/myUsername123.json'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate',
    +            'http.request.header.accept_language': '*',
    +            'http.request.header.sec_fetch_mode': 'cors',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    @@ -311,6 +326,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/catchAll/hell0/whatever-do'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    @@ -363,6 +383,11 @@ test.describe('parametrized vs static paths', () => {
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
                 url: expect.stringContaining('/user-page/settings'),
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'navigate',
    +            'http.request.header.user_agent': expect.any(String),
               },
             },
           },
    
  • dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts+5 0 modified
    @@ -70,6 +70,11 @@ test.describe('tracing in static routes with server islands', () => {
                 'sentry.op': 'http.server',
                 'sentry.origin': 'auto.http.astro',
                 'sentry.source': 'route',
    +            'http.request.header.accept': expect.any(String),
    +            'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +            'http.request.header.accept_language': 'en-US',
    +            'http.request.header.sec_fetch_mode': 'cors',
    +            'http.request.header.user_agent': expect.any(String),
               }),
               op: 'http.server',
               origin: 'auto.http.astro',
    
  • dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts+26 0 modified
    @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -75,6 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-http/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -106,6 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-inbound-headers/:id',
    +      'http.request.header.baggage': expect.any(String),
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
         },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
    @@ -146,6 +161,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -189,6 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-fetch/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction from module', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/example-module/transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction from module', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/example-module/transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts+1 1 modified
    @@ -2,4 +2,4 @@
     /// <reference types="next/image-types/global" />
     
     // NOTE: This file should not be edited
    -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
    +// see https://nextjs.org/docs/basic-features/typescript for more information.
    
  • dev-packages/e2e-tests/test-applications/nextjs-15/package.json+2 1 modified
    @@ -6,6 +6,7 @@
         "build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
         "clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs",
         "test:prod": "TEST_ENV=production playwright test",
    +    "test:prod-turbo": "TEST_ENV=prod-turbopack playwright test",
         "test:dev": "TEST_ENV=development playwright test",
         "test:dev-turbo": "TEST_ENV=dev-turbopack playwright test",
         "test:build": "pnpm install && pnpm build",
    @@ -46,7 +47,7 @@
           },
           {
             "build-command": "pnpm test:build-turbo",
    -        "assert-command": "pnpm test:prod && pnpm test:dev-turbo",
    +        "assert-command": "pnpm test:prod-turbo && pnpm test:dev-turbo",
             "label": "nextjs-15 (turbo)"
           }
         ]
    
  • dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs+1 1 modified
    @@ -14,7 +14,7 @@ const getStartCommand = () => {
         return 'pnpm next dev -p 3030 2>&1 | tee .tmp_dev_server_logs';
       }
     
    -  if (testEnv === 'production') {
    +  if (testEnv === 'production' || testEnv === 'prod-turbopack') {
         return 'pnpm next start -p 3030';
       }
     
    
  • dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts+35 0 modified
    @@ -22,3 +22,38 @@ test('App router transactions should be attached to the pageload request span',
       expect(pageloadTraceId).toBeTruthy();
       expect(serverTransaction.contexts?.trace?.trace_id).toBe(pageloadTraceId);
     });
    +
    +test('extracts HTTP request headers as span attributes', async ({ baseURL }) => {
    +  test.skip(
    +    process.env.TEST_ENV === 'prod-turbopack' || process.env.TEST_ENV === 'dev-turbopack',
    +    'Incoming fetch request headers are not added as span attributes when Turbopack is enabled (addHeadersAsAttributes)',
    +  );
    +
    +  const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
    +    return transactionEvent?.transaction === 'GET /pageload-tracing';
    +  });
    +
    +  await fetch(`${baseURL}/pageload-tracing`, {
    +    headers: {
    +      'User-Agent': 'Custom-NextJS-Agent/15.0',
    +      'Content-Type': 'text/html',
    +      'X-NextJS-Test': 'nextjs-header-value',
    +      Accept: 'text/html, application/xhtml+xml',
    +      'X-Framework': 'Next.js',
    +      'X-Request-ID': 'nextjs-789',
    +    },
    +  });
    +
    +  const serverTransaction = await serverTransactionPromise;
    +
    +  expect(serverTransaction.contexts?.trace?.data).toEqual(
    +    expect.objectContaining({
    +      'http.request.header.user_agent': 'Custom-NextJS-Agent/15.0',
    +      'http.request.header.content_type': 'text/html',
    +      'http.request.header.x_nextjs_test': 'nextjs-header-value',
    +      'http.request.header.accept': 'text/html, application/xhtml+xml',
    +      'http.request.header.x_framework': 'Next.js',
    +      'http.request.header.x_request_id': 'nextjs-789',
    +    }),
    +  );
    +});
    
  • dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts+7 0 modified
    @@ -39,6 +39,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts+38 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -208,3 +215,34 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
         measurements: {},
       });
     });
    +
    +test('Extracts HTTP request headers as span attributes', async ({ baseURL }) => {
    +  const transactionEventPromise = waitForTransaction('node-express', transactionEvent => {
    +    return (
    +      transactionEvent?.contexts?.trace?.op === 'http.server' &&
    +      transactionEvent?.transaction === 'GET /test-transaction'
    +    );
    +  });
    +
    +  await fetch(`${baseURL}/test-transaction`, {
    +    headers: {
    +      'User-Agent': 'Custom-Agent/1.0 (Test)',
    +      'Content-Type': 'application/json',
    +      'X-Custom-Header': 'test-value',
    +      Accept: 'application/json, text/plain',
    +      'X-Request-ID': 'req-123',
    +    },
    +  });
    +
    +  const transactionEvent = await transactionEventPromise;
    +
    +  expect(transactionEvent.contexts?.trace?.data).toEqual(
    +    expect.objectContaining({
    +      'http.request.header.user_agent': 'Custom-Agent/1.0 (Test)',
    +      'http.request.header.content_type': 'application/json',
    +      'http.request.header.x_custom_header': 'test-value',
    +      'http.request.header.accept': 'application/json, text/plain',
    +      'http.request.header.x_request_id': 'req-123',
    +    }),
    +  );
    +});
    
  • dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts+38 2 modified
    @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -75,6 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-http/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -106,6 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-inbound-headers/:id',
    +      'http.request.header.baggage': expect.any(String),
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
         },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
    @@ -146,6 +161,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -189,6 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-fetch/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -198,7 +224,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
       });
     
       expect(inboundTransaction.contexts?.trace).toEqual({
    -    data: expect.objectContaining({
    +    data: {
           'sentry.source': 'route',
           'sentry.origin': 'auto.http.otel.http',
           'sentry.op': 'http.server',
    @@ -219,8 +245,18 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'net.peer.port': expect.any(Number),
           'http.status_code': 200,
           'http.status_text': 'OK',
    +      'http.user_agent': 'node',
           'http.route': '/test-inbound-headers/:id',
    -    }),
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.baggage': expect.any(String),
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
    +      'http.request.header.user_agent': 'node',
    +    },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts+38 2 modified
    @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -75,6 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-http/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -106,6 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-inbound-headers/:id',
    +      'http.request.header.baggage': expect.any(String),
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
         },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
    @@ -146,6 +161,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -189,6 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-fetch/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -198,7 +224,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
       });
     
       expect(inboundTransaction.contexts?.trace).toEqual({
    -    data: expect.objectContaining({
    +    data: {
           'sentry.source': 'route',
           'sentry.origin': 'auto.http.otel.http',
           'sentry.op': 'http.server',
    @@ -220,7 +246,17 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-inbound-headers/:id',
    -    }),
    +      'http.user_agent': 'node',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.baggage': expect.any(String),
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
    +      'http.request.header.user_agent': 'node',
    +    },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts+38 2 modified
    @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -75,6 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-http/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': 'localhost:3030',
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -106,6 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-inbound-headers/:id',
    +      'http.request.header.baggage': expect.any(String),
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
         },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
    @@ -146,6 +161,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -189,6 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-fetch/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -198,7 +224,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
       });
     
       expect(inboundTransaction.contexts?.trace).toEqual({
    -    data: expect.objectContaining({
    +    data: {
           'sentry.source': 'route',
           'sentry.origin': 'auto.http.otel.http',
           'sentry.op': 'http.server',
    @@ -219,8 +245,18 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'net.peer.port': expect.any(Number),
           'http.status_code': 200,
           'http.status_text': 'OK',
    +      'http.user_agent': 'node',
           'http.route': '/test-inbound-headers/:id',
    -    }),
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.baggage': expect.any(String),
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
    +      'http.request.header.user_agent': 'node',
    +    },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts+11 0 modified
    @@ -37,6 +37,13 @@ test('Sends successful transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-success',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -57,6 +64,10 @@ test('Sends successful transaction', async ({ baseURL }) => {
     
       const spans = transactionEvent.spans || [];
     
    +  spans.forEach(span => {
    +    expect(Object.keys(span.data).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +  });
    +
       expect(spans).toEqual([
         {
           data: {
    
  • dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts+38 2 modified
    @@ -31,6 +31,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -74,6 +78,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-http/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -105,6 +116,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-inbound-headers/:id',
    +      'http.request.header.baggage': expect.stringContaining(traceId!), // we already check if traceId is defined
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
         },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
    @@ -145,6 +160,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
     
       const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
     
    +  const outgoingHttpSpanData = outgoingHttpSpan?.data || {};
    +  // Outgoing span (`http.client`) does not include headers as attributes
    +  expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false);
    +
       expect(traceId).toEqual(expect.any(String));
     
       // data is passed through from the inbound request, to verify we have the correct headers set
    @@ -188,6 +207,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-outgoing-fetch/:id',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': 'localhost:3030',
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    @@ -197,7 +223,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
       });
     
       expect(inboundTransaction.contexts?.trace).toEqual({
    -    data: expect.objectContaining({
    +    data: {
           'sentry.source': 'route',
           'sentry.origin': 'auto.http.otel.http',
           'sentry.op': 'http.server',
    @@ -219,7 +245,17 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-inbound-headers/:id',
    -    }),
    +      'http.user_agent': 'node',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.baggage': expect.stringContaining(traceId!), // we already check if traceId is defined
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
    +      'http.request.header.user_agent': 'node',
    +    },
         op: 'http.server',
         parent_span_id: outgoingHttpSpanId,
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts+7 0 modified
    @@ -36,6 +36,13 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/task',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         origin: 'auto.http.otel.http',
         op: 'http.server',
    
  • dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts+7 0 modified
    @@ -50,6 +50,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts+7 0 modified
    @@ -50,6 +50,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts+30 0 modified
    @@ -43,3 +43,33 @@ test('does not send transactions for build asset folder "_nuxt"', async ({ page
     
       expect(transactionEvent.transaction).toBe('GET /test-param/:param()');
     });
    +
    +test('extracts HTTP request headers as span attributes', async ({ baseURL }) => {
    +  const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
    +    return transactionEvent.transaction.includes('GET /api/test-param/');
    +  });
    +
    +  await fetch(`${baseURL}/api/test-param/headers-test`, {
    +    headers: {
    +      'User-Agent': 'Custom-Nuxt-Agent/3.0',
    +      'Content-Type': 'application/json',
    +      'X-Nuxt-Test': 'nuxt-header-value',
    +      Accept: 'application/json, text/html',
    +      'X-Framework': 'Nuxt',
    +      'X-Request-ID': 'nuxt-456',
    +    },
    +  });
    +
    +  const transaction = await transactionPromise;
    +
    +  expect(transaction.contexts?.trace?.data).toEqual(
    +    expect.objectContaining({
    +      'http.request.header.user_agent': 'Custom-Nuxt-Agent/3.0',
    +      'http.request.header.content_type': 'application/json',
    +      'http.request.header.x_nuxt_test': 'nuxt-header-value',
    +      'http.request.header.accept': 'application/json, text/html',
    +      'http.request.header.x_framework': 'Nuxt',
    +      'http.request.header.x_request_id': 'nuxt-456',
    +    }),
    +  );
    +});
    
  • dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts+30 0 modified
    @@ -32,3 +32,33 @@ test('server pageload request span has nested request span for sub request', asy
         ]),
       );
     });
    +
    +test('extracts HTTP request headers as span attributes', async ({ page, baseURL }) => {
    +  const serverTxnEventPromise = waitForTransaction('sveltekit-2', txnEvent => {
    +    return txnEvent?.transaction === 'GET /api/users';
    +  });
    +
    +  await fetch(`${baseURL}/api/users`, {
    +    headers: {
    +      'User-Agent': 'Custom-SvelteKit-Agent/1.0',
    +      'Content-Type': 'application/json',
    +      'X-Test-Header': 'sveltekit-test-value',
    +      Accept: 'application/json',
    +      'X-Framework': 'SvelteKit',
    +      'X-Request-ID': 'sveltekit-123',
    +    },
    +  });
    +
    +  const serverTxnEvent = await serverTxnEventPromise;
    +
    +  expect(serverTxnEvent.contexts?.trace?.data).toEqual(
    +    expect.objectContaining({
    +      'http.request.header.user_agent': 'Custom-SvelteKit-Agent/1.0',
    +      'http.request.header.content_type': 'application/json',
    +      'http.request.header.x_test_header': 'sveltekit-test-value',
    +      'http.request.header.accept': 'application/json',
    +      'http.request.header.x_framework': 'SvelteKit',
    +      'http.request.header.x_request_id': 'sveltekit-123',
    +    }),
    +  );
    +});
    
  • dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts+7 0 modified
    @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => {
           'http.status_code': 200,
           'http.status_text': 'OK',
           'http.route': '/test-transaction',
    +      'http.request.header.accept': '*/*',
    +      'http.request.header.accept_encoding': 'gzip, deflate',
    +      'http.request.header.accept_language': '*',
    +      'http.request.header.connection': 'keep-alive',
    +      'http.request.header.host': expect.any(String),
    +      'http.request.header.sec_fetch_mode': 'cors',
    +      'http.request.header.user_agent': 'node',
         },
         op: 'http.server',
         span_id: expect.stringMatching(/[a-f0-9]{16}/),
    
  • dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts+16 0 modified
    @@ -2,6 +2,18 @@ import { afterAll, describe, expect, test } from 'vitest';
     import { cleanupChildProcesses, createEsmAndCjsTests, createRunner } from '../../../utils/runner';
     import { createTestServer } from '../../../utils/server';
     
    +function getCommonHttpRequestHeaders(): Record<string, unknown> {
    +  return {
    +    'http.request.header.accept': '*/*',
    +    'http.request.header.accept_encoding': 'gzip, deflate',
    +    'http.request.header.accept_language': '*',
    +    'http.request.header.connection': 'keep-alive',
    +    'http.request.header.host': expect.any(String),
    +    'http.request.header.sec_fetch_mode': 'cors',
    +    'http.request.header.user_agent': 'node',
    +  };
    +}
    +
     describe('httpIntegration', () => {
       afterAll(() => {
         cleanupChildProcesses();
    @@ -118,6 +130,7 @@ describe('httpIntegration', () => {
                     'sentry.sample_rate': 1,
                     'sentry.source': 'route',
                     url: `http://localhost:${port}/test`,
    +                ...getCommonHttpRequestHeaders(),
                   });
                 },
               })
    @@ -159,6 +172,9 @@ describe('httpIntegration', () => {
                     'sentry.sample_rate': 1,
                     'sentry.source': 'route',
                     url: `http://localhost:${port}/test`,
    +                'http.request.header.content_length': '9',
    +                'http.request.header.content_type': 'text/plain;charset=UTF-8',
    +                ...getCommonHttpRequestHeaders(),
                   });
                 },
               })
    
  • packages/astro/src/index.server.ts+2 0 modified
    @@ -59,6 +59,8 @@ export {
       getSpanStatusFromHttpCode,
       getTraceData,
       getTraceMetaTags,
    +  httpHeadersToSpanAttributes,
    +  winterCGHeadersToDict,
       graphqlIntegration,
       hapiIntegration,
       httpIntegration,
    
  • packages/astro/src/server/middleware.ts+6 0 modified
    @@ -18,10 +18,12 @@ import {
       getClient,
       getCurrentScope,
       getTraceMetaTags,
    +  httpHeadersToSpanAttributes,
       SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
       SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
       setHttpStatus,
       startSpan,
    +  winterCGHeadersToDict,
       withIsolationScope,
     } from '@sentry/node';
     import type { APIContext, MiddlewareResponseHandler, RoutePart } from 'astro';
    @@ -220,6 +222,10 @@ async function instrumentRequestStartHttpServerSpan(
                 // This is here for backwards compatibility, we used to set this here before
                 method,
                 url: stripUrlQueryAndFragment(ctx.url.href),
    +            ...httpHeadersToSpanAttributes(
    +              winterCGHeadersToDict(request.headers),
    +              getClient()?.getOptions().sendDefaultPii ?? false,
    +            ),
               };
     
               if (parametrizedRoute) {
    
  • packages/astro/test/server/middleware.test.ts+1 1 modified
    @@ -54,7 +54,7 @@ describe('sentryMiddleware', () => {
           } as any;
         });
         vi.spyOn(SentryNode, 'getActiveSpan').mockImplementation(getSpanMock);
    -    vi.spyOn(SentryNode, 'getClient').mockImplementation(() => ({}) as Client);
    +    vi.spyOn(SentryNode, 'getClient').mockImplementation(() => ({ getOptions: () => ({}) }) as Client);
         vi.spyOn(SentryNode, 'getTraceMetaTags').mockImplementation(
           () => `
         <meta name="sentry-trace" content="123">
    
  • packages/aws-serverless/src/index.ts+2 0 modified
    @@ -42,6 +42,8 @@ export {
       close,
       getSentryRelease,
       createGetModuleFromFilename,
    +  httpHeadersToSpanAttributes,
    +  winterCGHeadersToDict,
       // eslint-disable-next-line deprecation/deprecation
       anrIntegration,
       // eslint-disable-next-line deprecation/deprecation
    
  • packages/bun/src/index.ts+2 0 modified
    @@ -62,6 +62,8 @@ export {
       close,
       getSentryRelease,
       createGetModuleFromFilename,
    +  httpHeadersToSpanAttributes,
    +  winterCGHeadersToDict,
       // eslint-disable-next-line deprecation/deprecation
       anrIntegration,
       // eslint-disable-next-line deprecation/deprecation
    
  • packages/bun/src/integrations/bunserver.ts+6 0 modified
    @@ -3,6 +3,8 @@ import {
       captureException,
       continueTrace,
       defineIntegration,
    +  getClient,
    +  httpHeadersToSpanAttributes,
       isURLObjectRelative,
       parseStringToURLObject,
       SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,
    @@ -205,6 +207,10 @@ function wrapRequestHandler<T extends RouteHandler = RouteHandler>(
           routeName = route;
         }
     
    +    const client = getClient();
    +    const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false;
    +    Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), sendDefaultPii));
    +
         isolationScope.setSDKProcessingMetadata({
           normalizedRequest: {
             url: request.url,
    
  • packages/bun/test/integrations/bunserver.test.ts+64 0 modified
    @@ -47,6 +47,11 @@ describe('Bun Serve Integration', () => {
               'url.port': port.toString(),
               'url.scheme': 'http:',
               'url.domain': 'localhost',
    +          'http.request.header.accept': '*/*',
    +          'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +          'http.request.header.connection': 'keep-alive',
    +          'http.request.header.host': expect.any(String),
    +          'http.request.header.user_agent': expect.stringContaining('Bun'),
             },
             op: 'http.server',
             name: 'GET /users',
    @@ -81,6 +86,12 @@ describe('Bun Serve Integration', () => {
               'url.port': port.toString(),
               'url.scheme': 'http:',
               'url.domain': 'localhost',
    +          'http.request.header.accept': '*/*',
    +          'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +          'http.request.header.connection': 'keep-alive',
    +          'http.request.header.content_length': '0',
    +          'http.request.header.host': expect.any(String),
    +          'http.request.header.user_agent': expect.stringContaining('Bun'),
             },
             op: 'http.server',
             name: 'POST /',
    @@ -128,6 +139,59 @@ describe('Bun Serve Integration', () => {
         expect(startSpanSpy).toHaveBeenCalledTimes(1);
       });
     
    +  test('includes HTTP request headers as span attributes', async () => {
    +    const server = Bun.serve({
    +      async fetch(_req) {
    +        return new Response('Headers test!');
    +      },
    +      port,
    +    });
    +
    +    // Make request with custom headers
    +    await fetch(`http://localhost:${port}/api/test`, {
    +      method: 'POST',
    +      headers: {
    +        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    +        'Content-Type': 'application/json',
    +        'X-Custom-Header': 'custom-value',
    +        Accept: 'application/json, text/plain',
    +        Authorization: 'Bearer token123',
    +      },
    +      body: JSON.stringify({ test: 'data' }),
    +    });
    +
    +    await server.stop();
    +
    +    // Verify span was created with header attributes
    +    expect(startSpanSpy).toHaveBeenCalledTimes(1);
    +    expect(startSpanSpy).toHaveBeenLastCalledWith(
    +      expect.objectContaining({
    +        attributes: expect.objectContaining({
    +          'sentry.origin': 'auto.http.bun.serve',
    +          'http.request.method': 'POST',
    +          'sentry.source': 'url',
    +          'url.path': '/api/test',
    +          'url.full': `http://localhost:${port}/api/test`,
    +          'url.port': port.toString(),
    +          'url.scheme': 'http:',
    +          'url.domain': 'localhost',
    +          // HTTP headers as span attributes following OpenTelemetry semantic conventions
    +          'http.request.header.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    +          'http.request.header.content_type': 'application/json',
    +          'http.request.header.x_custom_header': 'custom-value',
    +          'http.request.header.accept': 'application/json, text/plain',
    +          'http.request.header.accept_encoding': 'gzip, deflate, br, zstd',
    +          'http.request.header.connection': 'keep-alive',
    +          'http.request.header.content_length': '15',
    +          'http.request.header.host': expect.any(String),
    +        }),
    +        op: 'http.server',
    +        name: 'POST /api/test',
    +      }),
    +      expect.any(Function),
    +    );
    +  });
    +
       test('skips span creation for OPTIONS and HEAD requests', async () => {
         const server = Bun.serve({
           async fetch(_req) {
    
  • packages/cloudflare/src/request.ts+5 0 modified
    @@ -4,10 +4,12 @@ import {
       continueTrace,
       flush,
       getHttpSpanDetailsFromUrlObject,
    +  httpHeadersToSpanAttributes,
       parseStringToURLObject,
       SEMANTIC_ATTRIBUTE_SENTRY_OP,
       setHttpStatus,
       startSpan,
    +  winterCGHeadersToDict,
       withIsolationScope,
     } from '@sentry/core';
     import type { CloudflareOptions } from './client';
    @@ -64,6 +66,9 @@ export function wrapRequestHandler(
           attributes['user_agent.original'] = userAgentHeader;
         }
     
    +    const sendDefaultPii = options.sendDefaultPii ?? false;
    +    Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), sendDefaultPii));
    +
         attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';
     
         addCloudResourceContext(isolationScope);
    
  • packages/cloudflare/test/request.test.ts+1 0 modified
    @@ -319,6 +319,7 @@ describe('withSentry', () => {
               'sentry.sample_rate': 1,
               'http.response.status_code': 200,
               'http.request.body.size': 10,
    +          'http.request.header.content_length': '10',
             },
             op: 'http.server',
             origin: 'auto.http.cloudflare',
    
  • packages/core/src/index.ts+1 0 modified
    @@ -92,6 +92,7 @@ export {
       httpRequestToRequestData,
       extractQueryParamsFromUrl,
       headersToDict,
    +  httpHeadersToSpanAttributes,
     } from './utils/request';
     export { DEFAULT_ENVIRONMENT } from './constants';
     export { addBreadcrumb } from './breadcrumbs';
    
  • packages/core/src/utils/request.ts+41 0 modified
    @@ -128,6 +128,47 @@ function getAbsoluteUrl({
       return undefined;
     }
     
    +// "-user" because otherwise it would match "user-agent"
    +const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user', 'password', 'key'];
    +
    +/**
    + * Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions.
    + * Header names are converted to the format: http.request.header.<key>
    + * where <key> is the header name in lowercase with dashes converted to underscores.
    + *
    + * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/#http-request-header
    + */
    +export function httpHeadersToSpanAttributes(
    +  headers: Record<string, string | string[] | undefined>,
    +  sendDefaultPii: boolean = false,
    +): Record<string, string> {
    +  const spanAttributes: Record<string, string> = {};
    +
    +  try {
    +    Object.entries(headers).forEach(([key, value]) => {
    +      if (value !== undefined) {
    +        const lowerCasedKey = key.toLowerCase();
    +
    +        if (!sendDefaultPii && SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))) {
    +          return;
    +        }
    +
    +        const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`;
    +
    +        if (Array.isArray(value)) {
    +          spanAttributes[normalizedKey] = value.map(v => (v !== null && v !== undefined ? String(v) : v)).join(';');
    +        } else if (typeof value === 'string') {
    +          spanAttributes[normalizedKey] = value;
    +        }
    +      }
    +    });
    +  } catch {
    +    // Return empty object if there's an error
    +  }
    +
    +  return spanAttributes;
    +}
    +
     /** Extract the query params from an URL. */
     export function extractQueryParamsFromUrl(url: string): string | undefined {
       // url is path and query string
    
  • packages/core/test/lib/utils/request.test.ts+285 0 modified
    @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
     import {
       extractQueryParamsFromUrl,
       headersToDict,
    +  httpHeadersToSpanAttributes,
       httpRequestToRequestData,
       winterCGHeadersToDict,
       winterCGRequestToRequestData,
    @@ -420,4 +421,288 @@ describe('request utils', () => {
           expect(extractQueryParamsFromUrl(url)).toEqual(expected);
         });
       });
    +
    +  describe('httpHeadersToSpanAttributes', () => {
    +    it('works with empty headers object', () => {
    +      expect(httpHeadersToSpanAttributes({})).toEqual({});
    +    });
    +
    +    it('converts single string header values to strings', () => {
    +      const headers = {
    +        'Content-Type': 'application/json',
    +        'user-agent': 'test-agent',
    +      };
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.content_type': 'application/json',
    +        'http.request.header.user_agent': 'test-agent',
    +      });
    +    });
    +
    +    it('handles array header values by joining with semicolons', () => {
    +      const headers = {
    +        'custom-header': ['value1', 'value2'],
    +        accept: ['application/json', 'text/html'],
    +      };
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.custom_header': 'value1;value2',
    +        'http.request.header.accept': 'application/json;text/html',
    +      });
    +    });
    +
    +    it('filters undefined values in arrays when joining', () => {
    +      const headers = {
    +        'undefined-values': [undefined, undefined],
    +        'valid-header': 'valid-value',
    +      } as any;
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.valid_header': 'valid-value',
    +        'http.request.header.undefined_values': ';',
    +      });
    +    });
    +
    +    it('ignores undefined header values', () => {
    +      const headers = {
    +        'valid-header': 'valid-value',
    +        'undefined-header': undefined,
    +      };
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.valid_header': 'valid-value',
    +      });
    +    });
    +
    +    it('adds empty array headers as empty string', () => {
    +      const headers = {
    +        'empty-header': [],
    +        'valid-header': 'valid-value',
    +      } as any;
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.empty_header': '',
    +        'http.request.header.valid_header': 'valid-value',
    +      });
    +    });
    +
    +    it('converts header names to lowercase and replaces dashes with underscores', () => {
    +      const headers = {
    +        'Content-Type': 'application/json',
    +        'X-CUSTOM-HEADER': 'custom-value',
    +        'user-Agent': 'test-agent',
    +        ACCEPT: 'text/html',
    +      };
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.content_type': 'application/json',
    +        'http.request.header.x_custom_header': 'custom-value',
    +        'http.request.header.user_agent': 'test-agent',
    +        'http.request.header.accept': 'text/html',
    +      });
    +    });
    +
    +    it('handles real-world headers', () => {
    +      const headers = {
    +        Host: 'example.com',
    +        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    +        Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    +        'Accept-Language': 'en-US,en;q=0.5',
    +        'Accept-Encoding': 'gzip, deflate',
    +        Connection: 'keep-alive',
    +        'Upgrade-Insecure-Requests': '1',
    +        'Cache-Control': 'no-cache',
    +        'X-Forwarded-For': '192.168.1.1',
    +      };
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.host': 'example.com',
    +        'http.request.header.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    +        'http.request.header.accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    +        'http.request.header.accept_language': 'en-US,en;q=0.5',
    +        'http.request.header.accept_encoding': 'gzip, deflate',
    +        'http.request.header.connection': 'keep-alive',
    +        'http.request.header.upgrade_insecure_requests': '1',
    +        'http.request.header.cache_control': 'no-cache',
    +        'http.request.header.x_forwarded_for': '192.168.1.1',
    +      });
    +    });
    +
    +    it('handles multiple values for the same header by joining with semicolons', () => {
    +      const headers = {
    +        'x-random-header': ['test=abc123', 'preferences=dark-mode', 'number=three'],
    +        Accept: ['application/json', 'text/html'],
    +      };
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.x_random_header': 'test=abc123;preferences=dark-mode;number=three',
    +        'http.request.header.accept': 'application/json;text/html',
    +      });
    +    });
    +
    +    it('handles headers with empty string values', () => {
    +      const headers = {
    +        'empty-header': '',
    +        'valid-header': 'valid-value',
    +      };
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.empty_header': '',
    +        'http.request.header.valid_header': 'valid-value',
    +      });
    +    });
    +
    +    it('returns empty object when processing invalid headers throws error', () => {
    +      // Create a headers object that will throw an error when iterated
    +      const headers = {};
    +      Object.defineProperty(headers, Symbol.iterator, {
    +        get() {
    +          throw new Error('Test error');
    +        },
    +      });
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({});
    +    });
    +
    +    it('stringifies non-string values (except null) in arrays and joins them', () => {
    +      const headers = {
    +        'mixed-types': ['string-value', 123, true, null],
    +      } as any;
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.mixed_types': 'string-value;123;true;',
    +      });
    +    });
    +
    +    it('ignores non-string and non-array header values', () => {
    +      const headers = {
    +        'string-header': 'valid-value',
    +        'number-header': 123,
    +        'boolean-header': true,
    +        'null-header': null,
    +        'object-header': { key: 'value' },
    +      } as any;
    +
    +      const result = httpHeadersToSpanAttributes(headers);
    +
    +      expect(result).toEqual({
    +        'http.request.header.string_header': 'valid-value',
    +      });
    +    });
    +
    +    describe('PII filtering', () => {
    +      it('filters out sensitive headers when sendDefaultPii is false (default)', () => {
    +        const headers = {
    +          'Content-Type': 'application/json',
    +          'User-Agent': 'test-agent',
    +          Authorization: 'Bearer secret-token',
    +          Cookie: 'session=abc123',
    +          'X-API-Key': 'api-key-123',
    +          'X-Auth-Token': 'auth-token-456',
    +        };
    +
    +        const result = httpHeadersToSpanAttributes(headers, false);
    +
    +        expect(result).toEqual({
    +          'http.request.header.content_type': 'application/json',
    +          'http.request.header.user_agent': 'test-agent',
    +          // Sensitive headers should be filtered out
    +        });
    +      });
    +
    +      it('includes sensitive headers when sendDefaultPii is true', () => {
    +        const headers = {
    +          'Content-Type': 'application/json',
    +          'User-Agent': 'test-agent',
    +          Authorization: 'Bearer secret-token',
    +          Cookie: 'session=abc123',
    +          'X-API-Key': 'api-key-123',
    +        };
    +
    +        const result = httpHeadersToSpanAttributes(headers, true);
    +
    +        expect(result).toEqual({
    +          'http.request.header.content_type': 'application/json',
    +          'http.request.header.user_agent': 'test-agent',
    +          'http.request.header.authorization': 'Bearer secret-token',
    +          'http.request.header.cookie': 'session=abc123',
    +          'http.request.header.x_api_key': 'api-key-123',
    +        });
    +      });
    +
    +      it('filters sensitive headers case-insensitively', () => {
    +        const headers = {
    +          AUTHORIZATION: 'Bearer secret-token',
    +          Cookie: 'session=abc123',
    +          'x-api-key': 'key-123',
    +          'Content-Type': 'application/json',
    +        };
    +
    +        const result = httpHeadersToSpanAttributes(headers, false);
    +
    +        expect(result).toEqual({
    +          'http.request.header.content_type': 'application/json',
    +        });
    +      });
    +
    +      it('filters comprehensive list of sensitive headers', () => {
    +        const headers = {
    +          'Content-Type': 'application/json',
    +          'User-Agent': 'test-agent',
    +          Accept: 'application/json',
    +          Host: 'example.com',
    +
    +          // Should be filtered
    +          Authorization: 'Bearer token',
    +          Cookie: 'session=123',
    +          'Set-Cookie': 'session=456',
    +          'X-API-Key': 'key',
    +          'X-Auth-Token': 'token',
    +          'X-Secret': 'secret',
    +          'x-secret-key': 'another-secret',
    +          'WWW-Authenticate': 'Basic',
    +          'Proxy-Authorization': 'Basic auth',
    +          'X-Access-Token': 'access',
    +          'X-CSRF-Token': 'csrf',
    +          'X-XSRF-Token': 'xsrf',
    +          'X-Session-Token': 'session',
    +          'X-Password': 'password',
    +          'X-Private-Key': 'private',
    +          'X-Forwarded-user': 'user',
    +          'X-Forwarded-authorization': 'auth',
    +        };
    +
    +        const result = httpHeadersToSpanAttributes(headers, false);
    +
    +        expect(result).toEqual({
    +          'http.request.header.content_type': 'application/json',
    +          'http.request.header.user_agent': 'test-agent',
    +          'http.request.header.accept': 'application/json',
    +          'http.request.header.host': 'example.com',
    +        });
    +      });
    +    });
    +  });
     });
    
  • packages/google-cloud-serverless/src/index.ts+2 0 modified
    @@ -42,6 +42,8 @@ export {
       close,
       getSentryRelease,
       createGetModuleFromFilename,
    +  httpHeadersToSpanAttributes,
    +  winterCGHeadersToDict,
       // eslint-disable-next-line deprecation/deprecation
       anrIntegration,
       // eslint-disable-next-line deprecation/deprecation
    
  • packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts+2 0 modified
    @@ -15,6 +15,7 @@ import {
     } from '@sentry/core';
     import type { NextApiRequest } from 'next';
     import type { AugmentedNextApiResponse, NextApiHandler } from '../types';
    +import { addHeadersAsAttributes } from '../utils/addHeadersAsAttributes';
     import { flushSafelyWithTimeout } from '../utils/responseEnd';
     import { dropNextjsRootContext, escapeNextjsTracing } from '../utils/tracingUtils';
     
    @@ -87,6 +88,7 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz
                       attributes: {
                         [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
                         [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nextjs',
    +                    ...addHeadersAsAttributes(normalizedRequest.headers || {}),
                       },
                     },
                     async span => {
    
  • packages/nextjs/src/common/utils/addHeadersAsAttributes.ts+30 0 added
    @@ -0,0 +1,30 @@
    +import type { Span, WebFetchHeaders } from '@sentry/core';
    +import { getClient, httpHeadersToSpanAttributes, winterCGHeadersToDict } from '@sentry/core';
    +
    +/**
    + * Extracts HTTP request headers as span attributes and optionally applies them to a span.
    + */
    +export function addHeadersAsAttributes(
    +  headers: WebFetchHeaders | Headers | Record<string, string | string[] | undefined> | undefined,
    +  span?: Span,
    +): Record<string, string> {
    +  if (!headers) {
    +    return {};
    +  }
    +
    +  const client = getClient();
    +  const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false;
    +
    +  const headersDict: Record<string, string | string[] | undefined> =
    +    headers instanceof Headers || (typeof headers === 'object' && 'get' in headers)
    +      ? winterCGHeadersToDict(headers as Headers)
    +      : headers;
    +
    +  const headerAttributes = httpHeadersToSpanAttributes(headersDict, sendDefaultPii);
    +
    +  if (span) {
    +    span.setAttributes(headerAttributes);
    +  }
    +
    +  return headerAttributes;
    +}
    
  • packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts+6 0 modified
    @@ -22,6 +22,7 @@ import {
     import type { GenerationFunctionContext } from '../common/types';
     import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils';
     import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached';
    +import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes';
     import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
     import { getSanitizedRequestUrl } from './utils/urls';
     import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils';
    @@ -63,6 +64,11 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
     
           const headersDict = headers ? winterCGHeadersToDict(headers) : undefined;
     
    +      if (activeSpan) {
    +        const rootSpan = getRootSpan(activeSpan);
    +        addHeadersAsAttributes(headers, rootSpan);
    +      }
    +
           let data: Record<string, unknown> | undefined = undefined;
           if (getClient()?.getOptions().sendDefaultPii) {
             const props: unknown = args[0];
    
  • packages/nextjs/src/common/wrapMiddlewareWithSentry.ts+6 0 modified
    @@ -13,6 +13,7 @@ import {
       winterCGRequestToRequestData,
       withIsolationScope,
     } from '@sentry/core';
    +import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes';
     import { flushSafelyWithTimeout } from '../common/utils/responseEnd';
     import type { EdgeRouteHandler } from '../edge/types';
     
    @@ -59,13 +60,16 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
     
             let spanName: string;
             let spanSource: TransactionSource;
    +        let headerAttributes: Record<string, string> = {};
     
             if (req instanceof Request) {
               isolationScope.setSDKProcessingMetadata({
                 normalizedRequest: winterCGRequestToRequestData(req),
               });
               spanName = `middleware ${req.method} ${new URL(req.url).pathname}`;
               spanSource = 'url';
    +
    +          headerAttributes = addHeadersAsAttributes(req.headers);
             } else {
               spanName = 'middleware';
               spanSource = 'component';
    @@ -84,6 +88,7 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
               const rootSpan = getRootSpan(activeSpan);
               if (rootSpan) {
                 setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope);
    +            rootSpan.setAttributes(headerAttributes);
               }
             }
     
    @@ -94,6 +99,7 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
                 attributes: {
                   [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource,
                   [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrapMiddlewareWithSentry',
    +              ...headerAttributes,
                 },
               },
               () => {
    
  • packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts+6 0 modified
    @@ -19,6 +19,7 @@ import {
     } from '@sentry/core';
     import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils';
     import type { RouteHandlerContext } from './types';
    +import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes';
     import { flushSafelyWithTimeout } from './utils/responseEnd';
     import { commonObjectToIsolationScope } from './utils/tracingUtils';
     
    @@ -39,6 +40,10 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
           const activeSpan = getActiveSpan();
           const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
     
    +      if (rootSpan && process.env.NEXT_RUNTIME !== 'edge') {
    +        addHeadersAsAttributes(headers, rootSpan);
    +      }
    +
           let edgeRuntimeIsolationScopeOverride: Scope | undefined;
           if (rootSpan && process.env.NEXT_RUNTIME === 'edge') {
             const isolationScope = commonObjectToIsolationScope(headers);
    @@ -50,6 +55,7 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
             rootSpan.updateName(`${method} ${parameterizedRoute}`);
             rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
             rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server');
    +        addHeadersAsAttributes(headers, rootSpan);
           }
     
           return withIsolationScope(
    
  • packages/nextjs/src/common/wrapServerComponentWithSentry.ts+6 0 modified
    @@ -24,6 +24,7 @@ import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/
     import type { ServerComponentContext } from '../common/types';
     import { flushSafelyWithTimeout } from '../common/utils/responseEnd';
     import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached';
    +import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes';
     import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
     import { getSanitizedRequestUrl } from './utils/urls';
     import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils';
    @@ -61,6 +62,11 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
     
           const headersDict = context.headers ? winterCGHeadersToDict(context.headers) : undefined;
     
    +      if (activeSpan) {
    +        const rootSpan = getRootSpan(activeSpan);
    +        addHeadersAsAttributes(context.headers, rootSpan);
    +      }
    +
           let params: Record<string, string> | undefined = undefined;
     
           if (getClient()?.getOptions().sendDefaultPii) {
    
  • packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts+7 0 modified
    @@ -13,6 +13,7 @@ import {
       winterCGRequestToRequestData,
       withIsolationScope,
     } from '@sentry/core';
    +import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes';
     import { flushSafelyWithTimeout } from '../common/utils/responseEnd';
     import type { EdgeRouteHandler } from './types';
     
    @@ -31,11 +32,15 @@ export function wrapApiHandlerWithSentry<H extends EdgeRouteHandler>(
             const req: unknown = args[0];
             const currentScope = getCurrentScope();
     
    +        let headerAttributes: Record<string, string> = {};
    +
             if (req instanceof Request) {
               isolationScope.setSDKProcessingMetadata({
                 normalizedRequest: winterCGRequestToRequestData(req),
               });
               currentScope.setTransactionName(`${req.method} ${parameterizedRoute}`);
    +
    +          headerAttributes = addHeadersAsAttributes(req.headers);
             } else {
               currentScope.setTransactionName(`handler (${parameterizedRoute})`);
             }
    @@ -58,6 +63,7 @@ export function wrapApiHandlerWithSentry<H extends EdgeRouteHandler>(
                 rootSpan.setAttributes({
                   [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
                   [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
    +              ...headerAttributes,
                 });
                 setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope);
               }
    @@ -74,6 +80,7 @@ export function wrapApiHandlerWithSentry<H extends EdgeRouteHandler>(
                 attributes: {
                   [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
                   [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrapApiHandlerWithSentry',
    +              ...headerAttributes,
                 },
               },
               () => {
    
  • packages/nextjs/test/config/wrappers.test.ts+6 0 modified
    @@ -53,11 +53,14 @@ describe('data-fetching function wrappers should not create manual spans', () =>
     
       test('wrapped function sets route backfill attribute when called within an active span', async () => {
         const mockSetAttribute = vi.fn();
    +    const mockSetAttributes = vi.fn();
         const mockGetActiveSpan = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({
           setAttribute: mockSetAttribute,
    +      setAttributes: mockSetAttributes,
         } as any);
         const mockGetRootSpan = vi.spyOn(SentryCore, 'getRootSpan').mockReturnValue({
           setAttribute: mockSetAttribute,
    +      setAttributes: mockSetAttributes,
         } as any);
     
         const origFunction = vi.fn(async () => ({ props: {} }));
    @@ -72,11 +75,14 @@ describe('data-fetching function wrappers should not create manual spans', () =>
     
       test('wrapped function does not set route backfill attribute for /_error route', async () => {
         const mockSetAttribute = vi.fn();
    +    const mockSetAttributes = vi.fn();
         const mockGetActiveSpan = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({
           setAttribute: mockSetAttribute,
    +      setAttributes: mockSetAttributes,
         } as any);
         const mockGetRootSpan = vi.spyOn(SentryCore, 'getRootSpan').mockReturnValue({
           setAttribute: mockSetAttribute,
    +      setAttributes: mockSetAttributes,
         } as any);
     
         const origFunction = vi.fn(async () => ({ props: {} }));
    
  • packages/node-core/src/integrations/http/incoming-requests.ts+4 0 modified
    @@ -19,6 +19,7 @@ import {
       getCurrentScope,
       getIsolationScope,
       getSpanStatusFromHttpCode,
    +  httpHeadersToSpanAttributes,
       httpRequestToRequestData,
       parseStringToURLObject,
       SEMANTIC_ATTRIBUTE_SENTRY_OP,
    @@ -191,6 +192,8 @@ export function instrumentServer(
               const tracer = client.tracer;
               const scheme = fullUrl.startsWith('https') ? 'https' : 'http';
     
    +          const shouldSendDefaultPii = client?.getOptions().sendDefaultPii ?? false;
    +
               // We use the plain tracer.startSpan here so we can pass the span kind
               const span = tracer.startSpan(bestEffortTransactionName, {
                 kind: SpanKind.SERVER,
    @@ -211,6 +214,7 @@ export function instrumentServer(
                   'http.flavor': httpVersion,
                   'net.transport': httpVersion?.toUpperCase() === 'QUIC' ? 'ip_udp' : 'ip_tcp',
                   ...getRequestContentLengthAttribute(request),
    +              ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, shouldSendDefaultPii),
                 },
               });
     
    
  • packages/node/src/index.ts+2 0 modified
    @@ -92,6 +92,8 @@ export {
       getIsolationScope,
       getTraceData,
       getTraceMetaTags,
    +  httpHeadersToSpanAttributes,
    +  winterCGHeadersToDict,
       continueTrace,
       withScope,
       withIsolationScope,
    
  • packages/remix/src/server/instrumentServer.ts+6 0 modified
    @@ -23,6 +23,7 @@ import {
       getRootSpan,
       getTraceData,
       hasSpansEnabled,
    +  httpHeadersToSpanAttributes,
       isNodeEnv,
       loadModule,
       SEMANTIC_ATTRIBUTE_SENTRY_OP,
    @@ -31,6 +32,7 @@ import {
       setHttpStatus,
       spanToJSON,
       startSpan,
    +  winterCGHeadersToDict,
       winterCGRequestToRequestData,
       withIsolationScope,
     } from '@sentry/core';
    @@ -324,6 +326,10 @@ function wrapRequestHandler<T extends ServerBuild | (() => ServerBuild | Promise
                       [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
                       [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
                       method: request.method,
    +                  ...httpHeadersToSpanAttributes(
    +                    winterCGHeadersToDict(request.headers),
    +                    clientOptions.sendDefaultPii ?? false,
    +                  ),
                     },
                   },
                   async span => {
    
  • packages/sveltekit/src/server-common/handle.ts+11 0 modified
    @@ -3,17 +3,20 @@ import {
       continueTrace,
       debug,
       flushIfServerless,
    +  getClient,
       getCurrentScope,
       getDefaultIsolationScope,
       getIsolationScope,
       getTraceMetaTags,
    +  httpHeadersToSpanAttributes,
       SEMANTIC_ATTRIBUTE_SENTRY_OP,
       SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
       SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
       setHttpStatus,
       spanToJSON,
       startSpan,
       updateSpanName,
    +  winterCGHeadersToDict,
       winterCGRequestToRequestData,
       withIsolationScope,
     } from '@sentry/core';
    @@ -176,6 +179,10 @@ async function instrumentHandle(
               [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit',
               [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeName ? 'route' : 'url',
               'sveltekit.tracing.original_name': originalName,
    +          ...httpHeadersToSpanAttributes(
    +            winterCGHeadersToDict(event.request.headers),
    +            getClient()?.getOptions().sendDefaultPii ?? false,
    +          ),
             });
           }
     
    @@ -201,6 +208,10 @@ async function instrumentHandle(
                   [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit',
                   [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url',
                   'http.method': event.request.method,
    +              ...httpHeadersToSpanAttributes(
    +                winterCGHeadersToDict(event.request.headers),
    +                getClient()?.getOptions().sendDefaultPii ?? false,
    +              ),
                 },
                 name: routeName,
               },
    
  • .size-limit.js+1 1 modified
    @@ -233,7 +233,7 @@ module.exports = [
         import: createImport('init'),
         ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
         gzip: true,
    -    limit: '152 KB',
    +    limit: '154 KB',
       },
       {
         name: '@sentry/node - without tracing',
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.