Does a build cache entry contain my source code or secrets?
In our runs, FosterStack Cache stored what tasks produce, not your source files. That includes class files and anything a test prints, so a secret in an output is in the cache.
The short answer
- Source files were not stored. Class files were. The compile entry held class files and nothing else from our code. A comment was gone, but a string constant was inside the class file.
- Test output was stored. The line our test printed was in four files of the stored test result: the raw output, the HTML report and the XML report with its raw copy.
- A task's output is stored as the task wrote it. A value a task used only as an input went into the key, which is a hash of the inputs, and not into the entry. A value the task wrote into its output file was in the entry, in plain text.
- Resource files and jars were not stored. Gradle logged
Not worth cachingforprocessResourcesandjar, so our resource file with a fake secret was in no entry. - Anyone with a login can fetch an entry if they know its key. The read-only login fetched one. No login and a wrong password got a
401. We found no way to list keys over HTTP. - The build machine's host name was in the test report. Paths and the user name were not. The host name was in the XML report. Each entry also holds a small metadata file with a build id, a time, the Gradle version and the task name.
What to do
Our advice, not a test result:
- Treat read access to the cache like read access to your build outputs. Class files can be decompiled, and they keep string constants.
- Do not print secrets in tests, and do not write them into task outputs. Both end up in the cache. Test reports also name the machine that ran them.
- Keep secrets out of Java constants. A constant lives on in the class file, in the cache and in your jar.
- Declare an environment variable as a task input if the task reads it. We changed one that a task read without declaring it, and the old result came back from the cache.
- Give developers the read-only login and give CI the read-write login. See let CI write and developers read. One login reaches every project on a server, so use one server per group that must not share. See several projects on one server.
- Use HTTPS when the server is not on your own machine. Our runs used plain HTTP to a server on the same Mac. See use your build cache over HTTPS with a private CA.
What each task stores is on know what your Gradle build cache stores. Who can change entries is on can someone poison our shared build cache.
How we checked
FosterStack Cache 0.2.1 on a Mac (arm64) with a login turned on. Gradle 9.8.0 and Java 21 built a small project twice, each time from a fresh copy, with the local build cache off. Every secret was made up. We stopped the server, opened every file in its data folder with gunzip and tar, and searched each entry, and each file inside it, for each fake secret. Each row is what we found in these two builds.
| What we put in | Where it showed up in the stored entries |
|---|---|
| A string constant in a Java source file | Inside the class file of the compile entry. Also inside the class file of the test compile entry, which uses it |
| A fake key in a Java comment | In no entry |
The .java files themselves | Not in any entry |
| A resource file with a fake secret, packaged into the jar | In no entry. processResources and jar were not cached |
| Text printed by a test | In the test entry, in four files: output-events.bin (twice, once in the results folder and once in the XML folder), the HTML report and the XML report. It was also inside the test's class file in the test compile entry, because the text was a string in the test source |
A fake secret as a string in build.gradle.kts, passed to a task as an input | In no entry. The task wrote only its length |
| An environment variable, declared as a task input | In no entry. The task wrote only its length. We changed the value and built again: the task ran and stored a second entry under a different key |
| An environment variable the task reads but does not declare | In no entry. We changed it from 25 to 33 characters and built again: the task came FROM-CACHE with the old result, length=25 |
| An environment variable, declared, and written by the task into its output file | In that task's entry, as value= followed by the text |
A gradle.properties value used as a declared input | In no entry. The task wrote only its length |
The cache password, as a string in settings.gradle.kts | In no entry, not in the server's index file and not in the server log |
| The project folder, the home folder and the user name | In no entry |
| The host name of the build machine | In the test entry, in the hostname attribute of the XML report. In no other entry |
| The entry files on disk | Gzip-compressed tar archives, not encrypted. A 32-character hex key names each one, in a folder named for its first two characters. On this Mac the files were readable only by their owner (0600), which the server's code asks for |
| The metadata file in each entry | Build id, creation time, execution time, Gradle version, task name and type. Nothing else |
Read-only login, GET of a stored key | 200, 1,982 bytes. HEAD gave 200, PUT gave 403, an unknown key gave 404 |
No login, and a wrong password, GET of a stored key | 401 both times |
Looking for a list of keys: /, /statusz, /metrics, /healthz, /list, /?list, /keys, /index, /_list, /.keys, /v1/, /cache/ | The front page (also for /?list) showed the version and uptime. /statusz showed counts and /metrics showed counters. /healthz said ok. None of them showed a key. The other paths gave 404 or 400 |
Keys appear in Gradle's own -i output, and the server's index file holds them, so a key is not a secret from someone who can read your build logs or the server's files.
Not tested: Maven, Kotlin and Android builds, cacheable packaging tasks such as a shaded jar, which may store your resources, other Gradle plugins, other releases, Linux and Docker file permissions, and entries from builds with a different Java version.