Skip to content

Commit 4e305dd

Browse files
authored
fix(mongodb-redact): redact special character cases for connection strings MONGOSH-2991 (#597)
1 parent 80c0810 commit 4e305dd

File tree

4 files changed

+134
-14
lines changed

4 files changed

+134
-14
lines changed

packages/mongodb-redact/src/index.spec.ts

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,82 @@ describe('mongodb-redact', function () {
161161
expect(res).to.equal('<url>');
162162
});
163163

164-
it('should redact MongoDB connection URIs', function () {
165-
let res = redact(
166-
'mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&connectTimeoutMS=300000',
167-
);
168-
expect(res).to.equal('<mongodb uri>');
169-
res = redact('mongodb://localhost,localhost:27018,localhost:27019');
170-
expect(res).to.equal('<mongodb uri>');
164+
describe('MongoDB connection strings', function () {
165+
it('should redact MongoDB connection URIs', function () {
166+
let res = redact(
167+
'mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&connectTimeoutMS=300000',
168+
);
169+
expect(res).to.equal('<mongodb uri>');
170+
res = redact('mongodb://localhost,localhost:27018,localhost:27019');
171+
expect(res).to.equal('<mongodb uri>');
172+
});
173+
174+
it('should redact MongoDB URIs with credentials', function () {
175+
let res = redact('mongodb://user:password@localhost:27017/admin');
176+
expect(res).to.equal('<mongodb uri>');
177+
res = redact('mongodb://admin:[email protected]/mydb');
178+
expect(res).to.equal('<mongodb uri>');
179+
});
180+
181+
it('should redact MongoDB URIs with special characters in usernames and passwords', function () {
182+
let res = redact('mongodb://user:p%40ss!word@localhost:27017/');
183+
expect(res).to.equal('<mongodb uri>');
184+
res = redact('mongodb://ad!min:te%st#[email protected]:27017/');
185+
expect(res).to.equal('<mongodb uri>');
186+
res = redact('mongodb://!user:my%20pass@localhost/mydb');
187+
expect(res).to.equal('<mongodb uri>');
188+
res = redact(
189+
'mongodb://user:p&ssw!rd#[email protected]:27017/db?authSource=admin',
190+
);
191+
expect(res).to.equal('<mongodb uri>');
192+
});
193+
194+
it('should redact MongoDB SRV URIs', function () {
195+
let res = redact(
196+
'mongodb+srv://user:[email protected]/test',
197+
);
198+
expect(res).to.equal('<mongodb uri>');
199+
res = redact(
200+
'mongodb+srv://admin:[email protected]/mydb?retryWrites=true',
201+
);
202+
expect(res).to.equal('<mongodb uri>');
203+
});
204+
205+
it('should redact MongoDB URIs with query parameters', function () {
206+
let res = redact(
207+
'mongodb://localhost:27017/mydb?ssl=true&replicaSet=rs0',
208+
);
209+
expect(res).to.equal('<mongodb uri>');
210+
res = redact(
211+
'mongodb://user:[email protected]/db?authSource=admin&readPreference=primary',
212+
);
213+
expect(res).to.equal('<mongodb uri>');
214+
});
215+
216+
it('should redact MongoDB URIs with replica sets', function () {
217+
let res = redact(
218+
'mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myReplSet',
219+
);
220+
expect(res).to.equal('<mongodb uri>');
221+
res = redact('mongodb://user:pass@host1,host2,host3/db?replicaSet=rs0');
222+
expect(res).to.equal('<mongodb uri>');
223+
});
224+
225+
it('should redact MongoDB URIs with IP addresses', function () {
226+
let res = redact('mongodb://192.168.1.100:27017/mydb');
227+
expect(res).to.equal('<mongodb uri>');
228+
res = redact('mongodb://user:[email protected]:27017/admin');
229+
expect(res).to.equal('<mongodb uri>');
230+
});
231+
232+
it('should redact simple MongoDB URIs', function () {
233+
let res = redact('mongodb://localhost');
234+
expect(res).to.equal('<mongodb uri>');
235+
res = redact('mongodb://localhost:27017');
236+
expect(res).to.equal('<mongodb uri>');
237+
res = redact('mongodb://localhost/mydb');
238+
expect(res).to.equal('<mongodb uri>');
239+
});
171240
});
172241

173242
it('should redact general linux/unix user paths', function () {

packages/mongodb-redact/src/regexes.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export const regexes = [
2727
'$1<email>$6',
2828
],
2929

30+
// MongoDB connection strings
31+
[/mongodb(?:\+srv)?:\/\/\S+/gim, '<mongodb uri>'],
32+
3033
// IP addresses
3134
[
3235
/((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])/gm,
@@ -39,12 +42,6 @@ export const regexes = [
3942
'<url>',
4043
],
4144

42-
// MongoDB connection strings
43-
[
44-
/(mongodb:\/\/)(www\.)?[-a-zA-Z0-9@:%._+~#=,]{2,256}(\.[a-z]{2,6})?\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/gim,
45-
'<mongodb uri>',
46-
],
47-
4845
// Compass Schema URL fragments
4946
[/#schema\/\w+\.\w+/, '#schema/<namespace>'],
5047
] as const;

packages/mongodb-redact/src/secrets.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,55 @@ describe('dictionary-based secret redaction', function () {
9393
usr: '<user>',
9494
});
9595
});
96+
97+
describe('special characters in passwords', function () {
98+
it('redacts passwords at start, end, or entire string', function () {
99+
expect(
100+
redact('!start is pwd', [{ value: '!start', kind: 'password' }]),
101+
).to.equal('<password> is pwd');
102+
103+
expect(
104+
redact('pwd is end!', [{ value: 'end!', kind: 'password' }]),
105+
).to.equal('pwd is <password>');
106+
107+
expect(
108+
redact('The password is !@#$%', [{ value: '!@#$%', kind: 'password' }]),
109+
).to.equal('The password is <password>');
110+
});
111+
112+
it('redacts a special-character only connection string', function () {
113+
const secret = '!#!!';
114+
const content = 'Connection string: mongodb://!!!#:!#!!@localhost:27017/';
115+
116+
const redacted = redact(content, [{ value: secret, kind: 'password' }]);
117+
118+
expect(redacted).to.equal('Connection string: <mongodb uri>');
119+
});
120+
121+
for (const { char, password } of [
122+
{ char: '.', password: 'test.pass' },
123+
{ char: '*', password: 'test*pass' },
124+
{ char: '+', password: 'test+pass' },
125+
{ char: '?', password: 'test?pass' },
126+
{ char: '[', password: 'test[123]' },
127+
{ char: '(', password: 'test(abc)' },
128+
{ char: '|', password: 'test|pass' },
129+
{ char: '\\', password: 'test\\pass' },
130+
{ char: '^', password: '^test123' },
131+
{ char: '$', password: 'test$123' },
132+
{ char: '@', password: 'user@123' },
133+
{ char: '#', password: 'pass#word' },
134+
{ char: '%', password: 'test%20' },
135+
{ char: '&', password: 'rock&roll' },
136+
{ char: 'լավ', password: 'լավ' },
137+
]) {
138+
it(`redacts passwords with ${char}`, function () {
139+
const content = `pwd: ${password} end`;
140+
const redacted = redact(content, [
141+
{ value: password, kind: 'password' },
142+
]);
143+
expect(redacted).to.equal('pwd: <password> end');
144+
});
145+
}
146+
});
96147
});

packages/mongodb-redact/src/secrets.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ export function redactSecretsOnString<T extends string>(
3232
);
3333
}
3434

35-
const regex = new RegExp(`\\b${escape(value)}\\b`, 'g');
35+
// Escape the value for use in regex and use negative lookahead/lookbehind
36+
// to match secrets not surrounded by word characters
37+
const escapedValue = escape(value);
38+
const regex = new RegExp(`(?<!\\w)${escapedValue}(?!\\w)`, 'g');
3639
result = result.replace(regex, `<${kind}>`) as T;
3740
}
3841

0 commit comments

Comments
 (0)