Skip to content

RUBY-3835 redact credentials in URI rendering and InvalidURI errors#3039

Open
comandeo-mongo wants to merge 2 commits intomongodb:masterfrom
comandeo-mongo:RUBY-3835-redact-uri-password
Open

RUBY-3835 redact credentials in URI rendering and InvalidURI errors#3039
comandeo-mongo wants to merge 2 commits intomongodb:masterfrom
comandeo-mongo:RUBY-3835-redact-uri-password

Conversation

@comandeo-mongo
Copy link
Copy Markdown
Contributor

What

Cleartext passwords passed in the MongoDB connection string were exposed in three places:

  • Mongo::URI#to_sreconstruct_uri interpolated @password directly.
  • Mongo::URI#inspect — Ruby's default dumped @string and @password instance variables.
  • Mongo::Error::InvalidURI"Bad URI: #{uri}\n" interpolated the raw input.

Anywhere the result hit a logger, error reporter (Sentry/Bugsnag/Rollbar), or a backtrace surfaced in a UI, the password left the trust boundary.

How

  • New Mongo::URI.redact(string) class method returns the URI with userinfo replaced by a <credentials> placeholder. It works on raw strings (regex-based), so it is safe to call on malformed input — which is exactly when InvalidURI is raised.
  • URI#to_s and URI#inspect now use the placeholder. inspect no longer dumps instance variables.
  • Mongo::Error::InvalidURI redacts the URI argument before interpolation.
  • Existing roundtrips string shared examples now compare against the redacted form, so non-credential test fixtures are unaffected. Three test cases that hardcoded the credential-bearing output were updated.

Test plan

  • bundle exec rubocop lib/mongo/uri.rb lib/mongo/error/invalid_uri.rb spec/mongo/uri_spec.rb spec/mongo/uri/srv_protocol_spec.rb spec/mongo/error/invalid_uri_spec.rb — clean.
  • MONGODB_URI=... bundle exec rspec spec/mongo/uri_spec.rb spec/mongo/error/invalid_uri_spec.rb spec/mongo/uri_option_parsing_spec.rb spec/spec_tests/uri_options_spec.rb — 722 examples, 0 failures.
  • MONGODB_URI=... bundle exec rspec spec/mongo/uri/srv_protocol_spec.rb — pending tests skip (no DNS), no failures.
  • New tests: URI.redact, URI#to_s redaction, URI#inspect redaction, InvalidURI redaction for mongodb:// and mongodb+srv://, nil URI handling.

Jira

RUBY-3835

URI#to_s, URI#inspect, and Mongo::Error::InvalidURI all rendered the
cleartext password from the original connection string. The output of
each could end up in application logs, error reporters, or backtraces,
exposing credentials beyond the trust boundary.

Replace the userinfo with a fixed placeholder when reconstructing the
URI for display, and override inspect so it does not dump @string or
@password. Redact the URI passed to InvalidURI before interpolating it
into the message.
@comandeo-mongo comandeo-mongo marked this pull request as ready for review May 5, 2026 12:30
@comandeo-mongo comandeo-mongo requested a review from a team as a code owner May 5, 2026 12:30
@comandeo-mongo comandeo-mongo requested review from Copilot and jamis May 5, 2026 12:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses credential leakage risks by ensuring MongoDB connection strings are rendered with credentials redacted when converted to strings, inspected, or embedded into InvalidURI error messages.

Changes:

  • Added Mongo::URI.redact(string) and updated URI string rendering (#to_s, #inspect) to use a <credentials> placeholder.
  • Updated Mongo::Error::InvalidURI to redact URIs before interpolating into the error message.
  • Updated/added specs so roundtrip expectations and error-message assertions validate redacted output.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/mongo/uri.rb Introduces redaction helper/constants; updates #to_s and #inspect to avoid leaking credentials.
lib/mongo/error/invalid_uri.rb Redacts the provided URI before interpolating it into the exception message.
spec/mongo/uri_spec.rb Adds unit tests for .redact, #to_s redaction, and #inspect redaction; updates shared examples.
spec/mongo/uri/srv_protocol_spec.rb Updates expectations to compare against redacted to_s output for SRV URIs.
spec/mongo/error/invalid_uri_spec.rb Adds coverage ensuring InvalidURI messages redact credentials (including nil handling).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/mongo/uri.rb Outdated
Comment on lines +149 to +151
# Anchors at the start and stops at the first '/', '?', or '#' so it
# cannot accidentally redact something past the authority component.
USERINFO_REDACTION_REGEX = %r{\A(mongodb(?:\+srv)?://)[^/?#@]*@}.freeze
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are fair points I think. The necessity to securely handle malformed URI's means this code needs to be especially liberal in how it applies constraints. It's better to hide too much than too little, I think. If we match to the last @ instead of the first, that should address any leakage caused by the password itself containing an @ symbol. And making the regex case-insensitive is probably a good idea, too.

Comment thread spec/mongo/uri_spec.rb
Comment on lines +329 to +352
describe '.redact' do
it 'returns nil unchanged' do
expect(described_class.redact(nil)).to be_nil
end

it 'leaves a uri without userinfo unchanged' do
expect(described_class.redact('mongodb://localhost:27017'))
.to eq('mongodb://localhost:27017')
end

it 'replaces user and password with the placeholder' do
expect(described_class.redact('mongodb://alice:s3cret@host:27017/admin'))
.to eq('mongodb://<credentials>@host:27017/admin')
end

it 'redacts userinfo for mongodb+srv:// URIs' do
expect(described_class.redact('mongodb+srv://alice:s3cret@host'))
.to eq('mongodb+srv://<credentials>@host')
end

it 'redacts user-only userinfo' do
expect(described_class.redact('mongodb://alice@host'))
.to eq('mongodb://<credentials>@host')
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fair point, too.

Copy link
Copy Markdown
Contributor

@jamis jamis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Copilot on these issues it has raised; probably best to apply redactions too liberally than not liberally enough.

Comment thread lib/mongo/uri.rb Outdated
Comment on lines +149 to +151
# Anchors at the start and stops at the first '/', '?', or '#' so it
# cannot accidentally redact something past the authority component.
USERINFO_REDACTION_REGEX = %r{\A(mongodb(?:\+srv)?://)[^/?#@]*@}.freeze
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are fair points I think. The necessity to securely handle malformed URI's means this code needs to be especially liberal in how it applies constraints. It's better to hide too much than too little, I think. If we match to the last @ instead of the first, that should address any leakage caused by the password itself containing an @ symbol. And making the regex case-insensitive is probably a good idea, too.

Comment thread spec/mongo/uri_spec.rb
Comment on lines +329 to +352
describe '.redact' do
it 'returns nil unchanged' do
expect(described_class.redact(nil)).to be_nil
end

it 'leaves a uri without userinfo unchanged' do
expect(described_class.redact('mongodb://localhost:27017'))
.to eq('mongodb://localhost:27017')
end

it 'replaces user and password with the placeholder' do
expect(described_class.redact('mongodb://alice:s3cret@host:27017/admin'))
.to eq('mongodb://<credentials>@host:27017/admin')
end

it 'redacts userinfo for mongodb+srv:// URIs' do
expect(described_class.redact('mongodb+srv://alice:s3cret@host'))
.to eq('mongodb+srv://<credentials>@host')
end

it 'redacts user-only userinfo' do
expect(described_class.redact('mongodb://alice@host'))
.to eq('mongodb://<credentials>@host')
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fair point, too.

Tightens USERINFO_REDACTION_REGEX to handle two malformed-input cases
that the previous pattern leaked credentials on:

- Passwords containing an unescaped '@' (e.g. mongodb://alice:p@ss@host)
  now redact through the last '@' in the authority component instead of
  stopping at the first one. The character class drops '@' and relies on
  greedy backtracking bounded by '/', '?', '#' to find the right '@'.

- Schemes in mixed case (e.g. MongoDB://...) are now matched. The driver
  parser rejects these, so they always flow through the redactor on the
  way to InvalidURI; missing them guarantees a leak in the error.

Adds specs for both edge cases.
@comandeo-mongo comandeo-mongo requested a review from jamis May 6, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants