gitlab-runner verify --url= fails when CI_SERVER_TOKEN is set in environment

Summary

When running gitlab-runner verify --url= (empty URL to bypass URL filtering), the presence of CI_SERVER_TOKEN in the environment causes the command to fail with no runner matches the filtering parameters, even when the token matches a registered runner.

Steps to reproduce

  1. Register a runner (creates config.toml with a [[runners]] entry)
  2. Set CI_SERVER_TOKEN environment variable to the same token value as in config.toml
  3. Run gitlab-runner verify --url=
export CI_SERVER_TOKEN="glrtr-xxxxx"  # same token as in config.toml
gitlab-runner verify --url=
# FATAL: no runner matches the filtering parameters
.gitlab-ci.yml
N/A - This bug affects the gitlab-runner verify command, not CI job execution.

Actual behavior

FATAL: no runner matches the filtering parameters

Expected behavior

gitlab-runner verify --url= should verify the runner, since:

  • The empty --url= is intended to bypass URL filtering (select all runners)

  • The token in the environment matches the token in config.toml

At minimum, when both URL and Token are used as selectors, an empty URL should be treated as "don't filter by URL" rather than "must match empty string".

The GitLab Runner Helm chart's check-live script uses --url= with this comment:

# empty --url= helps `gitlab-runner verify` select all configured runners (otherwise filters for $CI_SERVER_URL)

This establishes --url= as an intended pattern, not a hack.

Relevant logs and/or screenshots

runner log
$ export CI_SERVER_TOKEN="glrtr-xxxxx"  # same token as in config.toml
$ gitlab-runner verify --url=
FATAL: no runner matches the filtering parameters

$ unset CI_SERVER_TOKEN
$ gitlab-runner verify --url=
Runtime platform                                    arch=arm64 os=linux pidpid=30301 revision=bda84871 version=18.5.0
Verifying runner... is valid                       correlation_id=01KEVT00YCSA5A1NPBZGHG63F7 runner=pXm12x1KwEnvironment description

This affects any environment where CI_SERVER_TOKEN persists as an environment variable during gitlab-runner verify execution.

Common scenario: Kubernetes deployments using secret injection (Vault CSI provider, external-secrets operator, or envFrom) where secrets are set at container level rather than mounted as files.

The GitLab Runner Helm chart's liveness probe relies on gitlab-runner verify --url= to health-check runners. This works when secrets are file-mounted (env var only exists in entrypoint shell), but fails when secrets are environment-injected (env var exists in all processes including liveness probe).

config.toml contents
concurrent = 1
check_interval = 0

[[runners]]
  name = "my-runner"
  url = "https://gitlab.example.com/"
  token = "glrtr-xxxxx"
  executor = "kubernetes"

Used GitLab Runner version

gitlab-runner --version 
Version: 18.5.0 
Git revision: bda84871 
Git branch: 18-5-stable 
GO version: go1.24.6 X:cacheprog 
Built: 2025-10-13T19:20:30Z 
OS/Arch: linux/amd64

Possible fixes

The issue is in `commands/verify.go` lines 32-34:

var hasSelector = c.Name != "" ||
  c.RunnerCredentials.URL != "" ||
  c.RunnerCredentials.Token != ""

And `common/config.go` `SameAs()` function:

func (c *RunnerCredentials) SameAs(other *RunnerCredentials) bool {
  return c.URL == other.URL && c.Token == other.Token
}

When --url= sets URL to empty but CI_SERVER_TOKEN env var sets Token, hasSelector=true and SameAs() fails because empty URL doesn't match the runner's actual URL.

Suggested fixes:

  1. Treat empty fields as wildcards in SameAs():

    func (c *RunnerCredentials) SameAs(other *RunnerCredentials) bool {
        urlMatch := c.URL == "" || other.URL == "" || c.URL == other.URL
        tokenMatch := c.Token == "" || other.Token == "" || c.Token == other.Token
        return urlMatch && tokenMatch
    }

           

  2. Or add explicit --all flag to verify all runners regardless of env vars

  3. Or exclude CI_SERVER_TOKEN/REGISTRATION_TOKEN from populating RunnerCredentials in verify command (these are registration env vars, not filtering env vars)

Workaround

Inject the token as a differently-named env var (e.g., _RUNNER_AUTH_TOKEN) and export it as CI_SERVER_TOKEN within the entrypoint script. This keeps CI_SERVER_TOKEN out of the pod's global environment while still making it available for registration. This requires overriding the entrypoint script