Our offices

  • Exceev Consulting
    61 Rue de Lyon
    75012, Paris, France
  • Exceev Technology
    332 Bd Brahim Roudani
    20330, Casablanca, Morocco

Follow us

Preferences

Brand kit

10 min read - GitHub Actions cache access: draw the trust boundary first

Infrastructure & DevOps

Published September 11, 2026 · Author Exceev Consulting

GitHub added a small piece of YAML with a large security consequence on 10 September 2026. The new cache-mode setting for GitHub Actions lets a workflow or individual job receive permission to read caches, write them, do both, or do neither. GitHub says the control is generally available on all plans.

This is useful because a CI cache crosses time. A test job can save files today that a release job restores tomorrow. If the first job processes pull-request code while the second can publish a package or deploy an image, cache access may connect two workflows that looked separate in the repository review.

The setting does not choose a safe policy for you. Teams still need to decide which runs can influence future runs, which cached files may execute, and where a cache miss is acceptable. Our recommendation is to map those relationships before adding cache-mode. The examples below are a proposed review method, not a report of tests on GitHub's service or an Exceev client environment.

Treat cache access as a permission

GitHub's cache-mode syntax documentation defines four values. read permits restores but blocks saves. write permits both. write-only allows saves without restores, while none blocks both operations. A workflow-level value applies to all jobs unless a job overrides it.

The service enforces the setting through scoped cache tokens. When the mode blocks an operation, the cache step logs a message and the workflow continues. A blocked restore becomes a cache miss; a blocked save is skipped. That behaviour matters operationally because a green job does not prove that it populated the cache.

The names are simple, but the decision is not a four-way preference. It depends on the job's input, the cache content and the authority of later consumers.

ModeAppropriate question before use
readCan this job consume entries produced by the trusted cache writer?
writeMay this job affect later runs, and is all executable input trusted?
write-onlyShould this producer save an entry without consuming an older one?
noneDoes this job need a cache enough to justify shared mutable state?

Do not start by converting every existing cache step to write. GitHub warns that explicitly granting write or write-only on a low-trust trigger can override the secure read-only default and bring back cache-poisoning risk.

Map producers, consumers and authority

Open each workflow under .github/workflows and record five facts for every job that uses actions/cache or dependency caching built into a setup action:

  • the event that starts the workflow;
  • whether an outside contributor can influence checked-out code, expressions, scripts or package installation;
  • the directories and file types restored or saved;
  • the cache key and any restore prefixes;
  • what the job can do after a restore, including access to secrets, OIDC, deployments, signing or package publication.

Then connect each writer to every plausible reader. A key such as an operating system plus a lockfile hash may be shared by a pull-request job and a default-branch job. Different workflow filenames do not create isolation by themselves.

Pay close attention to generated executables, package-manager stores, compiler outputs and tool downloads. A documentation preview that caches only inert source material presents a different problem from a release job that executes a restored binary. Record the actual path rather than the friendly cache name shown in a step.

The resulting map should answer a blunt question: can code controlled by a less-trusted actor place bytes where a more-trusted job will execute or package them? If the answer is unknown, keep write access out of the low-trust path until the team can trace the relationship.

Start with GitHub's trigger defaults

GitHub introduced read-only cache tokens for certain untrusted triggers in June 2026. The change covered cases where an actor without repository write permission can trigger a workflow in the shared default-branch cache scope. GitHub named pull_request_target, issue_comment and some fork-triggered workflow_run chains among the relevant cases.

The September control makes that boundary explicit and adjustable, but adjustment should have an evidence-backed reason. A warning annotation is not an approval. If a low-trust run stopped saving after the June change, create a trusted producer rather than restoring broad write access merely to recover build speed.

For example, a default-branch push workflow can build and save a dependency cache. Pull-request validation can restore that cache with cache-mode: read, then perform a normal cold install when no matching entry exists. This arrangement keeps review feedback fast when a trusted entry is available without letting the pull-request job replace it.

cache-mode: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('**/package-lock.json') }}

This snippet illustrates the new syntax only. It does not cover action pinning, package-manager behaviour, workflow permissions or the repository's full threat model.

Use job-level modes where authority changes

A single workflow may contain a test job and a publishing job. Giving the whole workflow write because one job needs to populate a cache gives every job the same cache capability unless you add overrides.

Set a restrictive workflow default, then justify narrower job exceptions. A build job running after a trusted push may receive write. A release job can use read if it needs a previously produced entry, or none if the team requires a clean build for that stage. The right answer follows the release design, not a universal template.

Reusable workflows need the same review. GitHub says a caller can limit cache access for the called workflow and that the called workflow cannot receive more access than its caller. Record the effective mode at the calling job, especially when one shared workflow serves pull requests, scheduled builds and releases.

Avoid treating write-only as a generic safer version of write. It prevents the producer from restoring older entries, which can be useful for a dedicated cache-seeding job. It still lets that job affect future consumers. Only a trusted producer should receive that influence.

A strong key is necessary but insufficient

Cache keys should cover the inputs that determine the cached output. A lockfile hash is better than a branch name for a dependency cache, and compiler caches may need the compiler version, flags and other inputs. Yet a precise key does not answer who may write the matching entry.

The independent SLSA threat model describes cache poisoning as a malicious artifact entering a cache and later being selected by a benign build. Its mitigations include isolation between builds, keys derived from the transitive inputs, and restricting writes to a trusted control plane or requiring suitable provenance for cache entries.

That guidance supplies the architectural test. cache-mode can restrict a GitHub Actions job's cache token, but it does not prove that the cached directory contains only safe files, that the key covers every input, or that a self-hosted runner has no other shared state. Review those mechanisms separately.

Test the policy without production authority

Create a disposable branch or test repository with the same workflow triggers and cache paths but no deployment credentials. Use a harmless marker file to distinguish an entry created by the trusted producer from one attempted by the low-trust job.

Run a cold case first. Confirm that the job succeeds when the restore misses and that dependency installation still verifies its inputs. Next, let the trusted producer save the marker and check that a read-only consumer can restore it. Finally, attempt a save from the read-only job and confirm that the logs show the save was skipped while the job remains green.

Record the workflow commit, event, actor class, effective mode, key, cache result and log link. Test the reusable-workflow route if production uses one. Do not add real secrets merely to make the test look realistic.

Measure the performance cost as well. Some release paths may tolerate none; a large compiler cache may not. Use observed warm and cold durations to decide where a trusted producer is worth operating. Security policy becomes easier to keep when the team can see the cost and has designed for a miss.

Review release jobs as separate consumers

Release automation deserves its own row even when it contains no cache step. Setup actions can hide dependency caching behind an input, and a reusable workflow may restore state elsewhere. Search both the calling and called files.

GitHub's CodeQL guidance for Actions workflows lists cache-poisoning queries alongside checks for untrusted code execution, excessive secret exposure and missing permissions. Run the relevant analysis, but read the workflow map too. A static finding helps locate a path; an absent finding does not document the intended trust boundary.

If the team suspects a cache was written through an untrusted path, stop trusted consumers from restoring it while the incident owner scopes the event. Rebuild needed state through an approved producer and assess secrets or publishing identities according to their actual exposure. This is a defensive response principle, not a claim that changing cache-mode alone resolves an incident.

Write down the accepted boundary

The final decision can fit on one page. Name the workflow and job, trigger, cache path, producer, consumers, selected mode, reason, cold-path behaviour and review owner. Link the source revision and the test run. Give each exception an expiry or review condition.

For a team operating across France and Morocco, use one repository-level record rather than inferring trust from office location. The trigger, code source and job authority define the technical boundary. Data location, contractual permission and regulatory duties require their own evidence and review. Broader delivery gaps belong in the services review, not this cache control.

Revisit the map when a workflow gains OIDC, a deployment environment, a package token or a new trigger. Also review changes to cache paths and restore prefixes. A line that improves build time can quietly change which job supplies executable state to a later release.

Sources and limitations

We opened and reviewed every source below on 11 September 2026.

GitHub is the primary source for its service and syntax. SLSA does not certify GitHub's implementation or this proposed review method. We have not tested the new control on a production repository, measured its effect on build time or assessed a customer's release obligations. Defaults and product behaviour can change, so verify the current documentation and observed effective mode before changing a release workflow.

We should talk.

Exceev works with startups and SMEs on strategy, AI integration, custom engineering, and practical technology enablement.

More articles

Adobe Commerce zero-day: prove the fix, then rotate credentials

Adobe says CVE-2026-75650 is exploited in the wild. Record the emergency hotfix, credential rotation and exposure review in one response.

Read more

CRA reporting starts: rehearse the 24-hour workflow

CRA reporting starts on 11 September 2026. Build and rehearse the evidence, decision and filing path before the first 24-hour clock begins.

Read more

Tell us about your project

Our offices

  • Exceev Consulting
    61 Rue de Lyon
    75012, Paris, France
  • Exceev Technology
    332 Bd Brahim Roudani
    20330, Casablanca, Morocco