@@ -28,13 +28,21 @@ name: Token Validation Benchmark
2828# If a run is interrupted, clear leftover containers before retrying:
2929# docker rm -f $(docker ps -aq --filter ancestor=catthehacker/ubuntu:act-latest)
3030on :
31- pull_request :
31+ # pull_request_target runs in the BASE repo context, so GITHUB_TOKEN keeps
32+ # write access even for fork PRs — which is what lets the compare job post its
33+ # comment. The tradeoff is that this event is inherently more dangerous: the
34+ # benchmark job below checks out and runs the PR HEAD (untrusted) code. To
35+ # keep that safe, the default token here is read-only (see permissions), and
36+ # pull-requests: write is granted ONLY to the trusted compare job that never
37+ # runs PR code. Do not add secrets or write scopes to the benchmark job.
38+ pull_request_target :
3239 workflow_dispatch :
33-
34- # Needed so the compare job can post a comment on the PR.
40+
41+ # Read-only by default. The benchmark job runs untrusted PR head code, so it
42+ # must not have a write-capable token. Write access is scoped per-job on the
43+ # compare job (below), which only runs trusted base-repo code.
3544permissions :
3645 contents : read
37- pull-requests : write
3846
3947env :
4048 # -mod=mod lets `go test` update go.mod/go.sum in the ephemeral CI checkout.
@@ -182,7 +190,17 @@ jobs:
182190 compare :
183191 needs : benchmark
184192 runs-on : ubuntu-latest
193+ # Write scope lives HERE, not at the top level, because this job runs only
194+ # trusted base-repo code (the checkout below takes the default base ref, and
195+ # it never checks out or executes PR head code). The benchmark job keeps the
196+ # read-only default token.
197+ permissions :
198+ contents : read
199+ pull-requests : write
185200 steps :
201+ # No ref specified: under pull_request_target this checks out the BASE
202+ # repo/branch (trusted), which is exactly what we want for the code that
203+ # holds the write token.
186204 - name : Checkout code
187205 uses : actions/checkout@v4
188206
0 commit comments