From 7ce9d8cbbafb9420078ca1555b907bce62aea367 Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 17 Nov 2025 13:13:36 +0100 Subject: [PATCH 1/7] chore(mongodb-redact): redact special character cases for password MONGOSH-2991 --- packages/mongodb-redact/src/secrets.spec.ts | 53 +++++++++++++++++++++ packages/mongodb-redact/src/secrets.ts | 5 +- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/mongodb-redact/src/secrets.spec.ts b/packages/mongodb-redact/src/secrets.spec.ts index 3af8b617..07599a42 100644 --- a/packages/mongodb-redact/src/secrets.spec.ts +++ b/packages/mongodb-redact/src/secrets.spec.ts @@ -93,4 +93,57 @@ describe('dictionary-based secret redaction', function () { usr: '', }); }); + + describe('special characters in passwords', function () { + it('redacts passwords at start, end, or entire string', function () { + expect( + redact('!start is pwd', [{ value: '!start', kind: 'password' }]), + ).to.equal(' is pwd'); + + expect( + redact('pwd is end!', [{ value: 'end!', kind: 'password' }]), + ).to.equal('pwd is '); + + expect( + redact('The password is !@#$%', [{ value: '!@#$%', kind: 'password' }]), + ).to.equal('The password is '); + }); + + it('redacts a special-character only connection string', function () { + const secret = '!#!!'; + const content = 'Connection string: mongodb://user:!#!!@localhost:27017/'; + + const redacted = redact(content, [{ value: secret, kind: 'password' }]); + + expect(redacted).to.equal( + 'Connection string: @localhost:27017/', + ); + }); + + for (const { char, password } of [ + { char: '.', password: 'test.pass' }, + { char: '*', password: 'test*pass' }, + { char: '+', password: 'test+pass' }, + { char: '?', password: 'test?pass' }, + { char: '[', password: 'test[123]' }, + { char: '(', password: 'test(abc)' }, + { char: '|', password: 'test|pass' }, + { char: '\\', password: 'test\\pass' }, + { char: '^', password: '^test123' }, + { char: '$', password: 'test$123' }, + { char: '@', password: 'user@123' }, + { char: '#', password: 'pass#word' }, + { char: '%', password: 'test%20' }, + { char: '&', password: 'rock&roll' }, + { char: 'լավ', password: 'լավ' }, + ]) { + it(`redacts passwords with ${char}`, function () { + const content = `pwd: ${password} end`; + const redacted = redact(content, [ + { value: password, kind: 'password' }, + ]); + expect(redacted).to.equal('pwd: end'); + }); + } + }); }); diff --git a/packages/mongodb-redact/src/secrets.ts b/packages/mongodb-redact/src/secrets.ts index 4c0a519e..fe4aa6b8 100644 --- a/packages/mongodb-redact/src/secrets.ts +++ b/packages/mongodb-redact/src/secrets.ts @@ -32,7 +32,10 @@ export function redactSecretsOnString( ); } - const regex = new RegExp(`\\b${escape(value)}\\b`, 'g'); + // Escape the value for use in regex and use negative lookahead/lookbehind + // to match secrets not surrounded by word characters + const escapedValue = escape(value); + const regex = new RegExp(`(?`) as T; } From f2c9beab8350adda8ed1eabda612fb790310ec68 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 18 Nov 2025 11:34:35 +0100 Subject: [PATCH 2/7] chore: redact MongoDB URIs better --- packages/mongodb-redact/.vscode/settings.json | 15 ++++ packages/mongodb-redact/src/index.spec.ts | 83 +++++++++++++++++-- packages/mongodb-redact/src/regexes.ts | 12 +-- packages/mongodb-redact/src/secrets.spec.ts | 6 +- 4 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 packages/mongodb-redact/.vscode/settings.json diff --git a/packages/mongodb-redact/.vscode/settings.json b/packages/mongodb-redact/.vscode/settings.json new file mode 100644 index 00000000..4a402fc6 --- /dev/null +++ b/packages/mongodb-redact/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "mochaExplorer.files": "{src,lib}/**/*.spec.ts", + "mochaExplorer.require": [ + "../../scripts/import-expansions.js", + "ts-node/register" + ], + "mochaExplorer.timeout": 60000, + "mochaExplorer.ui": "bdd", + "mochaExplorer.monkeyPatch": true, + "mochaExplorer.autoload": true, + "testExplorer.codeLens": true, + "testExplorer.gutterDecoration": true, + "testExplorer.onStart": "reset", + "testExplorer.onReload": "reset" +} diff --git a/packages/mongodb-redact/src/index.spec.ts b/packages/mongodb-redact/src/index.spec.ts index ca48dbdd..02566a5f 100644 --- a/packages/mongodb-redact/src/index.spec.ts +++ b/packages/mongodb-redact/src/index.spec.ts @@ -161,13 +161,82 @@ describe('mongodb-redact', function () { expect(res).to.equal(''); }); - it('should redact MongoDB connection URIs', function () { - let res = redact( - 'mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&connectTimeoutMS=300000', - ); - expect(res).to.equal(''); - res = redact('mongodb://localhost,localhost:27018,localhost:27019'); - expect(res).to.equal(''); + describe('MongoDB connection strings', function () { + it('should redact MongoDB connection URIs', function () { + let res = redact( + 'mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&connectTimeoutMS=300000', + ); + expect(res).to.equal(''); + res = redact('mongodb://localhost,localhost:27018,localhost:27019'); + expect(res).to.equal(''); + }); + + it('should redact MongoDB URIs with credentials', function () { + let res = redact('mongodb://user:password@localhost:27017/admin'); + expect(res).to.equal(''); + res = redact('mongodb://admin:secret123@db.example.com/mydb'); + expect(res).to.equal(''); + }); + + it('should redact MongoDB URIs with special characters in usernames and passwords', function () { + let res = redact('mongodb://user:p%40ss!word@localhost:27017/'); + expect(res).to.equal(''); + res = redact('mongodb://ad!min:te%st#123$@db.example.com:27017/'); + expect(res).to.equal(''); + res = redact('mongodb://!user:my%20pass@localhost/mydb'); + expect(res).to.equal(''); + res = redact( + 'mongodb://user:p&ssw!rd#123@host.com:27017/db?authSource=admin', + ); + expect(res).to.equal(''); + }); + + it('should redact MongoDB SRV URIs', function () { + let res = redact( + 'mongodb+srv://user:password@cluster0.example.com/test', + ); + expect(res).to.equal(''); + res = redact( + 'mongodb+srv://admin:secret@mycluster.mongodb.net/mydb?retryWrites=true', + ); + expect(res).to.equal(''); + }); + + it('should redact MongoDB URIs with query parameters', function () { + let res = redact( + 'mongodb://localhost:27017/mydb?ssl=true&replicaSet=rs0', + ); + expect(res).to.equal(''); + res = redact( + 'mongodb://user:pass@host.com/db?authSource=admin&readPreference=primary', + ); + expect(res).to.equal(''); + }); + + it('should redact MongoDB URIs with replica sets', function () { + let res = redact( + 'mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myReplSet', + ); + expect(res).to.equal(''); + res = redact('mongodb://user:pass@host1,host2,host3/db?replicaSet=rs0'); + expect(res).to.equal(''); + }); + + it('should redact MongoDB URIs with IP addresses', function () { + let res = redact('mongodb://192.168.1.100:27017/mydb'); + expect(res).to.equal(''); + res = redact('mongodb://user:password@10.0.0.5:27017/admin'); + expect(res).to.equal(''); + }); + + it('should redact simple MongoDB URIs', function () { + let res = redact('mongodb://localhost'); + expect(res).to.equal(''); + res = redact('mongodb://localhost:27017'); + expect(res).to.equal(''); + res = redact('mongodb://localhost/mydb'); + expect(res).to.equal(''); + }); }); it('should redact general linux/unix user paths', function () { diff --git a/packages/mongodb-redact/src/regexes.ts b/packages/mongodb-redact/src/regexes.ts index c4abff94..f6d08bd8 100644 --- a/packages/mongodb-redact/src/regexes.ts +++ b/packages/mongodb-redact/src/regexes.ts @@ -27,6 +27,12 @@ export const regexes = [ '$1$6', ], + // MongoDB connection strings (before IP addresses to handle mongodb://IP:port URIs) + [ + /(mongodb(?:\+srv)?:\/\/)(www\.)?(?:[^:/@\s]+:[^@\s]*@)?[-a-zA-Z0-9@:%._+~#=,]{2,256}(\.[a-z]{2,6})?\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/gim, + '', + ], + // IP addresses [ /((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 +45,6 @@ export const regexes = [ '', ], - // MongoDB connection strings - [ - /(mongodb:\/\/)(www\.)?[-a-zA-Z0-9@:%._+~#=,]{2,256}(\.[a-z]{2,6})?\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/gim, - '', - ], - // Compass Schema URL fragments [/#schema\/\w+\.\w+/, '#schema/'], ] as const; diff --git a/packages/mongodb-redact/src/secrets.spec.ts b/packages/mongodb-redact/src/secrets.spec.ts index 07599a42..ed2ae16e 100644 --- a/packages/mongodb-redact/src/secrets.spec.ts +++ b/packages/mongodb-redact/src/secrets.spec.ts @@ -111,13 +111,11 @@ describe('dictionary-based secret redaction', function () { it('redacts a special-character only connection string', function () { const secret = '!#!!'; - const content = 'Connection string: mongodb://user:!#!!@localhost:27017/'; + const content = 'Connection string: mongodb://!!!#:!#!!@localhost:27017/'; const redacted = redact(content, [{ value: secret, kind: 'password' }]); - expect(redacted).to.equal( - 'Connection string: @localhost:27017/', - ); + expect(redacted).to.equal('Connection string: '); }); for (const { char, password } of [ From 93b40a8eeb08535499c27a0355df276533ce5f90 Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Tue, 18 Nov 2025 12:21:58 +0100 Subject: [PATCH 3/7] Delete packages/mongodb-redact/.vscode/settings.json --- packages/mongodb-redact/.vscode/settings.json | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 packages/mongodb-redact/.vscode/settings.json diff --git a/packages/mongodb-redact/.vscode/settings.json b/packages/mongodb-redact/.vscode/settings.json deleted file mode 100644 index 4a402fc6..00000000 --- a/packages/mongodb-redact/.vscode/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mochaExplorer.files": "{src,lib}/**/*.spec.ts", - "mochaExplorer.require": [ - "../../scripts/import-expansions.js", - "ts-node/register" - ], - "mochaExplorer.timeout": 60000, - "mochaExplorer.ui": "bdd", - "mochaExplorer.monkeyPatch": true, - "mochaExplorer.autoload": true, - "testExplorer.codeLens": true, - "testExplorer.gutterDecoration": true, - "testExplorer.onStart": "reset", - "testExplorer.onReload": "reset" -} From 4d0517bee816669e85b67f48305491db8f7622cd Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 18 Nov 2025 12:52:08 +0100 Subject: [PATCH 4/7] feat: add shouldRedactCommand --- .../src/should-redact-command.spec.ts | 52 +++++++++++++++++++ .../src/should-redact-command.ts | 27 ++++++++++ 2 files changed, 79 insertions(+) create mode 100644 packages/mongodb-redact/src/should-redact-command.spec.ts create mode 100644 packages/mongodb-redact/src/should-redact-command.ts diff --git a/packages/mongodb-redact/src/should-redact-command.spec.ts b/packages/mongodb-redact/src/should-redact-command.spec.ts new file mode 100644 index 00000000..fae44916 --- /dev/null +++ b/packages/mongodb-redact/src/should-redact-command.spec.ts @@ -0,0 +1,52 @@ +import { expect } from 'chai'; +import { shouldRedactCommand } from '.'; + +describe('shouldRedactCommand', function () { + describe('shouldRedactCommand', function () { + it('returns true for createUser commands', function () { + expect(shouldRedactCommand('db.createUser({ user: "test" })')).to.be.true; + }); + + it('returns true for auth commands', function () { + expect(shouldRedactCommand('db.auth("user", "pass")')).to.be.true; + }); + + it('returns true for updateUser commands', function () { + expect(shouldRedactCommand('db.updateUser("user", { roles: [] })')).to.be + .true; + }); + + it('returns true for changeUserPassword commands', function () { + expect(shouldRedactCommand('db.changeUserPassword("user", "newpass")')).to + .be.true; + }); + + it('returns true for connect commands', function () { + expect(shouldRedactCommand('db = connect("mongodb://localhost")')).to.be + .true; + }); + + it('returns true for Mongo constructor', function () { + expect(shouldRedactCommand('new Mongo("mongodb://localhost")')).to.be + .true; + }); + + it('returns false for non-sensitive commands', function () { + expect(shouldRedactCommand('db.collection.find()')).to.be.false; + }); + + it('returns false for partial words like "authentication"', function () { + // The \b (word boundary) should prevent matching "auth" within "authentication" + expect(shouldRedactCommand('db.collection.find({authentication: true})')) + .to.be.false; + }); + + it('returns false for getUsers command', function () { + expect(shouldRedactCommand('db.getUsers()')).to.be.false; + }); + + it('returns false for show commands', function () { + expect(shouldRedactCommand('show dbs')).to.be.false; + }); + }); +}); diff --git a/packages/mongodb-redact/src/should-redact-command.ts b/packages/mongodb-redact/src/should-redact-command.ts new file mode 100644 index 00000000..445b7c50 --- /dev/null +++ b/packages/mongodb-redact/src/should-redact-command.ts @@ -0,0 +1,27 @@ +/** + * Regex pattern for commands that contain sensitive information and should be + * completely removed from history rather than redacted. + * + * These commands typically involve authentication or connection strings with credentials. + */ +const HIDDEN_COMMANDS = String.raw`\b(createUser|auth|updateUser|changeUserPassword|connect|Mongo)\b`; + +/** + * Checks if a mongosh command should be redacted because it often contains sensitive information like credentials. + * + * @param input - The command string to check + * @returns true if the command should be hidden/redacted, false otherwise + * + * @example + * ```typescript + * shouldRedactCommand('db.createUser({user: "admin", pwd: "secret"})') + * // Returns: true + * + * shouldRedactCommand('db.getUsers()') + * // Returns: false + * ``` + */ +export function shouldRedactCommand(input: string): boolean { + const hiddenCommands = new RegExp(HIDDEN_COMMANDS, 'g'); + return hiddenCommands.test(input); +} From 7e7577850d6e8dbc51f23ebf57a30a639a2e4bf8 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 18 Nov 2025 13:19:02 +0100 Subject: [PATCH 5/7] chore: add redact URI credentials --- package-lock.json | 2 + packages/mongodb-redact/package.json | 3 +- packages/mongodb-redact/src/index.ts | 2 + .../src/redact-uri-credentials.spec.ts | 173 ++++++++++++++++++ .../src/redact-uri-credentials.ts | 5 + packages/mongodb-redact/src/regexes.ts | 7 +- 6 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 packages/mongodb-redact/src/redact-uri-credentials.spec.ts create mode 100644 packages/mongodb-redact/src/redact-uri-credentials.ts diff --git a/package-lock.json b/package-lock.json index 3426513e..a295ef6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30947,6 +30947,7 @@ "version": "1.2.2", "license": "Apache-2.0", "dependencies": { + "mongodb-connection-string-url": "^3.0.1", "regexp.escape": "^2.0.1" }, "devDependencies": { @@ -51028,6 +51029,7 @@ "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.3", "mocha": "^8.4.0", + "mongodb-connection-string-url": "^3.0.1", "nyc": "^15.1.0", "prettier": "^3.5.3", "regexp.escape": "^2.0.1", diff --git a/packages/mongodb-redact/package.json b/packages/mongodb-redact/package.json index 5f6246d4..6770c99d 100644 --- a/packages/mongodb-redact/package.json +++ b/packages/mongodb-redact/package.json @@ -71,6 +71,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "regexp.escape": "^2.0.1" + "regexp.escape": "^2.0.1", + "mongodb-connection-string-url": "^3.0.1" } } diff --git a/packages/mongodb-redact/src/index.ts b/packages/mongodb-redact/src/index.ts index 0754f7e8..f9338021 100644 --- a/packages/mongodb-redact/src/index.ts +++ b/packages/mongodb-redact/src/index.ts @@ -46,5 +46,7 @@ export function redact( return message; } +export { redactUriCredentials } from './redact-uri-credentials'; + export default redact; export type { Secret } from './secrets'; diff --git a/packages/mongodb-redact/src/redact-uri-credentials.spec.ts b/packages/mongodb-redact/src/redact-uri-credentials.spec.ts new file mode 100644 index 00000000..926d0e5f --- /dev/null +++ b/packages/mongodb-redact/src/redact-uri-credentials.spec.ts @@ -0,0 +1,173 @@ +import { expect } from 'chai'; +import { redactUriCredentials } from './redact-uri-credentials'; +import redact from '.'; + +describe('redactUriCredentials', function () { + const testCases: Array<{ + description: string; + input: string; + expected: string; + }> = [ + { + description: 'should redact username and password', + input: 'mongodb://user:password@localhost:27017/admin', + expected: 'mongodb://@localhost:27017/admin', + }, + { + description: 'should redact only username when no password', + input: 'mongodb://user@localhost:27017/admin', + expected: 'mongodb://@localhost:27017/admin', + }, + { + description: 'should redact credentials in SRV URIs', + input: 'mongodb+srv://admin:sec!ret@cluster0.example.com/test', + expected: 'mongodb+srv://@cluster0.example.com/test', + }, + { + description: 'should redact passwords with ! character', + input: 'mongodb://user:p@ss!word@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: 'should redact passwords with # character', + input: 'mongodb://admin:test#123@db.example.com:27017/', + expected: 'mongodb://@db.example.com:27017/', + }, + { + description: 'should redact passwords with $ character', + input: 'mongodb://user:price$100@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: 'should redact passwords with % character', + input: 'mongodb://user:test%pass@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: 'should redact passwords with & character', + input: 'mongodb://user:rock&roll@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: 'should redact URL-encoded passwords', + input: 'mongodb://user:my%20password@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: + 'should redact complex passwords with multiple special characters', + input: 'mongodb://user:p&ssw!rd#123$@host.com:27017/db?authSource=admin', + expected: 'mongodb://@host.com:27017/db?authSource=admin', + }, + { + description: 'should redact usernames with special characters', + input: 'mongodb://us!er:password@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: 'should return URI unchanged when no credentials', + input: 'mongodb://localhost:27017/admin', + expected: 'mongodb://localhost:27017/admin', + }, + { + description: 'should handle simple localhost URI', + input: 'mongodb://localhost', + expected: 'mongodb://localhost/', + }, + { + description: 'should handle URI with database', + input: 'mongodb://localhost/mydb', + expected: 'mongodb://localhost/mydb', + }, + { + description: 'should handle URI with query parameters', + input: 'mongodb://localhost:27017/mydb?ssl=true&replicaSet=rs0', + expected: 'mongodb://localhost:27017/mydb?ssl=true&replicaSet=rs0', + }, + // URIs with replica sets + { + description: 'should redact credentials in replica set URIs', + input: + 'mongodb://user:pass@host1:27017,host2:27017,host3:27017/db?replicaSet=rs0', + expected: + 'mongodb://@host1:27017,host2:27017,host3:27017/db?replicaSet=rs0', + }, + { + description: 'should handle replica set URIs without credentials', + input: + 'mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myReplSet', + expected: + 'mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myReplSet', + }, + // URIs with IP addresses + { + description: 'should redact credentials with IP address host', + input: 'mongodb://user:password@192.168.1.100:27017/mydb', + expected: 'mongodb://@192.168.1.100:27017/mydb', + }, + { + description: 'should handle IP address URIs without credentials', + input: 'mongodb://10.0.0.5:27017/admin', + expected: 'mongodb://10.0.0.5:27017/admin', + }, + // SRV URIs + { + description: 'should handle SRV URIs without credentials', + input: 'mongodb+srv://cluster0.example.com/test', + expected: 'mongodb+srv://cluster0.example.com/test', + }, + // URIs with query parameters + { + description: 'should redact credentials and preserve query parameters', + input: + 'mongodb://user:pass@host.com/db?authSource=admin&readPreference=primary', + expected: + 'mongodb://@host.com/db?authSource=admin&readPreference=primary', + }, + { + description: 'should handle URIs with SSL options', + input: + 'mongodb://admin:secret@localhost:27017/mydb?ssl=true&tlsAllowInvalidCertificates=true', + expected: + 'mongodb://@localhost:27017/mydb?ssl=true&tlsAllowInvalidCertificates=true', + }, + { + description: 'should redact credentials in SRV URIs with query params', + input: + 'mongodb+srv://admin:secret@mycluster.mongodb.net/mydb?retryWrites=true', + expected: + 'mongodb+srv://@mycluster.mongodb.net/mydb?retryWrites=true', + }, + // Edge cases + { + description: 'should handle empty password', + input: 'mongodb://user:@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: + 'should handle password with only special characters (URL-encoded)', + input: 'mongodb://user:%21%40%23%24%25@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + { + description: 'should handle very long passwords', + input: `mongodb://user:${'a'.repeat(100)}@localhost:27017/`, + expected: 'mongodb://@localhost:27017/', + }, + { + description: 'should handle international characters in password', + input: 'mongodb://user:пароль@localhost:27017/', + expected: 'mongodb://@localhost:27017/', + }, + ]; + + testCases.forEach(({ description, input, expected }) => { + it(description, function () { + const result = redactUriCredentials(input); + expect(result).to.equal(expected); + + expect(redact(input)).to.equal(''); + }); + }); +}); diff --git a/packages/mongodb-redact/src/redact-uri-credentials.ts b/packages/mongodb-redact/src/redact-uri-credentials.ts new file mode 100644 index 00000000..da6a26f6 --- /dev/null +++ b/packages/mongodb-redact/src/redact-uri-credentials.ts @@ -0,0 +1,5 @@ +import { redactConnectionString } from 'mongodb-connection-string-url'; + +export function redactUriCredentials(uri: string): string { + return redactConnectionString(uri); +} diff --git a/packages/mongodb-redact/src/regexes.ts b/packages/mongodb-redact/src/regexes.ts index f6d08bd8..3266c7a2 100644 --- a/packages/mongodb-redact/src/regexes.ts +++ b/packages/mongodb-redact/src/regexes.ts @@ -27,11 +27,8 @@ export const regexes = [ '$1$6', ], - // MongoDB connection strings (before IP addresses to handle mongodb://IP:port URIs) - [ - /(mongodb(?:\+srv)?:\/\/)(www\.)?(?:[^:/@\s]+:[^@\s]*@)?[-a-zA-Z0-9@:%._+~#=,]{2,256}(\.[a-z]{2,6})?\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/gim, - '', - ], + // MongoDB connection strings + [/mongodb(?:\+srv)?:\/\/\S+/gim, ''], // IP addresses [ From 6ea96b41d658eaf1a9bcecb471af3a1cf401f7c8 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 18 Nov 2025 13:25:31 +0100 Subject: [PATCH 6/7] chore: cleanup --- packages/mongodb-redact/src/index.ts | 1 + .../src/should-redact-command.spec.ts | 70 ++++++------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/packages/mongodb-redact/src/index.ts b/packages/mongodb-redact/src/index.ts index f9338021..a653d136 100644 --- a/packages/mongodb-redact/src/index.ts +++ b/packages/mongodb-redact/src/index.ts @@ -46,6 +46,7 @@ export function redact( return message; } +export { shouldRedactCommand } from './should-redact-command'; export { redactUriCredentials } from './redact-uri-credentials'; export default redact; diff --git a/packages/mongodb-redact/src/should-redact-command.spec.ts b/packages/mongodb-redact/src/should-redact-command.spec.ts index fae44916..ea849546 100644 --- a/packages/mongodb-redact/src/should-redact-command.spec.ts +++ b/packages/mongodb-redact/src/should-redact-command.spec.ts @@ -2,51 +2,27 @@ import { expect } from 'chai'; import { shouldRedactCommand } from '.'; describe('shouldRedactCommand', function () { - describe('shouldRedactCommand', function () { - it('returns true for createUser commands', function () { - expect(shouldRedactCommand('db.createUser({ user: "test" })')).to.be.true; - }); - - it('returns true for auth commands', function () { - expect(shouldRedactCommand('db.auth("user", "pass")')).to.be.true; - }); - - it('returns true for updateUser commands', function () { - expect(shouldRedactCommand('db.updateUser("user", { roles: [] })')).to.be - .true; - }); - - it('returns true for changeUserPassword commands', function () { - expect(shouldRedactCommand('db.changeUserPassword("user", "newpass")')).to - .be.true; - }); - - it('returns true for connect commands', function () { - expect(shouldRedactCommand('db = connect("mongodb://localhost")')).to.be - .true; - }); - - it('returns true for Mongo constructor', function () { - expect(shouldRedactCommand('new Mongo("mongodb://localhost")')).to.be - .true; - }); - - it('returns false for non-sensitive commands', function () { - expect(shouldRedactCommand('db.collection.find()')).to.be.false; - }); - - it('returns false for partial words like "authentication"', function () { - // The \b (word boundary) should prevent matching "auth" within "authentication" - expect(shouldRedactCommand('db.collection.find({authentication: true})')) - .to.be.false; - }); - - it('returns false for getUsers command', function () { - expect(shouldRedactCommand('db.getUsers()')).to.be.false; - }); - - it('returns false for show commands', function () { - expect(shouldRedactCommand('show dbs')).to.be.false; - }); - }); + for (const command of [ + 'db.createUser({ user: "test" })', + 'db.auth("user", "pass")', + 'db.updateUser("user", { roles: [] })', + 'db.changeUserPassword("user", "newpass")', + 'db = connect("mongodb://localhost")', + 'new Mongo("mongodb://localhost")', + ]) { + it(`returns true for ${command}`, function () { + expect(shouldRedactCommand(command)).to.be.true; + }); + } + + for (const command of [ + 'db.collection.find()', + 'db.collection.find({authentication: true})', + 'db.getUsers()', + 'show dbs', + ]) { + it(`returns false for ${command}`, function () { + expect(shouldRedactCommand(command)).to.be.false; + }); + } }); From 370d518a362c977eaaeab45b49d1d63add67d861 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 18 Nov 2025 16:18:10 +0100 Subject: [PATCH 7/7] chore: rename to redact-connection-string --- package-lock.json | 4 ++-- packages/devtools-connect/src/log-hook.ts | 10 +++++----- packages/mongodb-redact/package.json | 2 +- packages/mongodb-redact/src/index.ts | 2 +- ...ntials.spec.ts => redact-connection-string.spec.ts} | 6 +++--- .../mongodb-redact/src/redact-connection-string.ts | 5 +++++ packages/mongodb-redact/src/redact-uri-credentials.ts | 5 ----- 7 files changed, 17 insertions(+), 17 deletions(-) rename packages/mongodb-redact/src/{redact-uri-credentials.spec.ts => redact-connection-string.spec.ts} (97%) create mode 100644 packages/mongodb-redact/src/redact-connection-string.ts delete mode 100644 packages/mongodb-redact/src/redact-uri-credentials.ts diff --git a/package-lock.json b/package-lock.json index a295ef6b..172ce5cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30947,7 +30947,7 @@ "version": "1.2.2", "license": "Apache-2.0", "dependencies": { - "mongodb-connection-string-url": "^3.0.1", + "mongodb-connection-string-url": "^3.0.1 || ^7.0.0", "regexp.escape": "^2.0.1" }, "devDependencies": { @@ -51029,7 +51029,7 @@ "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.3", "mocha": "^8.4.0", - "mongodb-connection-string-url": "^3.0.1", + "mongodb-connection-string-url": "^3.0.1 || ^7.0.0", "nyc": "^15.1.0", "prettier": "^3.5.3", "regexp.escape": "^2.0.1", diff --git a/packages/devtools-connect/src/log-hook.ts b/packages/devtools-connect/src/log-hook.ts index 38f64cbf..753c61ce 100644 --- a/packages/devtools-connect/src/log-hook.ts +++ b/packages/devtools-connect/src/log-hook.ts @@ -28,7 +28,7 @@ export function hookLogger( emitter: ConnectLogEmitter, log: MongoLogWriter, contextPrefix: string, - redactURICredentials: (uri: string) => string, + redactConnectionString: (uri: string) => string, ): void { oidcHookLogger(emitter, log, contextPrefix); proxyHookLogger(emitter, log, contextPrefix); @@ -44,7 +44,7 @@ export function hookLogger( 'Initiating connection attempt', { ...ev, - uri: redactURICredentials(ev.uri), + uri: redactConnectionString(ev.uri), }, ); }, @@ -119,7 +119,7 @@ export function hookLogger( `${contextPrefix}-connect`, 'Resolving SRV record failed', { - from: redactURICredentials(ev.from), + from: redactConnectionString(ev.from), error: ev.error?.message, duringLoad: ev.duringLoad, resolutionDetails: ev.resolutionDetails, @@ -138,8 +138,8 @@ export function hookLogger( `${contextPrefix}-connect`, 'Resolving SRV record succeeded', { - from: redactURICredentials(ev.from), - to: redactURICredentials(ev.to), + from: redactConnectionString(ev.from), + to: redactConnectionString(ev.to), resolutionDetails: ev.resolutionDetails, durationMs: ev.durationMs, }, diff --git a/packages/mongodb-redact/package.json b/packages/mongodb-redact/package.json index 6770c99d..eb8643de 100644 --- a/packages/mongodb-redact/package.json +++ b/packages/mongodb-redact/package.json @@ -72,6 +72,6 @@ }, "dependencies": { "regexp.escape": "^2.0.1", - "mongodb-connection-string-url": "^3.0.1" + "mongodb-connection-string-url": "^3.0.1 || ^7.0.0" } } diff --git a/packages/mongodb-redact/src/index.ts b/packages/mongodb-redact/src/index.ts index a653d136..d1a1171f 100644 --- a/packages/mongodb-redact/src/index.ts +++ b/packages/mongodb-redact/src/index.ts @@ -47,7 +47,7 @@ export function redact( } export { shouldRedactCommand } from './should-redact-command'; -export { redactUriCredentials } from './redact-uri-credentials'; +export { redactConnectionString } from './redact-connection-string'; export default redact; export type { Secret } from './secrets'; diff --git a/packages/mongodb-redact/src/redact-uri-credentials.spec.ts b/packages/mongodb-redact/src/redact-connection-string.spec.ts similarity index 97% rename from packages/mongodb-redact/src/redact-uri-credentials.spec.ts rename to packages/mongodb-redact/src/redact-connection-string.spec.ts index 926d0e5f..181e7592 100644 --- a/packages/mongodb-redact/src/redact-uri-credentials.spec.ts +++ b/packages/mongodb-redact/src/redact-connection-string.spec.ts @@ -1,8 +1,8 @@ import { expect } from 'chai'; -import { redactUriCredentials } from './redact-uri-credentials'; +import { redactConnectionString } from './redact-connection-string'; import redact from '.'; -describe('redactUriCredentials', function () { +describe('redactConnectionString', function () { const testCases: Array<{ description: string; input: string; @@ -164,7 +164,7 @@ describe('redactUriCredentials', function () { testCases.forEach(({ description, input, expected }) => { it(description, function () { - const result = redactUriCredentials(input); + const result = redactConnectionString(input); expect(result).to.equal(expected); expect(redact(input)).to.equal(''); diff --git a/packages/mongodb-redact/src/redact-connection-string.ts b/packages/mongodb-redact/src/redact-connection-string.ts new file mode 100644 index 00000000..9912764c --- /dev/null +++ b/packages/mongodb-redact/src/redact-connection-string.ts @@ -0,0 +1,5 @@ +import { redactConnectionString as redactConnectionStringImpl } from 'mongodb-connection-string-url'; + +export function redactConnectionString(uri: string): string { + return redactConnectionStringImpl(uri); +} diff --git a/packages/mongodb-redact/src/redact-uri-credentials.ts b/packages/mongodb-redact/src/redact-uri-credentials.ts deleted file mode 100644 index da6a26f6..00000000 --- a/packages/mongodb-redact/src/redact-uri-credentials.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { redactConnectionString } from 'mongodb-connection-string-url'; - -export function redactUriCredentials(uri: string): string { - return redactConnectionString(uri); -}