Where should build secrets (like repository or signing credentials) live in the gradle.properties hierarchy, and why?
answer
- secrets in ~/.gradle, never committed root file
- Git history keeps leaked secrets forever
- build reads by name, source-agnostic
- CI: ORG_GRADLE_PROJECT_ env var injection
- root file = non-secret shared config only
basics
~10 sPut secrets in GRADLE_USER_HOME/gradle.properties (~/.gradle), which is outside the repo and per-machine. Never put them in the committed project-root gradle.properties. On CI, inject them as env vars instead.
solid answer
~50 sSecrets belong in **`GRADLE_USER_HOME/gradle.properties`**, not the project-root file. The project-root file is committed to Git, so any credential there leaks to everyone with repo access and ends up in history forever. The user-home file lives outside the repository and is per-developer, so it's the safe home for signing keys, publishing tokens, and repository passwords. The build reads them by name — e.g. `providers.gradleProperty("myRepoPassword")` — without caring which file supplied the value, so moving a secret from the committed file to user-home is transparent to build logic. On **CI**, where there is no interactive `~/.gradle` to maintain by hand, the standard approach is to feed the same property via an environment variable using the `ORG_GRADLE_PROJECT_<name>` form (or write a user-home file from the secret store at job start). The committed project-root file should hold only non-sensitive shared flags and versions.
code
kotlin · 14 lines// build.gradle.kts — reads the secret by name, agnostic to source
publishing {
repositories {
maven {
url = uri("https://repo.example.com/releases")
credentials {
username = providers.gradleProperty("myRepoUsername").get()
password = providers.gradleProperty("myRepoPassword").get()
}
}
}
}
// Value comes from ~/.gradle/gradle.properties locally,
// or ORG_GRADLE_PROJECT_myRepoPassword on CI.go deeper
Say secrets go in ~/.gradle, never in the committed file.
Explain the in-repo vs out-of-repo boundary and that CI uses env-var injection rather than a checked-in file.
Add history-permanence of leaked secrets, source-agnostic reads by property name, and rotation when a leak occurs.
Cover org-wide secret governance: central secret stores, standardized CI injection, scanning for committed secrets, and per-repo GRADLE_USER_HOME isolation.
## The rule **Secrets go where the repo doesn't reach.** That means `GRADLE_USER_HOME/gradle.properties` (default `~/.gradle/gradle.properties`) for local development, and an injected environment variable on CI. The committed **project-root** `gradle.properties` must contain only non-sensitive, shareable configuration. ## Why not the project-root file The project-root file is version-controlled. A credential placed there is: - visible to everyone who can read the repo, - baked into Git history (so deleting it later doesn't truly remove it), and - often replicated into forks, mirrors, and build logs. That's an unacceptable disclosure surface for a password or signing key. ## Why user-home works `GRADLE_USER_HOME/gradle.properties` sits *outside* the project tree and is per-machine, so it never enters version control. It also **outranks** the project-root file in precedence, so a value defined there cleanly wins for that developer. The build references the secret by property name and is agnostic to the source: ```kotlin // build.gradle.kts val repoPassword = providers.gradleProperty("myRepoPassword") publishing { repositories { maven { url = uri("https://repo.example.com/releases") credentials { username = providers.gradleProperty("myRepoUsername").get() password = repoPassword.get() } } } } ``` ```properties # ~/.gradle/gradle.properties (never committed) myRepoUsername=alice myRepoPassword=s3cr3t ``` ## CI: no hand-maintained home file CI agents are ephemeral and shouldn't carry a checked-in secret. Two clean options: 1. **Env-var injection** — set `ORG_GRADLE_PROJECT_myRepoPassword` from the CI secret store; Gradle exposes it as the project property `myRepoPassword`, identical to a file entry. (The env-var mechanic itself is a sibling topic; the point here is *placement* — it keeps the secret out of the repo.) 2. **Write user-home at runtime** — have the job materialize `$GRADLE_USER_HOME/gradle.properties` from the secret manager before the build, then discard it. ## Summary Shared, non-secret config → committed project-root file. Secrets → user-home locally, injected env var on CI. The boundary between *in-repo* and *out-of-repo* is the whole game.
- A secret was committed to the project-root gradle.properties last week. Is rotating it enough?Removing the line isn't enough — it stays in Git history, so the credential must be rotated/revoked. Then move the new value to user-home or CI injection.
- How do you keep the build script unchanged whether the secret comes from a file or CI env var?Read it by property name (`providers.gradleProperty("...")` / project property). Both the user-home file and `ORG_GRADLE_PROJECT_*` populate the same property namespace, so the script doesn't branch.
saying these in an interview costs you the question
- Suggesting secrets in the committed project-root file 'because it's convenient'.
- Believing deleting a committed secret from the file removes it from Git history.
- Hardcoding credentials directly in build.gradle(.kts).