Skip to content

Commit 3ca8498

Browse files
authored
cargo: strip credential-provider keys from .cargo/config.toml (#14359)
## Problem PR #14340 set `CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS=""` to disable Cargo's credential lookup globally. However, per-registry `credential-provider` settings in .cargo/config.toml override the global env var: [registries.artifactory] credential-provider = "cargo:token" Cargo then invokes `cargo:token` for that registry, tries to look up `CARGO_REGISTRIES_ARTIFACTORY_TOKEN`, finds nothing, and fails with 'no token found'. ## Solution Parse .cargo/config.toml with TomlRB and strip `credential-provider` keys from both `[registries.*]` and `[registry]` sections before writing the config to the temporary working directory. Combined with the existing `CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS=""` from #14340, this ensures Cargo never tries to authenticate on its own — all HTTP requests go through the dependabot proxy unauthenticated, and the proxy injects the real credentials transparently. Uses `is_a?(Hash)` guards rather than `T.let` casts so that unexpected config structure (valid TOML but not the shape we expect) is safely skipped rather than raising. Fixes #14354
1 parent 2704e65 commit 3ca8498

4 files changed

Lines changed: 180 additions & 13 deletions

File tree

cargo/lib/dependabot/cargo/file_updater/lockfile_updater.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,11 @@ def write_temporary_dependency_files
304304

305305
File.write(lockfile.name, lockfile.content)
306306
File.write(T.must(toolchain).name, T.must(toolchain).content) if toolchain
307-
return unless config
307+
config_file = config
308+
return unless config_file
308309

309-
FileUtils.mkdir_p(File.dirname(T.must(config).name))
310-
File.write(T.must(config).name, T.must(config).content)
310+
FileUtils.mkdir_p(File.dirname(config_file.name))
311+
File.write(config_file.name, Helpers.sanitize_cargo_config(T.must(config_file.content)))
311312
end
312313

313314
sig { void }
Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,61 @@
1-
# typed: strong
1+
# typed: strict
22
# frozen_string_literal: true
33

4+
require "toml-rb"
5+
46
module Dependabot
57
module Cargo
68
module Helpers
79
extend T::Sig
810

11+
# Disable Cargo's *global* credential providers so that Cargo does not attempt to look up registry tokens
12+
# on its own. The dependabot proxy (https://github.com/dependabot/proxy/) handles all registry authentication
13+
# transparently by intercepting HTTP requests and injecting the appropriate credentials.
14+
#
15+
# Note: this only affects the global/default credential provider. Per-registry `credential-provider` settings
16+
# in .cargo/config.toml override this env var, so those are stripped separately by `sanitize_cargo_config`.
17+
#
18+
# Uses ||= so developers can override by setting CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS=cargo:token in their
19+
# shell (along with the appropriate CARGO_REGISTRIES_{NAME}_TOKEN vars) for local development without the proxy.
920
sig { void }
1021
def self.bypass_cargo_credential_providers
11-
# Disable Cargo's built-in credential providers entirely so that Cargo does not attempt to look up registry
12-
# tokens on its own. The dependabot proxy (https://github.com/dependabot/proxy/) handles all registry
13-
# authentication transparently by intercepting HTTP requests and injecting the appropriate credentials.
14-
#
15-
# Uses ||= so developers can override by setting CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS=cargo:token in their
16-
# shell (along with the appropriate CARGO_REGISTRIES_{NAME}_TOKEN vars) for local development without the proxy.
1722
ENV["CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS"] ||= ""
1823
end
24+
25+
# Strip per-registry `credential-provider` settings from .cargo/config.toml.
26+
#
27+
# Users may have entries like:
28+
# [registries.my-registry]
29+
# credential-provider = "cargo:token"
30+
#
31+
# These per-registry settings override the global CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS env var,
32+
# causing Cargo to look up tokens locally. Since the dependabot proxy handles all registry authentication
33+
# transparently, we remove these so Cargo makes plain unauthenticated requests that the proxy can intercept.
34+
sig { params(config_content: String).returns(String) }
35+
def self.sanitize_cargo_config(config_content)
36+
parsed = TomlRB.parse(config_content)
37+
return config_content unless parsed.is_a?(Hash)
38+
39+
registries = parsed["registries"]
40+
if registries.is_a?(Hash)
41+
registries.each_value do |registry_config|
42+
registry_config.delete("credential-provider") if registry_config.is_a?(Hash)
43+
end
44+
end
45+
46+
# Also strip credential-provider from [registry] (crates.io default registry). Users who `cargo publish`
47+
# from CI may have this set. It's a per-registry override that takes precedence over the global env var,
48+
# so we need to remove it to prevent Cargo from trying to look up a token.
49+
registry = parsed["registry"]
50+
registry.delete("credential-provider") if registry.is_a?(Hash)
51+
52+
TomlRB.dump(parsed)
53+
rescue TomlRB::Error => e
54+
raise Dependabot::DependencyFileNotParseable.new(
55+
".cargo/config.toml",
56+
"Failed to parse Cargo config file: #{e.message}"
57+
)
58+
end
1959
end
2060
end
2161
end

cargo/lib/dependabot/cargo/update_checker/version_resolver.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,11 @@ def write_temporary_dependency_files(prepared: true)
215215

216216
File.write(T.must(lockfile).name, T.must(lockfile).content) if lockfile
217217
File.write(T.must(toolchain).name, T.must(toolchain).content) if toolchain
218-
return unless config
218+
config_file = config
219+
return unless config_file
219220

220-
FileUtils.mkdir_p(File.dirname(T.must(config).name))
221-
File.write(T.must(config).name, T.must(config).content)
221+
FileUtils.mkdir_p(File.dirname(config_file.name))
222+
File.write(config_file.name, Helpers.sanitize_cargo_config(T.must(config_file.content)))
222223
end
223224

224225
sig { void }

cargo/spec/dependabot/cargo/helpers_spec.rb

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,129 @@
3434
end
3535
end
3636
end
37+
38+
describe ".sanitize_cargo_config" do
39+
context "when config has no credential-provider settings" do
40+
let(:config_content) do
41+
<<~TOML
42+
[registries.my-registry]
43+
index = "sparse+https://example.com/index/"
44+
token = "some-token"
45+
TOML
46+
end
47+
48+
it "returns equivalent config with non-credential-provider keys preserved" do
49+
result = described_class.sanitize_cargo_config(config_content)
50+
parsed = TomlRB.parse(result)
51+
expect(parsed["registries"]["my-registry"]["index"]).to eq("sparse+https://example.com/index/")
52+
expect(parsed["registries"]["my-registry"]["token"]).to eq("some-token")
53+
end
54+
end
55+
56+
context "when config has no registries section" do
57+
let(:config_content) do
58+
<<~TOML
59+
[source.crates-io]
60+
replace-with = "my-mirror"
61+
62+
[net]
63+
git-fetch-with-cli = true
64+
TOML
65+
end
66+
67+
it "returns equivalent config unchanged" do
68+
result = described_class.sanitize_cargo_config(config_content)
69+
parsed = TomlRB.parse(result)
70+
expect(parsed["source"]["crates-io"]["replace-with"]).to eq("my-mirror")
71+
expect(parsed["net"]["git-fetch-with-cli"]).to be true
72+
end
73+
end
74+
75+
context "when config has per-registry credential-provider" do
76+
let(:config_content) do
77+
<<~TOML
78+
[registries.artifactory]
79+
index = "sparse+https://example.com/api/cargo/cargo-local/index/"
80+
credential-provider = "cargo:token"
81+
82+
[registries.artifactory-remote]
83+
index = "sparse+https://example.com/api/cargo/cargo-crates-remote/index/"
84+
credential-provider = "cargo:token"
85+
TOML
86+
end
87+
88+
it "strips credential-provider from all registries" do
89+
result = described_class.sanitize_cargo_config(config_content)
90+
parsed = TomlRB.parse(result)
91+
92+
expect(parsed["registries"]["artifactory"]).not_to have_key("credential-provider")
93+
expect(parsed["registries"]["artifactory-remote"]).not_to have_key("credential-provider")
94+
end
95+
96+
it "preserves index URLs" do
97+
result = described_class.sanitize_cargo_config(config_content)
98+
parsed = TomlRB.parse(result)
99+
100+
expect(parsed["registries"]["artifactory"]["index"])
101+
.to eq("sparse+https://example.com/api/cargo/cargo-local/index/")
102+
expect(parsed["registries"]["artifactory-remote"]["index"])
103+
.to eq("sparse+https://example.com/api/cargo/cargo-crates-remote/index/")
104+
end
105+
end
106+
107+
context "when config has [registry] credential-provider (e.g. for cargo publish)" do
108+
let(:config_content) do
109+
<<~TOML
110+
[registry]
111+
credential-provider = "cargo:token"
112+
TOML
113+
end
114+
115+
it "strips the credential-provider" do
116+
result = described_class.sanitize_cargo_config(config_content)
117+
parsed = TomlRB.parse(result)
118+
119+
expect(parsed.fetch("registry", {})).not_to have_key("credential-provider")
120+
end
121+
end
122+
123+
context "when config has mixed settings" do
124+
let(:config_content) do
125+
<<~TOML
126+
[registries.with-cred]
127+
index = "sparse+https://example.com/index/"
128+
credential-provider = "cargo:token"
129+
130+
[registries.without-cred]
131+
index = "sparse+https://other.example.com/index/"
132+
133+
[source.crates-io]
134+
replace-with = "with-cred"
135+
136+
[net]
137+
git-fetch-with-cli = true
138+
TOML
139+
end
140+
141+
it "strips only credential-provider, preserves everything else" do
142+
result = described_class.sanitize_cargo_config(config_content)
143+
parsed = TomlRB.parse(result)
144+
145+
expect(parsed["registries"]["with-cred"]).not_to have_key("credential-provider")
146+
expect(parsed["registries"]["with-cred"]["index"]).to eq("sparse+https://example.com/index/")
147+
expect(parsed["registries"]["without-cred"]["index"]).to eq("sparse+https://other.example.com/index/")
148+
expect(parsed["source"]["crates-io"]["replace-with"]).to eq("with-cred")
149+
expect(parsed["net"]["git-fetch-with-cli"]).to be true
150+
end
151+
end
152+
153+
context "when config is malformed TOML" do
154+
let(:config_content) { "this is not valid toml {{{{" }
155+
156+
it "raises DependencyFileNotParseable" do
157+
expect { described_class.sanitize_cargo_config(config_content) }
158+
.to raise_error(Dependabot::DependencyFileNotParseable, /Failed to parse Cargo config file/)
159+
end
160+
end
161+
end
37162
end

0 commit comments

Comments
 (0)