Caching in this library is currently whatever each pinned setup action does by
default, plus one deliberate exception. There is no single machine-readable
contract saying what may be cached, by whom, restored from where, and with what
trust level — so the properties below are true by accident rather than by
construction.
What is already right
release.yml sets enable-cache: false on setup-uv, with the reasoning in a
comment: a cache entry written from a lower-trust ref would otherwise become an
input to a release build (zizmor cache-poisoning, high).
- Action and tool pins are immutable, so the code that populates a cache is fixed.
- Some workflows can disable caching on warm self-hosted runners.
What is missing
No declared contract covering:
- repository/tenant namespace, and what isolates one caller's entries from another's
- which event and ref trust levels may write a cache versus only restore one
- the dependency inputs that must appear in a key for a hit to be meaningful
- exact-key versus prefix/
restore-keys behaviour, and default-branch fallback
- persistent self-hosted state: what survives a job, and what must be cleaned
- eviction/retention, and what a cold versus warm run is expected to cost
- hosted/fleet equivalence — whether the same key means the same thing on both
Why it matters here specifically
GitHub's cache scoping is ref-based with default-branch fallback, and a pull
request's cache is scoped to the merge ref. A cache is not signed provenance. On a
persistent self-hosted runner the workspace itself is additional retained state
that GitHub makes no cleanliness guarantee about. Any of those can turn a cache
into an input nobody reviewed.
Suggested shape
- Inventory every cache producer and consumer across the reusables —
setup-uv,
setup-go, setup-gradle, Flutter/Qt, ccache, package managers, container
layers, and anything a fleet keeps locally.
- Declare the contract in
catalog/, in the same style as
workflow-routing.yml and python-execution.yml, and compile it fail-closed.
- Add adversarial checks: PR merge-ref scope, default-branch fallback, a
hostile branch writing a key a trusted job restores, stale/corrupt entries,
and fleet workspace residue.
- Record cold and warm timings so the optimisation is measured, without
narrowing any verification to buy the speed.
This is not a proposal to disable caching. It is a proposal to make it a typed
optimisation with proven trust boundaries.
Not currently a demonstrated exploit — no poisoned-cache path has been shown
in this repository. This is a completeness gap, and it should be scoped and
prioritised as one.
Caching in this library is currently whatever each pinned setup action does by
default, plus one deliberate exception. There is no single machine-readable
contract saying what may be cached, by whom, restored from where, and with what
trust level — so the properties below are true by accident rather than by
construction.
What is already right
release.ymlsetsenable-cache: falseonsetup-uv, with the reasoning in acomment: a cache entry written from a lower-trust ref would otherwise become an
input to a release build (zizmor
cache-poisoning, high).What is missing
No declared contract covering:
restore-keysbehaviour, and default-branch fallbackWhy it matters here specifically
GitHub's cache scoping is ref-based with default-branch fallback, and a pull
request's cache is scoped to the merge ref. A cache is not signed provenance. On a
persistent self-hosted runner the workspace itself is additional retained state
that GitHub makes no cleanliness guarantee about. Any of those can turn a cache
into an input nobody reviewed.
Suggested shape
setup-uv,setup-go,setup-gradle, Flutter/Qt, ccache, package managers, containerlayers, and anything a fleet keeps locally.
catalog/, in the same style asworkflow-routing.ymlandpython-execution.yml, and compile it fail-closed.hostile branch writing a key a trusted job restores, stale/corrupt entries,
and fleet workspace residue.
narrowing any verification to buy the speed.
This is not a proposal to disable caching. It is a proposal to make it a typed
optimisation with proven trust boundaries.
Not currently a demonstrated exploit — no poisoned-cache path has been shown
in this repository. This is a completeness gap, and it should be scoped and
prioritised as one.