Skip to content

Scope GITHUB_TOKEN permissions per job - #1239

Merged
cursor[bot] merged 1 commit into
mainfrom
security/scope-permissions
Sep 23, 2026
Merged

cursor[bot] merged 1 commit into
mainfrom
security/scope-permissions

Conversation

@hf-security-analysis

@hf-security-analysis hf-security-analysis Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Workflow hardening

GITHUB_TOKEN permissions scoped per job

A workflow with no permissions: block inherits whatever the repository or the
organization 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 file
carries its own block, because on its own it would silently strip the jobs it
missed.

Workflow Job Granted
.github/workflows/discovery-catalog.yml snapshot contents: read
.github/workflows/package-ci.yml package contents: read
.github/workflows/publish-pypi.yml build contents: read
.github/workflows/publish-testpypi.yml build contents: read
.github/workflows/test.yml check-env-docs contents: read
.github/workflows/test.yml lint contents: read
.github/workflows/test.yml test contents: read
.github/workflows/test.yml test-thinkingbox-env contents: read
.github/workflows/test.yml validate-env-locks contents: read
.github/workflows/validation-runtime.yml runtime contents: read

Opened by the workflow security bot. It changes what this pull request says it
changes, and nothing else.


Note

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_TOKEN scopes and granting only what each job needs.

Six workflows (discovery-catalog, package-ci, publish-pypi, publish-testpypi, test, validation-runtime) now set permissions: {} at the workflow root so jobs do not inherit org/repo default write access. Checkout/build/test/lint jobs that previously relied on workflow-wide contents: read now declare contents: read on the job. Jobs that already had explicit permissions (e.g. PyPI publish with id-token: write, release jobs with contents: 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.

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.
@burtenshaw burtenshaw added enhancement New feature or request size: small Small pull request labels Sep 23, 2026 — with Cursor
@bot-ci-comment

Copy link
Copy Markdown

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: write
  • post-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.

View PR

Open in Web View Automation 

Sent by Cursor Automation: Release

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.sh fails only on Python formatting drift: usort flags tests/envs/test_grid_world.py and tests/envs/test_julia_env.py (both documented in AGENTS.md as pre-existing on a clean tree), and ruff format --check src/ tests/ envs/ reports "56 files would be reformatted" (mostly under envs/). This PR changes zero .py files, so it neither introduces nor fixes any lint issue.
  • Debug code: CLEAN for this PR. check-debug.sh lists many print/console.print calls, but all are pre-existing in src/; none are touched here.
  • YAML validity: PASS. All six workflows parse as valid YAML. Verified top-level permissions: {} plus a per-job permissions: 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.

Open in Web View Automation 

Sent by Cursor Automation: Pre-review

permissions:
contents: read

permissions: {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cursor
cursor Bot merged commit ccdddd1 into main Sep 23, 2026
15 checks passed
@cursor
cursor Bot deleted the security/scope-permissions branch September 23, 2026 11:19
cursor Bot pushed a commit that referenced this pull request Sep 23, 2026
…oping)

Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
cursor Bot pushed a commit that referenced this pull request Sep 23, 2026
Trigger for the 0.6.0 candidate at 2e6aa75 (folds #1239 GITHUB_TOKEN
job scoping into the publish path). Scratch branch only; not merged to main.

Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
cursor Bot pushed a commit that referenced this pull request Sep 23, 2026
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>
cursor Bot added a commit that referenced this pull request Sep 24, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size: small Small pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant