fix(config): send environment credentials only to trusted hosts
Description
Hardens how glab uses credentials from environment variables. Before this change, an environment-provided token (GITLAB_TOKEN/GITLAB_ACCESS_TOKEN/OAUTH_TOKEN, and CI_JOB_TOKEN under CI auto-login) was sent to whichever host a command targeted, including a host named only in a command argument:
glab repo view https://other.example.com/acme/awesome-toolThe affected path is specifically an explicit host argument (a URL argument or -R), which bypasses remote resolution through RepoOverride. Git remotes were never affected: on main, a remote pointing at an unrecognized host is already refused ("None of the git remotes configured for this repository point to a known GitLab host").
The token keys are declared ScopePerHost in internal/config/schema.go, but the environment branch of fileConfig.GetWithSource resolved them globally, before any per-host lookup.
What changed
-
internal/config/config.go: environment-provided credentials (token,job_token, and theis_oauth2scheme flag, including aliases) now resolve only for trusted hosts:- the default host, which is the global
hostkey: fromGITLAB_HOST/GITLAB_URI/GL_HOST, the configuration file, orCI_SERVER_FQDNunder CI auto-login, falling back togitlab.com. SettingGITLAB_HOSTalone is enough: noglab auth loginor configuration file is needed. A subfolder in the default host (for exampleGITLAB_HOST=https://example.com/gitlab) is supported. - any host present in the configuration's
hostsmap.
This mirrors the approach
ghtook in 2.63.0. A new exportedconfig.IsTrustedHostholds the check. - the default host, which is the global
-
internal/cmdutils/factory.go:ApiClientprints a stderr warning when a token or job token environment variable is set but was withheld for the target host, with an actionable hint (glab auth login --hostname <host>). -
Non-credential per-host keys (
api_host,client_id,subfolder, ...) intentionally keep resolving globally: they are not secrets, and flows such asglab auth loginread them (for exampleGITLAB_CLIENT_ID) for a host that is not configured yet. -
internal/config/schema.go+ regenerated docs: thetokendescription now states which hosts receive an environment token.
With this change, the command above completes unauthenticated (no credential headers are sent) and prints:
! GITLAB_TOKEN is set, but "other.example.com" is neither the default host nor a configured host, so glab did not send the credential to it. To authenticate to this host, run glab auth login --hostname other.example.com.Related Issues
Closes #8545
How has this been tested?
- Unit tests in
internal/config(Test_GetWithSource_CredentialEnvVarsRequireTrustedHost,TestIsTrustedHost) cover: default-host fallback togitlab.com, hosts-map membership, default host from the configuration file and fromGITLAB_HOST, a subfolder in the default host, case-insensitivity, key aliases,is_oauth2scoping, CI auto-login (CI_JOB_TOKENresolves for theCI_SERVER_FQDNhost only), non-credential keys still resolving globally, and empty-hostname reads. TestNewClientFromConfig_EnvTokenScopedToTrustedHostsininternal/apipins that a client for an unconfigured host carries no credential, while trusted hosts still receive the environment token.TestFactory_ApiClientEnvTokenWarningininternal/cmdutilspins the stderr warning.- Existing tests that encoded the old behavior (
internal/api/client_test.go,internal/api/oauth_provenance_test.go) now make their hosts trusted throughGITLAB_HOSTor the CI server variables. - The lefthook pre-push suite passes: build, lint, tests, the generated-docs check, and
markdownlint,vale, andlycheeon the regenerated documentation. - End-to-end against a local server that records inbound credential headers (
hosts: {}, token only in the environment, host passed as a command argument):- before this change: the server receives the token.
- with this change: no credential is sent, the warning is printed, and the command still returns the project data.
- control: setting
GITLAB_HOSTto the server's address makes it the trusted default host, and the token is sent as before.
Screenshots (if appropriate):
N/A