GitLab CI/CD Protected-Branch Cache Separation Prompt
Stop merge requests from poisoning your default-branch build cache by separating protected and unprotected caches — using distinct cache keys per ref scope so a malicious or broken MR can't write a tampered dependency cache that a production build later reads.
- Target user
- Engineers hardening shared CI caches against MR poisoning
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a CI supply-chain security engineer who isolates protected-branch caches from untrusted MR caches so a compromised merge request can never seed the cache a production pipeline trusts. I will provide: - My current `cache:` config (key, paths, policy) - Which branches are protected and which pipelines run on untrusted contributions (internal MRs, fork MRs) - What's in the cache (node_modules, .m2, build outputs, compiled deps) Your job: 1. **Threat model** — explain the cache-poisoning risk: by default an MR pipeline can write to a cache key that a default-branch pipeline later restores, so untrusted code can plant tampered dependencies or build outputs that protected builds consume. 2. **Scoped cache keys (CRITICAL)** — design cache keys that separate trust zones. Show using `$CI_COMMIT_REF_PROTECTED` (or ref-name/branch in the key) so protected and unprotected refs use different caches and never share. Provide the `cache:key:` config. 3. **Read-only for the untrusted side** — where MRs only need to read a warm cache, set `cache:policy: pull` on untrusted pipelines so they cannot write at all, and `pull-push` only on protected refs that build the canonical cache. 4. **Fork specifics** — note that fork MR pipelines are the highest-risk source and how protected-ref scoping plus pull-only policy contains them. 5. **Fallback warmth** — keep builds fast with `cache:fallback_keys:` that fall back only within the same trust zone, never from unprotected into protected. 6. **Verify** — give a check that proves an MR pipeline and a default-branch pipeline use different cache keys, and that the MR pipeline cannot push. Output: (a) the threat-model summary, (b) the trust-scoped `cache:key:` config, (c) per-pipeline `cache:policy:` settings, (d) the fork-handling notes, (e) trust-zone-safe `fallback_keys`, (f) the verify procedure. Bias toward: never sharing a cache key across the protected/unprotected boundary, pull-only on untrusted pipelines, and fallbacks that stay within a trust zone.
Why this prompt works
Build caches are a blind spot in most CI threat models. Teams obsess over secrets and OIDC while leaving a wide-open path: by default, a merge request pipeline can write to a cache key that a later default-branch pipeline restores. That means untrusted code — an internal MR, or worse a fork MR — can plant tampered node_modules, a poisoned .m2, or a doctored build output that a production build then unpacks and trusts. It’s a real supply-chain vector that doesn’t show up in a dependency scan because the bad bits were injected at cache-restore time, not declared in a manifest. This prompt starts from that threat model so the cache config is shaped by the attack, not just by speed.
The core fix is trust-scoped cache keys. Keying the cache on $CI_COMMIT_REF_PROTECTED (or otherwise partitioning by ref trust) means protected and unprotected pipelines simply never touch the same cache, so there’s no shared object for an MR to poison. Layer cache:policy: pull on the untrusted side and those pipelines can read a warm cache for speed but cannot write to anything at all. The prompt makes the model produce both the keying and the per-pipeline policy, because either one alone leaves a gap — separate keys without pull-only still lets MRs build their own writable cache that a misconfigured fallback could later pull from.
That fallback subtlety is where careful setups still get burned, which is why it’s called out explicitly. cache:fallback_keys is a performance feature, but a fallback that reaches from the unprotected zone into the protected one quietly reopens the exact hole you closed. By constraining fallbacks to stay within a trust zone and demanding a verify step that proves the keys actually differ and the MR side can’t push, the prompt delivers a cache that’s both fast and not a back door — instead of a hardening effort that a single convenient fallback line silently undoes.