Skip to content

Declare a machine-readable cache and trust contract #148

Description

@rldyourmnd

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

  1. 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.
  2. Declare the contract in catalog/, in the same style as
    workflow-routing.yml and python-execution.yml, and compile it fail-closed.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions