Skip to content

Commit 25bf0ff

Browse files
albertodonatoclaude
andcommitted
fix: keep run-script caches from fork PRs of public repos
A trusted run can fill the pre-commit and uv caches with private packages or clones of private hook repositories, which a fork's script could read. Skip restoring them for pull requests from forks of a public repository. Also stop calling the github-token token read-only: the job takes the caller's permissions, so the caller's grant is the only cap on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 46452da commit 25bf0ff

2 files changed

Lines changed: 18 additions & 10 deletions

File tree

‎.github/workflows/run-script.yml‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ on:
2020
default: false
2121
github-token:
2222
type: boolean
23-
description: "Expose the job's read-only GitHub token to the script as GH_TOKEN"
23+
description: "Expose the job's GitHub token to the script as GH_TOKEN. It carries whatever permissions the caller grants, so grant only what the script needs"
2424
default: false
2525
mise-env:
2626
type: string
@@ -44,6 +44,8 @@ on:
4444
# contents: read for most callers, plus id-token: write for those passing
4545
# aws-role. A called workflow cannot be granted more than its caller has, and
4646
# asking for id-token: write here would fail every caller that doesn't grant it.
47+
# The caller's grant is therefore the only cap on the token github-token
48+
# exposes, so callers must grant no more than contents: read (and id-token).
4749
jobs:
4850
run:
4951
runs-on: ubuntu-latest
@@ -91,14 +93,18 @@ jobs:
9193
minimum_release_age: 7d
9294
cache: true
9395

94-
# Always read the caches, it's safe to do so even on untrusted PRs.
96+
# Caches are restored on every run except pull requests from forks of a
97+
# public repository: a trusted run may have filled them with private
98+
# artifacts (packages from a private registry, clones of private hook
99+
# repositories), which an outsider's script could read and exfiltrate.
100+
# Forks of a private repository belong to people who can already read it.
95101
#
96102
# Hook environments are built with the toolchains mise installs (Python,
97103
# Node, ...), so the key covers the mise config and lockfiles and the
98104
# selected environment as well as the hook pins: a toolchain bump must
99105
# not restore environments built against the old one.
100106
- name: Restore pre-commit cache
101-
if: inputs.pre-commit-cache
107+
if: inputs.pre-commit-cache && !(github.event.pull_request.head.repo.fork && !github.event.repository.private)
102108
id: pre-commit-cache
103109
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
104110
with:
@@ -108,7 +114,7 @@ jobs:
108114
# uv verifies what it installs against uv.lock's hashes, so a stale entry
109115
# only costs a download; restore the newest cache when the lock moved.
110116
- name: Restore uv cache
111-
if: inputs.uv-cache
117+
if: inputs.uv-cache && !(github.event.pull_request.head.repo.fork && !github.event.repository.private)
112118
id: uv-cache
113119
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
114120
with:

‎README.md‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ are loaded on top of the default config.
5858
```
5959
6060
Set `pre-commit-cache` to keep pre-commit's (or prek's) hook environments between
61-
runs, and `uv-cache` to keep uv's cache. Both are restored on every run but saved only
62-
off pull requests, so an untrusted pull request can't write a cache a later trusted run
63-
restores.
64-
65-
The job runs with exactly the permissions the caller grants, so grant at least
66-
`contents: read`.
61+
runs, and `uv-cache` to keep uv's cache. Both are saved only off pull requests, so an
62+
untrusted pull request can't write a cache a later trusted run restores, and restored on
63+
every run except pull requests from forks of a public repository, since a trusted run
64+
may have filled them with private packages or hook repositories.
65+
66+
The job runs with exactly the permissions the caller grants, so grant
67+
`contents: read` and nothing more (plus `id-token: write` for `aws-role`): with
68+
`github-token` set, the script gets a token carrying all of them.
6769
6870
Set `aws-role` to assume that AWS role through GitHub OIDC before the script runs,
6971
with `aws-region` if it isn't `us-east-1`. The caller must then grant `id-token: write`

0 commit comments

Comments
 (0)