Cache downloaded dependencies and build outputs, such as Gradle and package manager downloads. Never cache keystores, certificates, API keys, service account files or anything decoded from a secret. Anyone who can read a cache can read what is in it, and pull requests can often read it.
The platform warning
The caching documentation of a hosted CI service is blunt: it recommends that you do not store sensitive information, such as access tokens or login credentials, in files in the cache path. It explains why: anyone with read access can create a pull request on the repository and access the contents of a cache, and forks can create pull requests on the base branch and access caches on that branch.
So a cache is a shared surface, closer to a public folder than a private vault.
A safe split for mobile projects
| Item | Cache it? | Reason |
|---|---|---|
| Gradle dependency downloads | Yes | Public artifacts, safe to rebuild |
| Gradle build cache outputs | Yes, with care about who writes | Outputs can be trusted only if writers are trusted |
| CocoaPods downloads | Yes | Reproducible from the lockfile |
| Swift package checkouts | Yes | Reproducible from the resolved file |
| Ruby gems for the build tool | Yes | Pinned by a lockfile |
Android keystore, .p12, .p8 | Never | Signing identity |
| Play service account JSON | Never | Publishing credential |
| Decoded provisioning profiles | Never | Created from secrets at runtime |
google-services style config with private values | Review first | Depends on what it holds |
Where cache directories overlap with secrets
The danger is accidental. A cache path set to a broad folder, such as the whole home directory or the workspace root, can sweep in a decoded keystore written there earlier in the job. Prefer narrow, named cache paths: the Gradle user home download area, the Pods folder, the package manager's cache folder.
Also decode secrets into the runner's temporary directory, not the workspace, and delete them at the end of the job.
Who may write the cache
Gradle's own documentation notes that the local build cache has push enabled by default, while a remote build cache has push disabled by default. For remote caches, the safe pattern is to let a trusted job on the default branch write and let other builds only read. The hosted CI documentation says the same for its cache: low-trust triggers get read-only access by default, and the guidance is to have a trusted, push-triggered workflow maintain the cache.
If an attacker can write a poisoned cache that a release job later restores, your release job executes their content. See the post on cache keys for mobile builds for how keys affect this.
After a compromise
Purge caches and rebuild from lockfiles. A cache entry that existed before the incident may not be trustworthy afterwards.
Try it yourself
Open the demo to see how a dependency inventory is produced from a project. Look at how many components appear that no one chose directly, because those are what a cache silently carries.
Click through it at your own pace, or open it full screen.
Where Safeguard fits
Safeguard provides SBOM and dependency scanning for mobile projects and can gate builds with policies. Start with a free scan.
Quick check
Is a private repository cache private to the team?
Not in the sense people assume. Anyone who can open a pull request may be able to read it, so keep secrets out.
Can I cache the Pods folder?
Yes for speed, ideally keyed to the lockfile, and never with anything secret in the same path.
Sources
Last reviewed: 19 October 2026.