Skip to content

Commit 260f1a1

Browse files
committed
fix(utils): Streamline IP capturing on incoming requests
1 parent 9ed2112 commit 260f1a1

File tree

4 files changed

+356
-62
lines changed

4 files changed

+356
-62
lines changed

packages/remix/src/utils/web-fetch.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable complexity */
21
// Based on Remix's implementation of Fetch API
32
// https://github.com/remix-run/web-std-io/blob/d2a003fe92096aaf97ab2a618b74875ccaadc280/packages/fetch/
43
// The MIT License (MIT)
@@ -23,10 +22,6 @@
2322
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2423
// SOFTWARE.
2524

26-
import { logger } from '@sentry/utils';
27-
28-
import { DEBUG_BUILD } from './debug-build';
29-
import { getClientIPAddress } from './vendor/getIpAddress';
3025
import type { RemixRequest } from './vendor/types';
3126

3227
/*
@@ -124,15 +119,6 @@ export const normalizeRemixRequest = (request: RemixRequest): Record<string, any
124119
headers.set('Connection', 'close');
125120
}
126121

127-
let ip;
128-
129-
// Using a try block here not to break the whole request if we can't get the IP address
130-
try {
131-
ip = getClientIPAddress(headers);
132-
} catch (e) {
133-
DEBUG_BUILD && logger.warn('Could not get client IP address', e);
134-
}
135-
136122
// HTTP-network fetch step 4.2
137123
// chunked encoding is handled by Node.js
138124
const search = getSearch(parsedURL);
@@ -156,9 +142,6 @@ export const normalizeRemixRequest = (request: RemixRequest): Record<string, any
156142

157143
// [SENTRY] For compatibility with Sentry SDK RequestData parser, adding `originalUrl` property.
158144
originalUrl: parsedURL.href,
159-
160-
// [SENTRY] Adding `ip` property if found inside headers.
161-
ip,
162145
};
163146

164147
return requestOptions;

packages/utils/src/requestdata.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { isPlainObject, isString } from './is';
1313
import { logger } from './logger';
1414
import { normalize } from './normalize';
1515
import { stripUrlQueryAndFragment } from './url';
16+
import { getClientIPAddress, ipHeaderNames } from './vendor/getIpAddress';
1617

1718
const DEFAULT_INCLUDES = {
1819
ip: false,
@@ -98,7 +99,6 @@ export function extractPathForTransaction(
9899
return [name, source];
99100
}
100101

101-
/** JSDoc */
102102
function extractTransaction(req: PolymorphicRequest, type: boolean | TransactionNamingScheme): string {
103103
switch (type) {
104104
case 'path': {
@@ -116,7 +116,6 @@ function extractTransaction(req: PolymorphicRequest, type: boolean | Transaction
116116
}
117117
}
118118

119-
/** JSDoc */
120119
function extractUserData(
121120
user: {
122121
[key: string]: unknown;
@@ -146,17 +145,16 @@ function extractUserData(
146145
*/
147146
export function extractRequestData(
148147
req: PolymorphicRequest,
149-
options?: {
148+
options: {
150149
include?: string[];
151-
},
150+
} = {},
152151
): ExtractedNodeRequestData {
153-
const { include = DEFAULT_REQUEST_INCLUDES } = options || {};
154-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
155-
const requestData: { [key: string]: any } = {};
152+
const { include = DEFAULT_REQUEST_INCLUDES } = options;
153+
const requestData: { [key: string]: unknown } = {};
156154

157155
// headers:
158156
// node, express, koa, nextjs: req.headers
159-
const headers = (req.headers || {}) as {
157+
const headers = (req.headers || {}) as typeof req.headers & {
160158
host?: string;
161159
cookie?: string;
162160
};
@@ -191,6 +189,14 @@ export function extractRequestData(
191189
delete (requestData.headers as { cookie?: string }).cookie;
192190
}
193191

192+
// Remove IP headers in case IP data should not be included in the event
193+
if (!include.includes('ip')) {
194+
ipHeaderNames.forEach(ipHeaderName => {
195+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
196+
delete (requestData.headers as Record<string, unknown>)[ipHeaderName];
197+
});
198+
}
199+
194200
break;
195201
}
196202
case 'method': {
@@ -264,9 +270,12 @@ export function addRequestDataToEvent(
264270
};
265271

266272
if (include.request) {
267-
const extractedRequestData = Array.isArray(include.request)
268-
? extractRequestData(req, { include: include.request })
269-
: extractRequestData(req);
273+
const includeRequest = Array.isArray(include.request) ? [...include.request] : [...DEFAULT_REQUEST_INCLUDES];
274+
if (include.ip) {
275+
includeRequest.push('ip');
276+
}
277+
278+
const extractedRequestData = extractRequestData(req, { include: includeRequest });
270279

271280
event.request = {
272281
...event.request,
@@ -288,8 +297,9 @@ export function addRequestDataToEvent(
288297
// client ip:
289298
// node, nextjs: req.socket.remoteAddress
290299
// express, koa: req.ip
300+
// It may also be sent by proxies as specified in X-Forwarded-For or similar headers
291301
if (include.ip) {
292-
const ip = req.ip || (req.socket && req.socket.remoteAddress);
302+
const ip = (req.headers && getClientIPAddress(req.headers)) || req.ip || (req.socket && req.socket.remoteAddress);
293303
if (ip) {
294304
event.user = {
295305
...event.user,

packages/remix/src/utils/vendor/getIpAddress.ts renamed to packages/utils/src/vendor/getIpAddress.ts

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,58 +23,46 @@
2323
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2424
// SOFTWARE.
2525

26-
import { isIP } from 'net';
26+
// The headers to check, in priority order
27+
export const ipHeaderNames = [
28+
'X-Client-IP',
29+
'X-Forwarded-For',
30+
'Fly-Client-IP',
31+
'CF-Connecting-IP',
32+
'Fastly-Client-Ip',
33+
'True-Client-Ip',
34+
'X-Real-IP',
35+
'X-Cluster-Client-IP',
36+
'X-Forwarded',
37+
'Forwarded-For',
38+
'Forwarded',
39+
'X-Vercel-Forwarded-For',
40+
];
2741

2842
/**
2943
* Get the IP address of the client sending a request.
3044
*
3145
* It receives a Request headers object and use it to get the
3246
* IP address from one of the following headers in order.
3347
*
34-
* - X-Client-IP
35-
* - X-Forwarded-For
36-
* - Fly-Client-IP
37-
* - CF-Connecting-IP
38-
* - Fastly-Client-Ip
39-
* - True-Client-Ip
40-
* - X-Real-IP
41-
* - X-Cluster-Client-IP
42-
* - X-Forwarded
43-
* - Forwarded-For
44-
* - Forwarded
45-
*
4648
* If the IP address is valid, it will be returned. Otherwise, null will be
4749
* returned.
4850
*
4951
* If the header values contains more than one IP address, the first valid one
5052
* will be returned.
5153
*/
52-
export function getClientIPAddress(headers: Headers): string | null {
53-
// The headers to check, in priority order
54-
const headerNames = [
55-
'X-Client-IP',
56-
'X-Forwarded-For',
57-
'Fly-Client-IP',
58-
'CF-Connecting-IP',
59-
'Fastly-Client-Ip',
60-
'True-Client-Ip',
61-
'X-Real-IP',
62-
'X-Cluster-Client-IP',
63-
'X-Forwarded',
64-
'Forwarded-For',
65-
'Forwarded',
66-
];
67-
54+
export function getClientIPAddress(headers: { [key: string]: string | string[] | undefined }): string | null {
6855
// This will end up being Array<string | string[] | undefined | null> because of the various possible values a header
6956
// can take
70-
const headerValues = headerNames.map((headerName: string) => {
71-
const value = headers.get(headerName);
57+
const headerValues = ipHeaderNames.map((headerName: string) => {
58+
const rawValue = headers[headerName];
59+
const value = Array.isArray(rawValue) ? rawValue.join(';') : rawValue;
7260

7361
if (headerName === 'Forwarded') {
7462
return parseForwardedHeader(value);
7563
}
7664

77-
return value?.split(',').map((v: string) => v.trim());
65+
return value && value.split(',').map((v: string) => v.trim());
7866
});
7967

8068
// Flatten the array and filter out any falsy entries
@@ -92,7 +80,7 @@ export function getClientIPAddress(headers: Headers): string | null {
9280
return ipAddress || null;
9381
}
9482

95-
function parseForwardedHeader(value: string | null): string | null {
83+
function parseForwardedHeader(value: string | null | undefined): string | null {
9684
if (!value) {
9785
return null;
9886
}
@@ -105,3 +93,31 @@ function parseForwardedHeader(value: string | null): string | null {
10593

10694
return null;
10795
}
96+
97+
//
98+
/**
99+
* Custom method instead of importing this from `net` package, as this only exists in node
100+
* Accepts:
101+
* 127.0.0.1
102+
* 192.168.1.1
103+
* 192.168.1.255
104+
* 255.255.255.255
105+
* 10.1.1.1
106+
* 0.0.0.0
107+
*
108+
* Rejects:
109+
* 1.1.1.01
110+
* 30.168.1.255.1
111+
* 127.1
112+
* 192.168.1.256
113+
* -1.2.3.4
114+
* 1.1.1.1.
115+
* 3...3
116+
* 192.168.1.099
117+
*/
118+
function isIP(str: string): boolean {
119+
const regex =
120+
/^((?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/;
121+
122+
return regex.test(str);
123+
}

0 commit comments

Comments
 (0)