Skip to content

Commit ff81739

Browse files
authored
Merge pull request #16694 from getsentry/prepare-release/9.31.0
meta(changelog): Update changelog for 9.31.0
2 parents 7040a15 + 488cca2 commit ff81739

File tree

4 files changed

+220
-2
lines changed

4 files changed

+220
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Adds the ability to pass custom `scope` and `client` parameters to the `getTrace
1818

1919
### Other Changes
2020

21+
- feat(core): Add support for `x-forwarded-host` and `x-forwarded-proto` headers ([#16687](https://github.com/getsentry/sentry-javascript/pull/16687))
2122
- deps: Remove unused `@sentry/opentelemetry` dependency ([#16677](https://github.com/getsentry/sentry-javascript/pull/16677))
2223
- deps: Update all bundler plugin instances to latest & allow caret ranges ([#16641](https://github.com/getsentry/sentry-javascript/pull/16641))
2324
- feat(deps): Bump @prisma/instrumentation from 6.8.2 to 6.9.0 ([#16608](https://github.com/getsentry/sentry-javascript/pull/16608))

packages/core/src/utils/request.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,15 @@ export function httpRequestToRequestData(request: {
7474
};
7575
}): RequestEventData {
7676
const headers = request.headers || {};
77-
const host = typeof headers.host === 'string' ? headers.host : undefined;
78-
const protocol = request.protocol || (request.socket?.encrypted ? 'https' : 'http');
77+
78+
// Check for x-forwarded-host first, then fall back to host header
79+
const forwardedHost = typeof headers['x-forwarded-host'] === 'string' ? headers['x-forwarded-host'] : undefined;
80+
const host = forwardedHost || (typeof headers.host === 'string' ? headers.host : undefined);
81+
82+
// Check for x-forwarded-proto first, then fall back to existing protocol detection
83+
const forwardedProto = typeof headers['x-forwarded-proto'] === 'string' ? headers['x-forwarded-proto'] : undefined;
84+
const protocol = forwardedProto || request.protocol || (request.socket?.encrypted ? 'https' : 'http');
85+
7986
const url = request.url || '';
8087

8188
const absoluteUrl = getAbsoluteUrl({

packages/core/test/lib/utils/request.test.ts

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,215 @@ describe('request utils', () => {
198198
data: { xx: 'a', yy: 'z' },
199199
});
200200
});
201+
202+
describe('x-forwarded headers support', () => {
203+
it('should prioritize x-forwarded-proto header over explicit protocol parameter', () => {
204+
const actual = httpRequestToRequestData({
205+
url: '/test',
206+
headers: {
207+
host: 'example.com',
208+
'x-forwarded-proto': 'https',
209+
},
210+
protocol: 'http',
211+
});
212+
213+
expect(actual).toEqual({
214+
url: 'https://example.com/test',
215+
headers: {
216+
host: 'example.com',
217+
'x-forwarded-proto': 'https',
218+
},
219+
});
220+
});
221+
222+
it('should prioritize x-forwarded-proto header even when downgrading from https to http', () => {
223+
const actual = httpRequestToRequestData({
224+
url: '/test',
225+
headers: {
226+
host: 'example.com',
227+
'x-forwarded-proto': 'http',
228+
},
229+
protocol: 'https',
230+
});
231+
232+
expect(actual).toEqual({
233+
url: 'http://example.com/test',
234+
headers: {
235+
host: 'example.com',
236+
'x-forwarded-proto': 'http',
237+
},
238+
});
239+
});
240+
241+
it('should prioritize x-forwarded-proto header over socket encryption detection', () => {
242+
const actual = httpRequestToRequestData({
243+
url: '/test',
244+
headers: {
245+
host: 'example.com',
246+
'x-forwarded-proto': 'https',
247+
},
248+
socket: {
249+
encrypted: false,
250+
},
251+
});
252+
253+
expect(actual).toEqual({
254+
url: 'https://example.com/test',
255+
headers: {
256+
host: 'example.com',
257+
'x-forwarded-proto': 'https',
258+
},
259+
});
260+
});
261+
262+
it('should prioritize x-forwarded-host header over standard host header', () => {
263+
const actual = httpRequestToRequestData({
264+
url: '/test',
265+
headers: {
266+
host: 'localhost:3000',
267+
'x-forwarded-host': 'example.com',
268+
'x-forwarded-proto': 'https',
269+
},
270+
});
271+
272+
expect(actual).toEqual({
273+
url: 'https://example.com/test',
274+
headers: {
275+
host: 'localhost:3000',
276+
'x-forwarded-host': 'example.com',
277+
'x-forwarded-proto': 'https',
278+
},
279+
});
280+
});
281+
282+
it('should construct URL correctly when both x-forwarded-proto and x-forwarded-host are present', () => {
283+
const actual = httpRequestToRequestData({
284+
method: 'POST',
285+
url: '/api/test?param=value',
286+
headers: {
287+
host: 'localhost:3000',
288+
'x-forwarded-host': 'api.example.com',
289+
'x-forwarded-proto': 'https',
290+
'content-type': 'application/json',
291+
},
292+
protocol: 'http',
293+
});
294+
295+
expect(actual).toEqual({
296+
method: 'POST',
297+
url: 'https://api.example.com/api/test?param=value',
298+
query_string: 'param=value',
299+
headers: {
300+
host: 'localhost:3000',
301+
'x-forwarded-host': 'api.example.com',
302+
'x-forwarded-proto': 'https',
303+
'content-type': 'application/json',
304+
},
305+
});
306+
});
307+
308+
it('should fall back to standard headers when x-forwarded headers are not present', () => {
309+
const actual = httpRequestToRequestData({
310+
url: '/test',
311+
headers: {
312+
host: 'example.com',
313+
},
314+
protocol: 'https',
315+
});
316+
317+
expect(actual).toEqual({
318+
url: 'https://example.com/test',
319+
headers: {
320+
host: 'example.com',
321+
},
322+
});
323+
});
324+
325+
it('should ignore x-forwarded headers when they contain non-string values', () => {
326+
const actual = httpRequestToRequestData({
327+
url: '/test',
328+
headers: {
329+
host: 'example.com',
330+
'x-forwarded-host': ['forwarded.example.com'] as any,
331+
'x-forwarded-proto': ['https'] as any,
332+
},
333+
protocol: 'http',
334+
});
335+
336+
expect(actual).toEqual({
337+
url: 'http://example.com/test',
338+
headers: {
339+
host: 'example.com',
340+
},
341+
});
342+
});
343+
344+
it('should correctly transform localhost request to public URL using x-forwarded headers', () => {
345+
const actual = httpRequestToRequestData({
346+
method: 'GET',
347+
url: '/',
348+
headers: {
349+
host: 'localhost:3000',
350+
'x-forwarded-proto': 'https',
351+
'x-forwarded-host': 'example.com',
352+
},
353+
});
354+
355+
expect(actual).toEqual({
356+
method: 'GET',
357+
url: 'https://example.com/',
358+
headers: {
359+
host: 'localhost:3000',
360+
'x-forwarded-proto': 'https',
361+
'x-forwarded-host': 'example.com',
362+
},
363+
});
364+
});
365+
366+
it('should respect x-forwarded-proto even when it downgrades from encrypted socket', () => {
367+
const actual = httpRequestToRequestData({
368+
url: '/test',
369+
headers: {
370+
host: 'example.com',
371+
'x-forwarded-proto': 'http',
372+
},
373+
socket: {
374+
encrypted: true,
375+
},
376+
});
377+
378+
expect(actual).toEqual({
379+
url: 'http://example.com/test',
380+
headers: {
381+
host: 'example.com',
382+
'x-forwarded-proto': 'http',
383+
},
384+
});
385+
});
386+
387+
it('should preserve query parameters when constructing URL with x-forwarded headers', () => {
388+
const actual = httpRequestToRequestData({
389+
method: 'GET',
390+
url: '/search?q=test&category=api',
391+
headers: {
392+
host: 'localhost:8080',
393+
'x-forwarded-host': 'search.example.com',
394+
'x-forwarded-proto': 'https',
395+
},
396+
});
397+
398+
expect(actual).toEqual({
399+
method: 'GET',
400+
url: 'https://search.example.com/search?q=test&category=api',
401+
query_string: 'q=test&category=api',
402+
headers: {
403+
host: 'localhost:8080',
404+
'x-forwarded-host': 'search.example.com',
405+
'x-forwarded-proto': 'https',
406+
},
407+
});
408+
});
409+
});
201410
});
202411

203412
describe('extractQueryParamsFromUrl', () => {

packages/remix/test/integration/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@sentry/browser": "file:../../../browser",
2828
"@sentry/core": "file:../../../core",
2929
"@sentry/node": "file:../../../node",
30+
"@sentry/opentelemetry": "file:../../../opentelemetry",
3031
"@sentry/react": "file:../../../react",
3132
"@sentry-internal/browser-utils": "file:../../../browser-utils",
3233
"@sentry-internal/replay": "file:../../../replay-internal",

0 commit comments

Comments
 (0)