skip to content

How do you supply private-repository credentials in CI without committing secrets, and how do you keep them from leaking into build logs or the cache?

level: seniorimportance: should knowfreq 40%

answer

  1. ORG_GRADLE_PROJECT_<name>Username/Password
  2. secret store -> env var, never committed
  3. no echo / no -Psecret on logged command line
  4. build scans can leak inputs
  5. short-lived scoped tokens + rotation

basics

~10 s

Inject credentials as ORG_GRADLE_PROJECT_<name>Username/Password environment variables from the CI secret store. Keep them out of the repo, never echo them, and let Gradle treat them as credentials so they aren't printed.

solid answer

~40 s

The clean pattern is: the build script declares `credentials(PasswordCredentials::class)` (or HttpHeaderCredentials) with no literal values, developers put secrets in `~/.gradle/gradle.properties`, and CI provides the same values via `ORG_GRADLE_PROJECT_<repoName>Username` / `...Password` environment variables sourced from its secret manager. This way the build script is identical everywhere and no secret touches version control. To avoid leaks: never `echo` the variables, don't pass secrets as `-P` on a command line that gets logged, run with `--no-scan` or scrub build scans, and rely on Gradle treating registered credentials as sensitive. Prefer short-lived/scoped tokens (e.g. CI job tokens) over long-lived passwords so a leak has limited blast radius.

code

bash · 5 lines
bash
# CI injects from its secret store (values masked in logs):
export ORG_GRADLE_PROJECT_mavenPrivateUsername="$CI_REPO_USER"
export ORG_GRADLE_PROJECT_mavenPrivatePassword="$CI_REPO_TOKEN"

./gradlew build   # build script unchanged: credentials(PasswordCredentials::class)

go deeper

for a junior

Know CI gets secrets from its secret store as env vars, not from the repo.

for a middle

Map ORG_GRADLE_PROJECT_<name>Username/Password to the credentials convention and keep the build script unchanged across environments.

for a senior

Address leak vectors (logs, -P, build scans, cache) and prefer scoped/short-lived tokens.

for a principal

Define org policy: central secret store, masking, rotation cadence, scoped tokens, and audit/incident response for credential exposure.

## The goal Produce one build script that authenticates to private repos in every environment — laptop, CI, release runner — without ever committing a secret and without printing it. ## The injection mechanism Gradle maps environment variables of the form `ORG_GRADLE_PROJECT_<prop>` to the Gradle property `<prop>`. Combined with the credentials naming convention, a repo named `mavenPrivate` reads its username/password from: - `ORG_GRADLE_PROJECT_mavenPrivateUsername` - `ORG_GRADLE_PROJECT_mavenPrivatePassword` CI defines those env vars from its secret store (GitHub Actions secrets, GitLab CI/CD variables, Vault, etc.). Locally, the same property names live in `~/.gradle/gradle.properties`. The build script stays: ```kotlin repositories { maven { name = "mavenPrivate" url = uri("https://repo.example.com/private") credentials(PasswordCredentials::class) } } ``` ## Avoiding leaks 1. **Never echo** the secret env vars in pipeline scripts; CI usually masks declared secrets in logs but only if you don't transform them. 2. **Avoid `-Pkey=secret` on the command line** — process listings and verbose logs can capture it; use env vars or gradle.properties instead. 3. **Build scans**: a published scan can capture environment/inputs; either disable scans for credentialed builds or ensure scan redaction is configured. 4. **Configuration cache**: source credentials through providers/typed credentials so Gradle tracks them as sensitive inputs rather than eagerly serializing raw strings. 5. **Scope and rotate**: use short-lived job tokens or deploy tokens with read-only scope; rotate on schedule and on suspected exposure. ## Why env-var injection beats baked files Writing a gradle.properties on the CI runner with the secret inline risks the file lingering in a cached workspace or an artifact. Env vars exist only for the job's process and are masked by the platform, giving a smaller exposure window.

  • Why prefer ORG_GRADLE_PROJECT_ env vars over writing the secret into a gradle.properties file on the CI runner?
    Env vars live only for the job process and are masked by the platform; a written file can linger in a cached workspace or be archived as an artifact, widening the exposure window.
  • Why avoid passing the password as -Pname=secret on the command line?
    Command lines appear in process listings and verbose/CI logs, so the secret can be captured. Env vars and gradle.properties keep it off the command line.
  • What reduces the blast radius if a CI credential does leak?
    Using short-lived, narrowly-scoped (e.g. read-only deploy/job) tokens and rotating them, so an exposed credential expires quickly and can do little.

saying these in an interview costs you the question

  • Echoing secret env vars in pipeline steps, defeating CI log masking.
  • Committing a CI-specific gradle.properties with the secret into the repo.
  • Passing secrets via -P on a logged command line.

context