Skip to content

Commit 1468529

Browse files
hsbtclaude
andcommitted
Redact URI userinfo from settings names in Bundler User-Agent
The `options/` segment of Bundler's User-Agent is built from `Bundler.settings.all`, which returns only setting names. Mirror settings, however, embed a URI in the key itself (`mirror.http://user:token@host/`), so the URI userinfo becomes part of the name. That name is then sent to every gem source Bundler contacts, including public or attacker-controlled sources, leaking the embedded credentials cross-origin. Strip the userinfo from any settings key that embeds a URI when building the User-Agent, keeping the useful telemetry (which settings are in use and the host) while dropping the secret. The redaction is limited to the User-Agent; `Bundler.settings.all` still returns the real keys, which `local_overrides` and `gem_mirrors` rely on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7288145 commit 1468529

2 files changed

Lines changed: 20 additions & 1 deletion

File tree

lib/bundler/fetcher.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,13 @@ def user_agent
215215
agent << " #{ruby.engine}/#{ruby.versions_string(engine_version)}"
216216
end
217217

218-
agent << " options/#{Bundler.settings.all.join(",")}"
218+
# Some settings keys embed a URI (e.g. `mirror.<uri>`) whose userinfo can
219+
# carry credentials. Strip the userinfo before advertising the setting
220+
# names so we don't leak secrets to every gem source we talk to.
221+
options = Bundler.settings.all.map do |key|
222+
key.include?("://") ? key.gsub(%r{//[^/@]+@}, "//") : key
223+
end
224+
agent << " options/#{options.join(",")}"
219225

220226
agent << " ci/#{cis.join(",")}" if cis.any?
221227

spec/bundler/fetcher_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@
141141
expect(fetcher.user_agent).to match(%r{options/foo,bar})
142142
end
143143

144+
it "strips userinfo from settings keys that embed a URI" do
145+
allow(Bundler.settings).to receive(:all).and_return(
146+
%w[foo mirror.http://user:token@example.org/ mirror.https://token@example.net/]
147+
)
148+
options = fetcher.user_agent.split(" ").find {|x| x.start_with?("options/") }
149+
150+
expect(options).not_to include("token")
151+
expect(options).not_to include("user:")
152+
expect(options).to include("mirror.http://example.org/")
153+
expect(options).to include("mirror.https://example.net/")
154+
expect(options).to include("foo")
155+
end
156+
144157
describe "include CI information" do
145158
it "from one CI" do
146159
with_env_vars("CI" => nil, "JENKINS_URL" => "foo") do

0 commit comments

Comments
 (0)