Safeguard
Developer Tools•

Do protected and unprotected branches share a GitLab CI cache?

By default GitLab keeps separate caches for protected and unprotected refs. Why, what the key suffix means, and when turning separation off is unsafe.

Safeguard Security Team
3 min read

By default, no. GitLab appends a suffix to cache keys so pipelines on protected refs use one cache and pipelines on unprotected refs use another. This stops a less trusted branch from writing files a release pipeline later restores. You can turn separation off, but GitLab says to do so only where all Developers are highly trusted.

Why a shared cache is a risk

A CI cache holds files that a later job restores and often runs: downloaded packages, build tools, compiled output. If any developer branch can write to the cache that the release job reads, a bad file placed in an ordinary branch can reach the release build. That is the cache poisoning pattern described in our cache poisoning explainer.

How GitLab separates them

GitLab's caching page says protected and unprotected branches do not share a cache by default. The cache key gets -protected when the pipeline runs on a protected ref for a user with Maintainer or Owner level access, and -non_protected otherwise. For tag pipelines, the protection status of the tag decides the suffix, not the branch it came from.

PipelineKey suffixReads protected cache
Protected branch, maintainer-level user-protectedYes
Feature branch-non_protectedNo
Protected tag-protectedYes
Unprotected tag-non_protectedNo

Fallback keys follow the same suffix rule, so a fallback cannot cross the line either.

Cache versus artifacts

The two are easy to confuse. A cache stores dependencies on the runner, or in object storage with distributed caching, and is not shared between projects. Artifacts pass build results between stages and live in GitLab. Neither can reference files outside the project directory. GitLab also states that caching is an optimisation and is not guaranteed, so a pipeline must never depend on a cache hit for correctness.

Steps to keep it safe

  1. Leave "Use separate caches for protected branches" on. Find it in the project's CI/CD settings.
  2. Use cache:key:files so the key follows your lockfile, and a changed lockfile gets a fresh cache.
  3. Set policy: pull on jobs that should only read, such as test jobs on feature branches, so they never write.
  4. Pin dependency versions. GitLab notes that clearing a cache forces fresh downloads, which can pull unreviewed upstream releases.
  5. Run release builds from a clean cache on a schedule and compare the output.
test:
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull

Try it yourself

Click through it at your own pace, or open it full screen. Look at how a dependency inventory lists exact versions, which is what you want to compare when a cache is cleared.

Safeguard's dependency and SBOM scanning builds that inventory, so a changed package between two builds shows up. Try a free scan at https://gold.safeguard.sh/scan.

Quick check

Does a separate cache cost speed?

Somewhat, since the first pipeline on each side builds its own cache. The cost is small next to the risk of a poisoned release build.

Can I trust a cache hit as proof the files are good?

No. A hit shows the key matched, not that the contents are clean.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.