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?
answer
- ORG_GRADLE_PROJECT_<name>Username/Password
- secret store -> env var, never committed
- no echo / no -Psecret on logged command line
- build scans can leak inputs
- short-lived scoped tokens + rotation
basics
~10 sInject 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 sThe 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# 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
Know CI gets secrets from its secret store as env vars, not from the repo.
Map ORG_GRADLE_PROJECT_<name>Username/Password to the credentials convention and keep the build script unchanged across environments.
Address leak vectors (logs, -P, build scans, cache) and prefer scoped/short-lived tokens.
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.