Skip to content

Commit 78e3397

Browse files
committed
Sanitize network errors
1 parent 049aa87 commit 78e3397

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

extensions/copilot/src/extension/log/vscode-node/test/sanitizeNetworkError.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ describe('sanitizeNetworkErrorForTelemetry', () => {
3939
);
4040
});
4141

42+
test('strips credentials with @ in URL userinfo', () => {
43+
expect(sanitizeNetworkErrorForTelemetry(
44+
'https://fictional-user@example.com:fictional-pass@proxy.fictional.example.com:8080/path'
45+
)).toBe(
46+
'https://<credentials>@<host>:8080/path'
47+
);
48+
});
49+
4250
test('replaces IPv4 addresses', () => {
4351
expect(sanitizeNetworkErrorForTelemetry(
4452
'connect ETIMEDOUT 10.20.30.40:443'
@@ -91,6 +99,14 @@ describe('sanitizeNetworkErrorForTelemetry', () => {
9199
);
92100
});
93101

102+
test('replaces full IPv6 addresses', () => {
103+
expect(sanitizeNetworkErrorForTelemetry(
104+
'connect ENETUNREACH 2001:db8:85a3:0:0:8a2e:370:7334:443'
105+
)).toBe(
106+
'connect ENETUNREACH <ip>:443'
107+
);
108+
});
109+
94110
test('does not match non-IPv6 double colons', () => {
95111
expect(sanitizeNetworkErrorForTelemetry(
96112
'net::ERR_SOCKET_NOT_CONNECTED'
@@ -115,6 +131,30 @@ describe('sanitizeNetworkErrorForTelemetry', () => {
115131
);
116132
});
117133

134+
test('strips credentials with @ in password', () => {
135+
expect(sanitizeNetworkErrorForTelemetry(
136+
'Failed to establish a socket connection to proxies: PROXY fictional-user:P%40ss@fictional-proxy.example.com:8080'
137+
)).toBe(
138+
'Failed to establish a socket connection to proxies: PROXY <credentials>@<host>:8080'
139+
);
140+
});
141+
142+
test('strips credentials with email as username', () => {
143+
expect(sanitizeNetworkErrorForTelemetry(
144+
'Failed to establish a socket connection to proxies: PROXY fictional.user@example.com:fictional-pass@fictional-proxy.example.com:8080'
145+
)).toBe(
146+
'Failed to establish a socket connection to proxies: PROXY <credentials>@<host>:8080'
147+
);
148+
});
149+
150+
test('strips credentials with multiple @ signs', () => {
151+
expect(sanitizeNetworkErrorForTelemetry(
152+
'Failed to establish a socket connection to proxies: HTTPS fictional-id:fictional-pass@fictional-realm@fictional-proxy.example.com:80'
153+
)).toBe(
154+
'Failed to establish a socket connection to proxies: HTTPS <credentials>@<host>:80'
155+
);
156+
});
157+
118158
test('handles multiple proxies with credentials', () => {
119159
expect(sanitizeNetworkErrorForTelemetry(
120160
'Failed to establish a socket connection to proxies: PROXY testuser:testpass@proxy1.fictional.example.com:8080; PROXY proxy2.fictional.example.com:8080; DIRECT'

extensions/copilot/src/platform/log/common/logService.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,13 +409,15 @@ export function collectSingleLineErrorMessage(e: any, includeDetails = false): s
409409
*/
410410
export function sanitizeNetworkErrorForTelemetry(message: string): string {
411411
// Strip credentials and host from proxy result strings (e.g., "PROXY user:pass@host" → "PROXY <credentials>@<host>")
412-
message = message.replace(/(\b(?:PROXY|HTTPS?|SOCKS[45]?)\s+)[^\s@]+@([^\s:\/]+)/gi, '$1<credentials>@<host>');
412+
message = message.replace(/(\b(?:PROXY|HTTPS?|SOCKS[45]?)\s+)[^\s]+@([^\s:\/]+)/gi, '$1<credentials>@<host>');
413413
// Strip host from proxy result strings without credentials (e.g., "PROXY host:8080" → "PROXY <host>:8080")
414414
message = message.replace(/(\b(?:PROXY|HTTPS?|SOCKS[45]?)\s+)([a-zA-Z0-9][-a-zA-Z0-9.]*)/gi, '$1<host>');
415415
// Strip credentials and host from URLs (e.g., "://user:pass@host" → "://<credentials>@<host>")
416-
message = message.replace(/(\/\/)[^\s/@]+@([^\s:\/]+)/g, '$1<credentials>@<host>');
416+
message = message.replace(/(\/\/)[^\s/]+@([^\s:\/]+)/g, '$1<credentials>@<host>');
417417
// Replace IPv4 addresses, preserving the port if present
418418
message = message.replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<ip>');
419+
// Replace IPv6 addresses (full form, e.g., "2001:db8:85a3:0:0:8a2e:370:7334")
420+
message = message.replace(/(?<![a-zA-Z_:])(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}(?![a-zA-Z_])/g, '<ip>');
419421
// Replace IPv6 addresses (compressed form with ::, e.g., "2001:db8::1" or "::1")
420422
message = message.replace(/(?<![a-zA-Z_:])(?:(?:[0-9a-fA-F]{1,4}:){1,7}|:):[0-9a-fA-F:]*[0-9a-fA-F](?![a-zA-Z_])/g, '<ip>');
421423
// Replace FQDNs (at least one dot, TLD of 2+ alpha chars), preserving the port if present

0 commit comments

Comments
 (0)