# Best Practices for Security

version 9.8.0-20260724012737+0000

## Validate the Gradle Distribution SHA-256 Checksum

Set `distributionSha256Sum` in `gradle-wrapper.properties` to verify the integrity of the downloaded Gradle distribution.

### Explanation

Always set the `distributionSha256Sum` property in your `gradle-wrapper.properties` file to verify the integrity of the downloaded Gradle distribution. This ensures the `gradle-X.X-bin.zip` file matches the official SHA-256 checksum published by Gradle, protecting your build from corruption or tampering.

    distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
    distributionSha256Sum=2b3f4...sha256-here...f4511

This validation step enhances security by preventing the execution of compromised or incomplete Gradle distributions.

The official SHA-256 checksums can be found on the Gradle releases page.

### References

- Gradle Releases with Checksums

- Gradle Wrapper Reference

### Tags

`#properties`, `#wrapper`

## Validate the Gradle Wrapper on every Upgrade

The Gradle Wrapper runs before your build logic and can deeply affect your build. You should treat any change to the Wrapper as security-sensitive.

Validate the Wrapper JAR and distribution settings every time you upgrade Gradle.

### Explanation

When you update Gradle, two Wrapper files typically change:

- `gradle/wrapper/gradle-wrapper.jar`

- `gradle/wrapper/gradle-wrapper.properties` (`distributionUrl` and optionally `distributionSha256Sum`)

You should verify that:

- The Wrapper JAR is the official binary published by Gradle (not tampered with).

- The Wrapper JAR checksum matches one of the values published at gradle.org/release-checksums.

- The distribution URL points to the expected Gradle release.

- The distribution checksum (if configured via `distributionSha256Sum`) matches the release you intend to use (also listed on gradle.org/release-checksums).

Running an unverified Wrapper risks executing untrusted code before any of your build’s safeguards run.

#### Do this

If you use the `setup-gradle` action (version v4 or newer) for GitHub Actions, Wrapper validation will be performed automatically. The action validates the checksum of every `gradle-wrapper.jar` in your repository and fails the build if it finds any unknown Wrapper JAR.

If you use a different GitHub Actions setup, you can use the dedicated Gradle Wrapper validation action instead.

### References

- Gradle Wrapper checksum verification

- Gradle release checksums

### Tags

`#wrapper`

## Do not Run `./gradlew` on Untrusted Projects

When working with an untrusted project, you should exercise caution before running `./gradlew`.

### Explanation

The `gradlew` (Gradle Wrapper) script is an executable that downloads and runs a specific version of Gradle. It should be generated by running the Wrapper task with a trusted Gradle installation, but there is no guarantee that a project’s authors have done this. The script or `gradle-wrapper.jar` could have been modified. The only way to verify this is through checksum verification.

Running an unverified wrapper is similar to running any other untrusted script from the internet. Build scripts (`build.gradle`, `settings.gradle`, etc.) can also execute arbitrary code during the configuration phase.

An unreviewed wrapper or build script could potentially access or modify files on your system, install malicious software, or compromise your environment (e.g., stealing environment variables like `GITHUB_TOKEN` or `AWS_SECRET_KEY`).

| Opening an untrusted project in an IDE such as IntelliJ IDEA or Android Studio will typically trigger a Gradle sync automatically, which executes build logic. Review the project’s wrapper and build scripts before opening it in your IDE.

#### Do this

##### 1. Review the Build Scripts

- Inspect Build Files: Look at `build.gradle(.kts)`, `settings.gradle(.kts)`, and `gradle.properties` for suspicious dependencies, plugins, custom `exec` tasks, or obfuscated code.

##### 2. Verify the Gradle Wrapper Files

- Checksum Verification: Use checksum validation to ensure `gradle-wrapper.jar` hasn’t been tampered with.

- Check the URL: Open `gradle/wrapper/gradle-wrapper.properties` and ensure the `distributionUrl` points strictly to the official site: `https://services.gradle.org/`.

- Regenerate the Wrapper: If you have a trusted Gradle installation, delete the project’s `gradlew`, `gradlew.bat`, and `gradle/wrapper/gradle-wrapper.jar`, then run `gradle wrapper` (not `./gradlew`). Compare the regenerated files against the originals, any differences indicate the project’s wrapper may have been modified.

##### 3. Work in an Isolated Environment

You can optionally run the project in a Docker container or a Virtual Machine (VM). This ensures that even if a malicious script runs, it cannot access your files.

### References

- Gradle Wrapper Reference

### Tags

`#wrapper`

## Build Output Should Be Byte-for-Byte Reproducible

A build is reproducible when the same source code produces byte-for-byte identical output files, on any machine, at any time.

### Explanation

Reproducibility matters far beyond the publishing flow. If binaries remain identical, then downstream consumers can verify that they match the source. Build systems typically verify artifact checksums; non-determinism in artifact bytes defeats caches and deduplication logic without providing any benefit.

With this strong form of reproducibility, the question "Did my change actually change anything?" is easy to answer: compare the output hashes. Gradle’s own up-to-date checks and remote build cache also rely on stable inputs producing stable outputs.

Some common sources of non-determinism in builds are file timestamps inside archives, the order of entries inside archives, and the SDK used to compile classes. For JVM builds, keeping wall-clock timestamps on `.class` files inside a `.jar`, or letting archive entries follow the filesystem’s directory-iteration order, means that two clean builds of the same source produce slightly different artifacts with different checksums even though the output is functionally identical.

| Since Gradle 9.0.0, archive artifacts are set to be reproducible by default.

This is list of examples is not exhaustive. There are other ways to introduce non-reproducibility in your project. One good test to confirm reproducibility is to try re-building an older version on a different machine, and comparing the output to the published binary.

### Example

#### Don’t Do This

build.gradle.kts

    plugins {
        `java-library` (1)
    }

    tasks.named<Jar>("jar") {
        isPreserveFileTimestamps = true   (2)
        isReproducibleFileOrder = false   (3)
    }

build.gradle

    plugins {
        id 'java-library' (1)
    }

    tasks.named('jar', Jar) {
        preserveFileTimestamps = true   (2)
        reproducibleFileOrder = false   (3)
    }

1 | No `java { toolchain { …​ } }` block is declared. Gradle has no default toolchain, so the JDK used for compilation falls back to whichever `JAVA_HOME` is active. Different machines (and even different JDK patch releases on the same machine) emit different bytecode.
2 | Sets `preserveFileTimestamps` to `true`, overriding the Gradle 9.0+ default of `false`. Every entry inside the jar is stamped with the build’s wall-clock time, so the same source produces a different jar on every build.
3 | Sets `reproducibleFileOrder` to `false`, overriding the Gradle 9.0+ default of `true`. Archive entries are ordered by the filesystem’s directory-iteration order, which varies by OS and filesystem.

#### Do This Instead

build.gradle.kts

    plugins {
        `java-library`
    }

    java {
        toolchain {
            // Choose your project's required version
            languageVersion = JavaLanguageVersion.of(21) (1)
        }
    }
    (2)

build.gradle

    plugins {
        id 'java-library'
    }

    java {
        toolchain {
            // Choose your project's required version
            languageVersion = JavaLanguageVersion.of(21) (1)
        }
    }
    (2)

1 | Pin the JDK used to compile so the build is decoupled from each developer’s local `JAVA_HOME` and from whichever JDK happens to be on the CI image; Gradle auto-provisions a matching JDK if one isn’t found.
2 | Leave archive defaults alone — Gradle 9.0+ sets `preserveFileTimestamps = false` and `reproducibleFileOrder = true` on every AbstractArchiveTask (`Jar`, `War`, `Ear`, `Zip`, `Tar`). The performance cost of these defaults is negligible: `reproducibleFileOrder` adds a sort over directory listings that’s dominated by I/O and compression work, and dropping timestamps actually makes archives marginally smaller and avoids unnecessary downstream cache invalidation. If you maintain a build that still supports an older Gradle version, set both flags explicitly with `tasks.withType<AbstractArchiveTask>().configureEach { …​ }`.

### References

- Toolchains for JVM projects

- AbstractArchiveTask

- reproducible-builds.org

### Tags

`#publishing`, `#tasks`