From 5fc2293489f9b2f2f4b3dc8b718421a0c34b371b Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Thu, 14 May 2026 08:48:06 -0400 Subject: [PATCH] feat: add buf beta registry cargo credential provider Add a new `buf beta registry cargo` subcommand that implements Cargo's `cargo:token-from-stdout` credential-provider protocol, so Cargo can obtain a Bearer token for BSR-hosted Cargo registries by shelling out to buf instead of storing tokens in `credentials.toml`. Users configure it via `~/.cargo/config.toml`: [registry] global-credential-providers = ["cargo:token-from-stdout buf beta registry cargo"] Behavior: - Reads CARGO_REGISTRY_INDEX_URL, extracts the host (strips an optional "sparse+" prefix, lowercases, drops the port). - Checks the host against a positional-argument allow-list. With no positional args the allow-list defaults to [buf.build]; positional args replace (not extend) the default. Ports in positional args are stripped so they compare equal to URL-extracted hosts. - For an allow-listed host, resolves a token via the existing bufconnect chain: BUF_TOKEN env var first, then ~/.netrc. - On success, writes `Bearer \n` to stdout and exits 0. - For hosts outside the allow-list (or a missing/unparseable URL), exits non-zero with no output so Cargo can fall through to its next configured credential provider. - For allow-listed hosts with no token, writes a "Failure: ..." message pointing at `buf registry login` and exits non-zero. - A malformed BUF_TOKEN produces a redacted user-visible error; the underlying bufconnect parser error (which embeds the raw token value) is logged at --debug level only. No new bufconnect or authentication primitives are introduced; the command is a thin orchestrator over existing token providers. Cargo invokes credential providers on every dependency resolution, so the implementation makes no network calls and constructs no Connect clients. Includes command-level tests covering the silent/loud contract, allow-list semantics, BUF_TOKEN precedence over .netrc, host case-insensitivity, host:port normalization, and a leak-prevention assertion that the BUF_TOKEN value never reaches stderr. --- CHANGELOG.md | 1 + cmd/buf/buf.go | 2 + .../registry/registrycargo/registrycargo.go | 208 ++++++++ .../registrycargo/registrycargo_test.go | 461 ++++++++++++++++++ 4 files changed, 672 insertions(+) create mode 100644 cmd/buf/internal/command/beta/registry/registrycargo/registrycargo.go create mode 100644 cmd/buf/internal/command/beta/registry/registrycargo/registrycargo_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f163253f..f5ae23acd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add LSP completion for `buf.gen.yaml`, `buf.yaml`, and `buf.policy.yaml` files. - Add error for when a dependency is added to `buf.yaml` and is missing from `buf.lock`. +- Add `buf beta registry cargo` subcommand that acts as a Cargo `cargo:token-from-stdout` credential provider for BSR-hosted Cargo registries. ## [v1.69.0] - 2026-04-29 diff --git a/cmd/buf/buf.go b/cmd/buf/buf.go index d1f1dcc4cb..0a9d6b46e1 100644 --- a/cmd/buf/buf.go +++ b/cmd/buf/buf.go @@ -36,6 +36,7 @@ import ( "github.com/bufbuild/buf/cmd/buf/internal/command/beta/price" betaplugindelete "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/plugin/plugindelete" betapluginpush "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/plugin/pluginpush" + "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/registrycargo" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/webhook/webhookcreate" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/webhook/webhookdelete" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/webhook/webhooklist" @@ -415,6 +416,7 @@ func newRootCommand(name string) *appcmd.Command { Use: "registry", Short: "Manage assets on the Buf Schema Registry", SubCommands: []*appcmd.Command{ + registrycargo.NewCommand("cargo", builder), { Use: "webhook", Short: "Manage webhooks for a repository on the Buf Schema Registry", diff --git a/cmd/buf/internal/command/beta/registry/registrycargo/registrycargo.go b/cmd/buf/internal/command/beta/registry/registrycargo/registrycargo.go new file mode 100644 index 0000000000..c33ab61159 --- /dev/null +++ b/cmd/buf/internal/command/beta/registry/registrycargo/registrycargo.go @@ -0,0 +1,208 @@ +// Copyright 2020-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package registrycargo implements the "buf beta registry cargo" command, +// which acts as a Cargo "cargo:token-from-stdout" credential provider for +// BSR-hosted Cargo registries. +package registrycargo + +import ( + "context" + "errors" + "fmt" + "net" + "net/url" + "slices" + "strings" + + "buf.build/go/app/appcmd" + "buf.build/go/app/appext" + "github.com/bufbuild/buf/private/bufpkg/bufconnect" + "github.com/bufbuild/buf/private/pkg/netrc" + "github.com/spf13/pflag" +) + +// errSilent is returned to exit with a non-zero status without printing a +// "Failure: ..." line to stderr. The top-level wrapError in cmd/buf/buf.go +// returns errors whose Error() is "" unchanged, bypassing the failure +// wrapping. This is the same mechanism used by other commands that need a +// silent non-zero exit (see private/pkg/bandeps/cmd/bandeps/main.go). +var errSilent = errors.New("") + +// NewCommand returns a new Command. +func NewCommand( + name string, + builder appext.SubCommandBuilder, +) *appcmd.Command { + flags := newFlags() + return &appcmd.Command{ + Use: name + " [host...]", + Short: "Cargo credential provider for BSR-hosted Cargo registries", + Long: `This command implements Cargo's "cargo:token-from-stdout" credential-provider protocol for BSR-hosted Cargo registries. + +Add the following to your ~/.cargo/config.toml to make Cargo use buf as its credential provider for the public BSR at buf.build: + + [registry] + global-credential-providers = ["cargo:token-from-stdout buf beta registry cargo"] + +To opt in to a different set of hosts (for example, an Enterprise BSR instance), list them as positional arguments. The positional arguments replace the default; buf.build is not implicitly included. Ports in positional arguments are stripped before matching, so the allow-list operates at the hostname level only: + + [registry] + global-credential-providers = ["cargo:token-from-stdout buf beta registry cargo bsr.example.com"] + +Multiple hosts are supported: + + [registry] + global-credential-providers = ["cargo:token-from-stdout buf beta registry cargo buf.build bsr.example.com"] + +Tokens are looked up using the existing buf authentication chain: the BUF_TOKEN environment variable, then ~/.netrc. Manage tokens with "buf registry login". + +Failure behavior: + + - If the host extracted from CARGO_REGISTRY_INDEX_URL is not in the allow-list (or no host can be extracted, or CARGO_REGISTRY_INDEX_URL is unset), the command exits non-zero with no output so Cargo can fall through to its next configured credential provider. + - If the host is in the allow-list but no token resolves, the command writes a "Failure:" message to stderr pointing at "buf registry login" and exits non-zero. + +Pass --debug to log host-resolution and token-lookup steps.`, + Args: appcmd.ArbitraryArgs, + Run: builder.NewRunFunc( + func(ctx context.Context, container appext.Container) error { + return run(ctx, container, flags) + }, + ), + BindFlags: flags.Bind, + } +} + +type flags struct{} + +func newFlags() *flags { + return &flags{} +} + +func (f *flags) Bind(flagSet *pflag.FlagSet) {} + +func run( + ctx context.Context, + container appext.Container, + flags *flags, +) error { + logger := container.Logger() + + rawURL := container.Env("CARGO_REGISTRY_INDEX_URL") + if rawURL == "" { + logger.Debug("CARGO_REGISTRY_INDEX_URL not set; nothing to do") + return errSilent + } + host := hostFromCargoRegistryURL(rawURL) + if host == "" { + logger.Debug( + "no host could be extracted from CARGO_REGISTRY_INDEX_URL", + "url", rawURL, + ) + return errSilent + } + + allowedHosts := effectiveAllowedHosts(container) + if !slices.Contains(allowedHosts, host) { + logger.Debug( + "host not in allow-list; falling through to next cargo credential provider", + "host", host, + "allowed_hosts", allowedHosts, + ) + return errSilent + } + + envTokenProvider, err := bufconnect.NewTokenProviderFromContainer(container) + if err != nil { + // Deliberately do not include err in the user-visible message: + // bufconnect.NewTokenProviderFromContainer formats the raw BUF_TOKEN + // value into its errors (see newMultipleTokenProvider in + // static_token_provider.go), which would echo the user's secret to + // stderr. The full error is available at --debug. + logger.Debug("BUF_TOKEN failed to parse", "error", err.Error()) + return fmt.Errorf( + `the %[1]s environment variable could not be parsed. Either unset %[1]s, or run "buf registry login %[2]s" to populate ~/.netrc instead. Run with --debug for parser details`, + bufconnect.TokenEnvKey, host, + ) + } + netrcTokenProvider := bufconnect.NewNetrcTokenProvider(container, netrc.GetMachineForName) + + for _, provider := range []bufconnect.TokenProvider{envTokenProvider, netrcTokenProvider} { + if token := provider.RemoteToken(host); token != "" { + if _, err := fmt.Fprintf(container.Stdout(), "Bearer %s\n", token); err != nil { + return err + } + return nil + } + } + + logger.Debug("no token found for host", "host", host) + return fmt.Errorf( + `no token found for %[1]s. Run "buf registry login %[1]s", using a Buf API token as the password. For details, visit https://buf.build/docs/bsr/authentication`, + host, + ) +} + +// hostFromCargoRegistryURL returns the host extracted from a Cargo registry +// index URL, or "" if no host can be determined. +// +// It strips a leading "sparse+" prefix and parses the remainder as a URL. +// The returned host has any port stripped and is lowercased so the allow- +// list comparison in run() is case-insensitive (DNS hosts are +// case-insensitive but net/url preserves the original case). +func hostFromCargoRegistryURL(rawURL string) string { + if rawURL == "" { + return "" + } + trimmed := strings.TrimPrefix(rawURL, "sparse+") + parsed, err := url.Parse(trimmed) + if err != nil { + return "" + } + return strings.ToLower(parsed.Hostname()) +} + +// effectiveAllowedHosts returns the positional host allow-list, normalized +// (lowercased, port stripped) for case-insensitive comparison against the +// URL host. If no positional arguments were supplied, returns the default +// [buf.build]. +func effectiveAllowedHosts(container appext.Container) []string { + numArgs := container.NumArgs() + if numArgs == 0 { + return []string{bufconnect.DefaultRemote} + } + hosts := make([]string, numArgs) + for i := range numArgs { + hosts[i] = normalizeHost(container.Arg(i)) + } + return hosts +} + +// normalizeHost lowercases s and strips an optional port. It accepts +// "host", "host:port", and "[host]:port" forms; if splitting fails (e.g. +// the input is a bare hostname with no port), the lowercased input is +// returned unchanged. This makes positional-arg allow-list entries +// comparable to URL-extracted hosts, which url.URL.Hostname() also +// returns without a port. +// +// Bare IPv6 literals (e.g. "::1") trip net.SplitHostPort's "too many +// colons" check and fall through to the unchanged-input branch, matching +// what url.URL.Hostname() returns for "[::1]" hosts. +func normalizeHost(s string) string { + lower := strings.ToLower(s) + if host, _, err := net.SplitHostPort(lower); err == nil { + return host + } + return lower +} diff --git a/cmd/buf/internal/command/beta/registry/registrycargo/registrycargo_test.go b/cmd/buf/internal/command/beta/registry/registrycargo/registrycargo_test.go new file mode 100644 index 0000000000..bbe96fa5b3 --- /dev/null +++ b/cmd/buf/internal/command/beta/registry/registrycargo/registrycargo_test.go @@ -0,0 +1,461 @@ +// Copyright 2020-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package registrycargo + +import ( + "bytes" + "context" + "fmt" + "maps" + "os" + "path/filepath" + "testing" + + "buf.build/go/app/appcmd" + "buf.build/go/app/appcmd/appcmdtesting" + "buf.build/go/app/appext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHostFromCargoRegistryURL(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + input string + expected string + }{ + { + name: "sparse_https_root", + input: "sparse+https://buf.build/gen/cargo/", + expected: "buf.build", + }, + { + name: "sparse_https_subpath", + input: "sparse+https://buf.build/gen/cargo/index/co/nn/connectrpc_eliza_community_neoeinstein-prost", + expected: "buf.build", + }, + { + name: "sparse_http", + input: "sparse+http://buf.build/gen/cargo/", + expected: "buf.build", + }, + { + name: "https_no_sparse_prefix", + input: "https://buf.build/", + expected: "buf.build", + }, + { + name: "sparse_https_with_port", + input: "sparse+https://buf.build:8443/gen/cargo/", + expected: "buf.build", + }, + { + name: "empty", + input: "", + expected: "", + }, + { + name: "scheme_only", + input: "sparse+https://", + expected: "", + }, + { + name: "path_only_no_scheme", + input: "buf.build/gen/cargo/", + expected: "", + }, + { + name: "sparse_plus_path_only", + input: "sparse+buf.build/gen/cargo/", + expected: "", + }, + { + name: "uppercase_host_lowercased", + input: "sparse+https://BUF.BUILD/gen/cargo/", + expected: "buf.build", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.expected, hostFromCargoRegistryURL(tc.input)) + }) + } +} + +func TestNormalizeHost(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + input string + expected string + }{ + {name: "bare", input: "buf.build", expected: "buf.build"}, + {name: "with_port", input: "buf.build:8443", expected: "buf.build"}, + {name: "uppercase", input: "BUF.BUILD", expected: "buf.build"}, + {name: "uppercase_with_port", input: "BUF.BUILD:8443", expected: "buf.build"}, + {name: "ipv6_with_port", input: "[::1]:8443", expected: "::1"}, + {name: "empty", input: "", expected: ""}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.expected, normalizeHost(tc.input)) + }) + } +} + +const cargoIndexBufBuild = "sparse+https://buf.build/gen/cargo/" + +// runSilent invokes the command and asserts a silent exit 1: stderr must be +// empty (no "Failure:" line). Use for paths where the URL is missing, no +// host can be extracted, or the host isn't in the allow-list. +func runSilent( + t *testing.T, + envOverrides map[string]string, + args ...string, +) { + t.Helper() + runCargo( + t, + envOverrides, + 1, + "", + nil, + nil, + args..., + ) +} + +// runSuccess invokes the command and asserts exit 0 with the given stdout. +func runSuccess( + t *testing.T, + envOverrides map[string]string, + expectedStdout string, + args ...string, +) { + t.Helper() + runCargo( + t, + envOverrides, + 0, + expectedStdout, + nil, + nil, + args..., + ) +} + +// runLoud invokes the command and asserts exit 1 with stderr containing each +// of the given substrings. Use for paths where the host *is* in the +// allow-list but no token resolves. +func runLoud( + t *testing.T, + envOverrides map[string]string, + stderrSubstrings []string, + args ...string, +) { + t.Helper() + require.NotEmpty(t, stderrSubstrings, "loud failure tests must assert at least one stderr substring") + runCargo( + t, + envOverrides, + 1, + "", + stderrSubstrings, + nil, + args..., + ) +} + +// runCargo is the underlying harness. The interceptor mirrors the subset of +// cmd/buf/buf.go's wrapError that this command actually exercises: empty- +// message non-Connect errors pass through unchanged (silent failure), and +// any other error gets the "Failure: " prefix. wrapError has additional +// handling for *connect.Error, syserror, ImportNotExistError, etc. that the +// real binary applies in production but this command never produces, so the +// interceptor does not replicate them. +// +// expectedStderrPartials: nil means assert stderr is empty; a non-nil slice +// means assert each substring is present. +// +// forbiddenStderrSubstrings: any substring listed here must NOT appear in +// stderr. Used for leak-prevention assertions (e.g. that a wrapped error +// does not echo a sensitive value from the underlying cause). +func runCargo( + t *testing.T, + envOverrides map[string]string, + expectedExitCode int, + expectedStdout string, + expectedStderrPartials []string, + forbiddenStderrSubstrings []string, + args ...string, +) { + t.Helper() + var stderrBuf *bytes.Buffer + options := []appcmdtesting.RunOption{ + appcmdtesting.WithEnv(func(string) map[string]string { + env := map[string]string{ + "PATH": os.Getenv("PATH"), + } + maps.Copy(env, envOverrides) + return env + }), + appcmdtesting.WithExpectedExitCode(expectedExitCode), + appcmdtesting.WithExpectedStdout(expectedStdout), + appcmdtesting.WithArgs(args...), + } + if len(forbiddenStderrSubstrings) > 0 { + stderrBuf = &bytes.Buffer{} + options = append(options, appcmdtesting.WithStderr(stderrBuf)) + } + if expectedStderrPartials == nil { + options = append(options, appcmdtesting.WithExpectedStderrPartials()) + } else { + options = append(options, appcmdtesting.WithExpectedStderrPartials(expectedStderrPartials...)) + } + appcmdtesting.Run( + t, + func(name string) *appcmd.Command { + return NewCommand( + name, + appext.NewBuilder( + name, + appext.BuilderWithInterceptor( + func(next func(context.Context, appext.Container) error) func(context.Context, appext.Container) error { + return func(ctx context.Context, container appext.Container) error { + err := next(ctx, container) + if err == nil { + return nil + } + if err.Error() == "" { + return err + } + return fmt.Errorf("Failure: %w", err) + } + }, + ), + ), + ) + }, + options..., + ) + if stderrBuf != nil { + stderrText := stderrBuf.String() + for _, forbidden := range forbiddenStderrSubstrings { + assert.NotContains(t, stderrText, forbidden, "stderr leaked forbidden substring") + } + } +} + +// writeNetrc writes a netrc file with a single machine entry and returns its +// path. +func writeNetrc(t *testing.T, machine, login, password string) string { + t.Helper() + path := filepath.Join(t.TempDir(), ".netrc") + contents := fmt.Sprintf("machine %s\n login %s\n password %s\n", machine, login, password) + require.NoError(t, os.WriteFile(path, []byte(contents), 0o600)) + return path +} + +func TestCargo_UnsetURL_SilentExit1(t *testing.T) { + t.Parallel() + runSilent(t, map[string]string{}) +} + +func TestCargo_NoArgs_DefaultAllowList_BufBuild_UnscopedToken(t *testing.T) { + t.Parallel() + runSuccess(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "BUF_TOKEN": "plain-token", + }, + "Bearer plain-token", + ) +} + +func TestCargo_NoArgs_DefaultAllowList_OtherHost_SilentExit1(t *testing.T) { + t.Parallel() + runSilent(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": "sparse+https://other.example.com/gen/cargo/", + "BUF_TOKEN": "plain-token", + }, + ) +} + +func TestCargo_ExplicitHost_Matches(t *testing.T) { + t.Parallel() + runSuccess(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": "sparse+https://bsr.example.com/gen/cargo/", + "BUF_TOKEN": "plain-token", + }, + "Bearer plain-token", + "bsr.example.com", + ) +} + +func TestCargo_PositionalHostWithPort_MatchesURL(t *testing.T) { + t.Parallel() + runSuccess(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": "sparse+https://bsr.example.com:8443/gen/cargo/", + "BUF_TOKEN": "plain-token", + }, + "Bearer plain-token", + "bsr.example.com:8443", + ) +} + +func TestCargo_PositionalHostUppercase_MatchesURL(t *testing.T) { + t.Parallel() + runSuccess(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": "sparse+https://bsr.example.com/gen/cargo/", + "BUF_TOKEN": "plain-token", + }, + "Bearer plain-token", + "BSR.example.com", + ) +} + +func TestCargo_ExplicitHost_DoesNotIncludeDefault_BufBuild_Silent(t *testing.T) { + t.Parallel() + runSilent(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "BUF_TOKEN": "plain-token", + }, + "bsr.example.com", + ) +} + +func TestCargo_MultipleExplicitHosts_RequestMatchesOne(t *testing.T) { + t.Parallel() + runSuccess(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "BUF_TOKEN": "plain-token", + }, + "Bearer plain-token", + "a.example.com", "buf.build", + ) +} + +func TestCargo_ScopedToken_Match(t *testing.T) { + t.Parallel() + runSuccess(t, + map[string]string{ //nolint:gosec // G101: BUF_TOKEN literals are synthetic env fixtures for contract tests. + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "BUF_TOKEN": "scoped-tok@buf.build", + }, + "Bearer scoped-tok", + ) +} + +func TestCargo_MalformedBufToken_LoudFailureMentionsBufToken(t *testing.T) { + t.Parallel() + const secret = "supersecret-do-not-leak" + runCargo( + t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + // Duplicate remote triggers newMultipleTokenProvider's + // "repeated remote address" error, which embeds the raw token. + "BUF_TOKEN": secret + "@buf.build," + secret + "@buf.build", + }, + 1, + "", + []string{ + "Failure:", + "BUF_TOKEN environment variable could not be parsed", + "buf registry login buf.build", + "unset BUF_TOKEN", + }, + // The wrapped error must NOT echo the raw token; the parser-level + // detail is debug-only. + []string{secret}, + ) +} + +func TestCargo_ScopedToken_NoMatchForHost_LoudFailure(t *testing.T) { + t.Parallel() + runLoud(t, + map[string]string{ //nolint:gosec // G101: BUF_TOKEN literals are synthetic env fixtures for contract tests. + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "BUF_TOKEN": "scoped-tok@elsewhere.example.com", + }, + []string{ + "Failure:", + "no token found for buf.build", + "\"buf registry login buf.build\"", + }, + ) +} + +func TestCargo_NetrcMatch(t *testing.T) { + t.Parallel() + netrcPath := writeNetrc(t, "buf.build", "user", "netrc-secret") + runSuccess(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "NETRC": netrcPath, + }, + "Bearer netrc-secret", + ) +} + +func TestCargo_ScopedTokenNoMatch_FallsThroughToNetrc(t *testing.T) { + t.Parallel() + netrcPath := writeNetrc(t, "buf.build", "user", "netrc-secret") + runSuccess(t, + map[string]string{ //nolint:gosec // G101: BUF_TOKEN literals are synthetic env fixtures for contract tests. + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "BUF_TOKEN": "scoped-tok@elsewhere.example.com", + "NETRC": netrcPath, + }, + "Bearer netrc-secret", + ) +} + +func TestCargo_BufTokenWinsOverNetrc(t *testing.T) { + t.Parallel() + netrcPath := writeNetrc(t, "buf.build", "user", "netrc-loses") + runSuccess(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "BUF_TOKEN": "env-wins", + "NETRC": netrcPath, + }, + "Bearer env-wins", + ) +} + +func TestCargo_NetrcMiss_NoEnvVar_LoudFailure(t *testing.T) { + t.Parallel() + emptyNetrc := writeNetrc(t, "other.example.com", "user", "irrelevant") + runLoud(t, + map[string]string{ + "CARGO_REGISTRY_INDEX_URL": cargoIndexBufBuild, + "NETRC": emptyNetrc, + }, + []string{ + "Failure:", + "no token found for buf.build", + }, + ) +}