Skip to content

Commit aa97143

Browse files
committed
feat(core): Forward the client IP of tunnel requests to Sentry
1 parent a3eddb6 commit aa97143

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

‎packages/core/src/utils/tunnel.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,16 @@ function forwardHeaders(request: Request): Record<string, string> {
301301
headers['Content-Length'] = contentLength;
302302
}
303303

304+
// Relay infers the user's IP and geo from the sender, which is now this server. Proxies and CDNs put the
305+
// visitor's IP in these headers, in the order Relay itself reads them.
306+
const clientIp =
307+
request.headers.get('cf-connecting-ip') ||
308+
request.headers.get('x-vercel-forwarded-for') ||
309+
request.headers.get('x-forwarded-for');
310+
if (clientIp) {
311+
headers['X-Sentry-Forwarded-For'] = clientIp;
312+
}
313+
304314
return headers;
305315
}
306316

‎packages/core/test/lib/utils/tunnel.test.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,30 @@ describe('handleTunnelRequest', () => {
9898
);
9999
});
100100

101+
it.each([
102+
['cf-connecting-ip', { 'cf-connecting-ip': '1.1.1.1', 'x-forwarded-for': '2.2.2.2' }, '1.1.1.1'],
103+
['x-vercel-forwarded-for', { 'x-vercel-forwarded-for': '3.3.3.3', 'x-forwarded-for': '2.2.2.2' }, '3.3.3.3'],
104+
['x-forwarded-for', { 'x-forwarded-for': '2.2.2.2, 10.0.0.1' }, '2.2.2.2, 10.0.0.1'],
105+
])('forwards the client IP from %s', async (_, incomingHeaders, forwardedFor) => {
106+
fetchMock.mockResolvedValueOnce(new Response('ok'));
107+
108+
await handleTunnelRequest({
109+
request: new Request('http://localhost/tunnel', {
110+
method: 'POST',
111+
body: `${JSON.stringify({ dsn: TEST_DSN })}\n`,
112+
headers: incomingHeaders,
113+
}),
114+
allowedDsns: [TEST_DSN],
115+
});
116+
117+
expect(fetchMock).toHaveBeenCalledWith(
118+
expect.any(String),
119+
expect.objectContaining({
120+
headers: expect.objectContaining({ 'X-Sentry-Forwarded-For': forwardedFor }),
121+
}),
122+
);
123+
});
124+
101125
it('rejects a disallowed DSN after reading only the envelope header', async () => {
102126
const result = await handleTunnelRequest({
103127
request: makeEndlessRequest(`${JSON.stringify({ dsn: 'https://other@example.com/9999' })}\n`),

0 commit comments

Comments
 (0)