Scope GITHUB_TOKEN permissions per job - #1239
Conversation
A job with no `permissions:` block inherits whatever the repository hands out. Each block added here grants what that job's own steps need and nothing more.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
There was a problem hiding this comment.
APPROVE at ad2ee951
Correct least-privilege move: workflow-level permissions: {}, then per-job grants. Verified the release-critical jobs already carried (and still carry) what Trusted Publishing needs:
publish-to-pypi/ TestPyPI publish:id-token: write(+contents: read)create-github-release:contents: writepost-release-bump:contents: write+pull-requests: write
Only build newly declares contents: read (was inheriting the old workflow-level read). No package-cargo change.
Timing for tomorrow’s 0.6.0: safe to merge before or after the tag — if it lands before v0.6.0, Thursday’s publish-pypi.yml runs with this hardening. Do not fold into #1211 (workflows are not in the wheel). Prefer merging after the tag only if you want zero churn on the publish path for this cut.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Alignment Review Report
Scope: this PR touches only 6 GitHub Actions workflow files — it introduces a default-deny permissions: {} at the workflow level and grants least-privilege permissions: per job. No Python, framework, or environment code is changed.
Automated Checks
- Lint: FAIL, but pre-existing and unrelated.
bash .claude/hooks/lint.shfails only on Python formatting drift:usortflagstests/envs/test_grid_world.pyandtests/envs/test_julia_env.py(both documented inAGENTS.mdas pre-existing on a clean tree), andruff format --check src/ tests/ envs/reports "56 files would be reformatted" (mostly underenvs/). This PR changes zero.pyfiles, so it neither introduces nor fixes any lint issue. - Debug code: CLEAN for this PR.
check-debug.shlists manyprint/console.printcalls, but all are pre-existing insrc/; none are touched here. - YAML validity: PASS. All six workflows parse as valid YAML. Verified top-level
permissions: {}plus a per-jobpermissions:block on every job (0 jobs missing a block):discovery-catalog(snapshot),package-ci(package),publish-pypi(build, publish-to-pypi, create-github-release, post-release-bump),publish-testpypi(build, publish-to-testpypi),test(validate-env-locks, test, test-thinkingbox-env, check-env-docs, lint),validation-runtime(runtime).
Open RFCs Context
All RFCs are In Review or Draft, but none cover CI/CD or GitHub Actions token permissions, so none conflict with this change:
- In Review: 000 (project phases), 001 (abstractions), 002 (env-spec), 003 (MCP), 005 (agentic harnesses), 008 (auto-validation), 012 (harbor capture providers)
- Draft: 010 (echo-env token world model), 011 (ARD catalog discovery)
- 004 (rubrics) has no status line.
Tier 1: Fixes Required
None. The change is functionally correct and complete — sensitive jobs retain the scopes they need: publish-to-pypi/publish-to-testpypi keep id-token: write (OIDC trusted publishing), create-github-release keeps contents: write, and post-release-bump keeps contents: write + pull-requests: write.
Optional cosmetic nit (non-blocking): removing the old top-level permissions: block left a double blank line before the new permissions: {} / concurrency: in discovery-catalog.yml, package-ci.yml, publish-pypi.yml, publish-testpypi.yml, and validation-runtime.yml. The repo has no yamllint/actionlint config, so nothing enforces this; collapsing to a single blank line would just keep style consistent. (test.yml is already clean.)
Tier 2: Alignment Discussion
Principle Conflicts
None identified. The change reinforces the security posture in INVARIANTS.md ("No credential exposure", least-privilege for GITHUB_TOKEN) and PRINCIPLES.md ("Container isolation ... for security"). It does not touch the Gymnasium API, client/server separation, reward computation, or agent isolation.
RFC Conflicts
None identified. No open RFC covers CI/CD workflow permissions.
Summary
- 0 mechanical issues to fix (1 optional cosmetic nit: double blank line in 5 files)
- 0 alignment points for human review
- 0 RFC conflicts to discuss
Clean, well-scoped security hardening — default-deny at the workflow level with least-privilege per job, and every job verified to retain exactly the token scopes it needs. No reviewers need to be pulled in for alignment concerns.
Sent by Cursor Automation: Pre-review
| permissions: | ||
| contents: read | ||
|
|
||
| permissions: {} |
There was a problem hiding this comment.
Optional nit: this leaves a double blank line above permissions: {} (the old top-level permissions: block was removed here). Collapsing to a single blank line keeps the style consistent. Same applies in package-ci.yml, publish-pypi.yml, publish-testpypi.yml, and validation-runtime.yml. Nothing enforces this (no yamllint/actionlint config), so it's purely cosmetic and non-blocking.
…oping) Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
dev131 uploaded 200 OK through #1239-hardened Trusted Publishing, but the simple index lagged past five 30s retries. Scratch-only; not for main. Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
OpenEnv 0.6.0 Breaking change - `--llm-endpoint` / OpenAIClient now take a full base URL; the implicit port 8000 is gone. `http://localhost` means port 80; use `http://localhost:8000` or `--llm-port 8000` for the old behavior (#1189, landed via #1237). New - NovitaSandboxProvider: run an OpenEnv server in a Novita AI sandbox over wss://, from a registry image or a local Dockerfile; install with `pip install openenv[novita]` (#1191). - RFC 008 Level 2 validation building blocks: manifest-v2, report-v2 and runtime-plan schemas, the severity-v2 policy, and DockerValidationProvider (#1178, #1179). The `openenv validate` CLI is unchanged in this release. Fixes - A failed `new_session()` no longer leaves the provider's container or sandbox running (#1145). Repository and environment images (not in the wheel) - coding_env `additional_imports` now extends the default safe-import allowlist (#1147). - Environment lockfile security updates: anyio 4.14.2 and soupsieve 2.9.2 (#1196, #1197, #1201, #1202). - CI hardening: per-job GITHUB_TOKEN scopes and SHA-pinned actions (#1221, #1223, #1225, #1226, #1228, #1239, #1240); validation-lab toolchain pins (#1229, #1230, #1231); docs navigation and Miles integration docs (#1219, #1220). Known, accepted for this release: Novita provider debt (unbraced $ARG ordering in Dockerfile flattening, a tbench2 example readiness leak); fixes follow in #1235. Validated: exact-head CI 13/13 on 0034202; TestPyPI 0.6.0.dev141 (run 35854754769) byte-identical to the release wheel apart from Version; clean wheel/sdist install, CLI, and Echo reset/step smoke.


Workflow hardening
GITHUB_TOKENpermissions scoped per jobA workflow with no
permissions:block inherits whatever the repository or theorganization hands out, which is commonly write access to everything. Any step
in that job — including one inside a third-party action — can then push commits,
open releases or edit issues with it.
Each block below grants a job the scopes its own steps need and nothing else.
Jobs that already declared their permissions were left untouched. The
workflow-level
permissions: {}is only added once every job in the filecarries its own block, because on its own it would silently strip the jobs it
missed.
.github/workflows/discovery-catalog.ymlsnapshotcontents: read.github/workflows/package-ci.ymlpackagecontents: read.github/workflows/publish-pypi.ymlbuildcontents: read.github/workflows/publish-testpypi.ymlbuildcontents: read.github/workflows/test.ymlcheck-env-docscontents: read.github/workflows/test.ymllintcontents: read.github/workflows/test.ymltestcontents: read.github/workflows/test.ymltest-thinkingbox-envcontents: read.github/workflows/test.ymlvalidate-env-lockscontents: read.github/workflows/validation-runtime.ymlruntimecontents: readNote
Low Risk
Workflow-only change that narrows default token permissions; jobs that need write access already declare it explicitly.
Overview
Hardens GitHub Actions by removing broad workflow-level
GITHUB_TOKENscopes and granting only what each job needs.Six workflows (
discovery-catalog,package-ci,publish-pypi,publish-testpypi,test,validation-runtime) now setpermissions: {}at the workflow root so jobs do not inherit org/repo default write access. Checkout/build/test/lint jobs that previously relied on workflow-widecontents: readnow declarecontents: readon the job. Jobs that already had explicit permissions (e.g. PyPI publish withid-token: write, release jobs withcontents: write) are unchanged.No application or runtime behavior changes—only CI token scoping.
Reviewed by Cursor Bugbot for commit ad2ee95. Bugbot is set up for automated code reviews on this repo. Configure here.