Repository navigation
Refuse to resolve a ref under a trigger that carries secrets - #262
Merged
Merged
Conversation
resolve_ref.sh takes a pull request number, fetches refs/pull/<n>/head, and the job builds that commit and plays it. Here that is safe: both match workflows run from the actions tab, which needs write access, they declare contents: read at the top and neither is given a secret. Wired instead to pull_request_target, to workflow_run or to a comment event, the same script would run a fork's branch in a job holding this repository's secrets and a token that can write to it. The script now runs under workflow_dispatch, push, schedule and release, and exits non-zero under anything else. Those are the triggers where the ref was chosen by somebody who can already push here. With GITHUB_EVENT_NAME unset it runs, since that is a machine and not a runner, and a workflow called with workflow_call sees the event of the workflow that called it, so the release workflow's call is checked as well. The tests name the trigger rather than inheriting it, because they run inside a workflow themselves and would otherwise check whatever started them. That is the whole of what a script can check, and the documentation says so rather than a comment saying it. A job's permissions block is exposed to nothing running inside the job, and the repository's secrets cannot be enumerated from a step, so the check says the ref came from somebody with write access and says nothing about how privileged the job is. The contents: read at the top of strength.yml and calibrate.yml is what it does not replace. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XhA13EsiXpMQ5vea2P3Fc9
Contributor
Speed against
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
resolve_ref.shtakes a pull request number, fetchesrefs/pull/<n>/headandbuilds it. Here that has always been safe: the match workflows are dispatch
only, declare
contents: readand name no secret. Nothing enforced any ofthat, and the tools this script sits beside are now used outside this
repository, where a
pull_request_targetwiring would hand a fork arbitrarycode execution with a write token.
The script now refuses unless the trigger is one where the ref was chosen by
somebody who can already push:
workflow_dispatch,push,scheduleorrelease. It also runs whenGITHUB_EVENT_NAMEis unset, which means it isnot on a runner at all.
pull_requestis deliberately not on the list. A fork'spull_requestrungets a read-only token and no secrets, so it is arguably safe, but allowing it
would break the rule the guard states and nothing here needs it.
What it does not check, and why
Not permissions, and not secrets. Neither is readable from inside a job: the
permissions:block is exposed to nothing running there, and a secret is inthe environment only when a step above passes it, so its absence proves nothing
about what else the job could be handed. A script that tested for
GITHUB_TOKENand called that a permission check would be describing its ownenvironment and naming it something it is not.
The trigger is readable, and it is the part that carries the risk. The runner
sets
GITHUB_EVENT_NAMEon every job, anenv:block cannot override aGITHUB_name, and a workflow entered throughworkflow_callsees thecaller's event, so the check holds when the release workflow calls
strength.yml.So the guard does not say the job is unprivileged. A
workflow_dispatchworkflow that granted
contents: writeand passed a secret would pass it.That is the workflow's to declare, and
docs/DEVELOPMENT.mdnow says so, andnames the
permissions: contents: readat the top of both match workflows asthe part a reader has to write for themselves.
288 tests pass, seven of them new. The existing helper now sets
GITHUB_EVENT_NAMEexplicitly rather than inheriting it, without which thefile would fail in CI, where the tests themselves run under
pull_request.🤖 Generated with Claude Code
https://claude.ai/code/session_01XhA13EsiXpMQ5vea2P3Fc9
Generated by Claude Code