Repository navigation
Give CI's jobs a read-only token by default - #197
Merged
Merged
Conversation
The repository's default GITHUB_TOKEN is write, and three ci.yml jobs (pytest · ruff format, typecheck · build, publish-db) had no permissions block, so they held it. The pytest job runs PyPI packages with checkout's persisted token on pushes to main. A workflow-level `permissions: contents: read` now covers every job without its own block. None of the three needs more: checkout fetches this repository, setup-uv and setup-bun read public releases, and the database artifact is uploaded and then downloaded within one run through the Actions runtime token, not GITHUB_TOKEN (download-artifact v4 without `github-token` reads only the current run). tests/test_ci_gates.py pins the workflow block to exactly contents: read, checks every job's effective grant against it, shows the check catches nine widenings and allows narrowing, and pins publish-db's same-run, tokenless download. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
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.
What
.github/workflows/ci.ymlgets a workflow-levelpermissions: contents: read. Every job without its own block now gets a read-onlyGITHUB_TOKENinstead of the repository default, which is write (gh api repos/PolicyEngine/policyengine-scorecard/actions/permissions/workflow→default_workflow_permissions: write).Three jobs had no block and held that write token:
python(pytest · ruff format),app(typecheck · build) andpublish-db. Thepythonjob runsuv run --with …PyPI packages on pushes to main, andactions/checkoutpersists the token into.git/configfor them.replay-engineandreceiver-sandboxalready setcontents: readand keep their blocks.This is the other half of the least-privilege work from both reviews of #193. #196 removes the results bot's
workflowspermission. This PR touches only ci.yml's top lines; #196's ci.yml changes start at thereplay-variantsjob, so the two don't conflict.Why nothing needs more than
contents: readGITHUB_TOKENpythongithub-tokendefault reads astral-sh/uv's public releases;upload-artifact@v4has no token inputapptokendefault reads oven-sh/bun's public tagspublish-dbdownload-artifact@v4withnameonly, from the same runArtifact transfer within one run does not use
GITHUB_TOKEN:action.yml:github-tokenis "required when downloading artifacts from a different repository or from a different workflow run. If this is not specified, the action will attempt to download artifacts from the current repository and the current workflow run." Its README adds that by default the permissions are scoped so they can only download artifacts within the current workflow run.src/download-artifact.tssetsfindByonlyif (inputs.token). Without it,@actions/artifact'sgetArtifactcallsgetArtifactInternal, a Twirp client whose token isgetRuntimeToken()=ACTIONS_RUNTIME_TOKEN(packages/artifact/src/internal/shared/config.ts).runjob has onlycontents: readand runsdownload-artifact@v4andupload-artifact@v4. Both succeeded in runs 39204514294, 39200154998, 39196267700 and 39180905504.GitHub's docs (workflow-syntax, "How permissions are calculated for a workflow job"): the repository default is adjusted by the workflow-level block, then the job-level block, and "if you specify the access for any of these permissions, all of those that are not specified are set to
none."Invariants (pinned in
tests/test_ci_gates.py)permissionsis exactly{"contents": "read"}.{"contents": "read"}, or within its entry inNEEDS_MORE, which is empty. A string grant (read-all/write-all) or a missing grant fails.write-all,read-all,contents: write, an addedactions: read,pythoncontents: write,appwrite-all,publish-dbaddingactions: read, andreplay-engineid-token: write. Narrowing ({},contents: none) passes.pythonjob uploads, from this run (withis exactly{name}, nogithub-token,needs: python), onupload-artifact@v4/download-artifact@v4. A major bump should re-check the token claim. Nothing in ci.yml handssecrets.GITHUB_TOKENorgithub.tokento a step.Against main's ci.yml, the check reports
['the workflow level is None', 'python gets None', 'app gets None', 'publish-db gets None'].Property-based tests don't fit here: the input is one static file. The mutation cases above do that job.
Tests run locally
One file at a time,
-n ≤ 4,.venv-tests:tests/test_ci_gates.py: 20 passedtests/test_uk_replay_queue.py: 22 passed (its token pins hold)tests/test_uk_replay_results_pr.py: 62 passed (add_matrix_variantcompares the whole parsed file, and the new top-level key is in both before and after)tests/test_uk_replay_determinism.py: 12 passeduvx ruff format --check tests/test_ci_gates.py: cleanThis PR's CI shows
python(upload) andapprunning under the new block.publish-dbruns only on pushes to main. The merge's push run will show it still uploads, or warns as before (the Supabase host stopped resolving on 6 October).axiom: n/a: CI configuration only
🤖 Generated with Claude Code