Skip to content

ci: default pr-ci token to contents read - #1493

Open
skevetter wants to merge 1 commit into
mainfrom
ci/pr-ci-least-privilege-permissions
Open

skevetter wants to merge 1 commit into
mainfrom
ci/pr-ci-least-privilege-permissions

Conversation

@skevetter

@skevetter skevetter commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

pr-ci.yml had no workflow-level permissions:. Every job without its own block ran with the repository default GITHUB_TOKEN scope, which may be read-write. The token is also passed into e2e tests as GH_ACCESS_TOKEN (github.token in the integration jobs), so an over-scoped token is exposed to test code.

This sets the default to contents: read and adds only the scope the audit below proves a job needs.

Part of the pr-ci.yml restructuring series (hardening).

Changes

  • Top-level permissions: contents: read.
  • lint job-level permissions: contents: read and pull-requests: read. golangci-lint-action only-new-issues: true calls pulls.get on pull_request events using github-token, which defaults to github.token.
  • No other edits. Existing changes (contents: read, pull-requests: read) and agent-skill (contents: read) blocks are unchanged.

Per-job audit

Job Actions and token-relevant steps Permission needed
changes dorny/paths-filter (lists PR files on pull_request) contents: read, pull-requests: read (already set on job)
precommit j178/prek-action (its token input is unused) contents: read
agent-skill setup-node, setup-python contents: read (already set on job)
lint golangci-lint-action only-new-issues calls pulls.get on pull_request contents: read, pull-requests: read (added)
unit-tests setup-go, actions/cache, upload-artifact (runtime token only) contents: read
provider-update-windows checkout, setup-go, upload-artifact on failure contents: read
can-read-secret secrets presence check only contents: read
build-cli goreleaser-action build --snapshot (no API calls), upload-artifact contents: read
licenses setup-go contents: read
integration-tests-unprivileged download-artifact (same run, no run-id) contents: read
integration-tests-microsandbox download-artifact, actions/cache, upload-artifact; e2e gets GH_ACCESS_TOKEN contents: read
integration-tests jlumbroso/free-disk-space (no API), skevetter/setup-kind (manifest fetch is unauthenticated, no token input), create-github-app-token (app secrets), download/cache/upload-artifact; e2e gets app token or github.token contents: read
ci-success needs evaluation only contents: read

No job needed write scopes. No job needed pull-requests other than changes and lint. Artifact and cache actions use the runtime token, not GITHUB_TOKEN scopes.

Verification

  • YAML parses. Top-level permissions is {contents: read}. Jobs with a permissions block are changes, agent-skill, lint.
  • actionlint v1.7.7: only one finding, the pre-existing background: true key on the jlumbroso/free-disk-space step in integration-tests. It is present on origin/main and was not changed here, because this PR limits edits to permissions blocks. Recent main runs of pr-ci are green.
  • prek hooks pass on pr-ci.yml.
  • CI on this PR is the runtime proof: every job runs with the new default scope. Results are recorded below.

CI

pr-ci run 38694439014 on head eeba5e7: conclusion success. All jobs passed, including Lint, Pre-commit, Unit Tests (ubuntu/macos), Build CLI (all three), Provider Update Tests (windows), every e2e test (the jobs that receive GH_ACCESS_TOKEN), and CI Success. No permission errors. No reruns were needed.

CodeRabbit

CLI review (coderabbit review --agent --committed against merge base 7d04a47): completed, 0 findings, reviewed .github/workflows/pr-ci.yml. GitHub CodeRabbit check: pass ("Review skipped: draft pull request"). Unresolved review threads: 0.

Summary by CodeRabbit

  • Chores
    • Updated CI workflow permissions to support reading pull request diffs and repository contents.

pr-ci.yml had no workflow-level permissions, so every job without its
own block ran with the repository default GITHUB_TOKEN scope. The token
is also passed to e2e tests as GH_ACCESS_TOKEN.

- Add top-level permissions: contents: read.
- Add job-level permissions to lint: pull-requests: read, because
  golangci-lint-action only-new-issues calls pulls.get on pull_request.
- Existing changes and agent-skill blocks are unchanged.
@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for images-devsy-sh canceled.

Name Link
🔨 Latest commit eeba5e7
🔍 Latest deploy log https://app.netlify.com/projects/images-devsy-sh/deploys/6ad076c24b7de0154a5cd3d1

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6ee482a9-e9f4-49cc-922f-ef1f100649d7
📥 Commits

Reviewing files that changed from the base of the PR and between 7d04a47 and eeba5e7.

📒 Files selected for processing: 1
  • .github/workflows/pr-ci.yml

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

📝 Walkthrough

The PR CI workflow now sets contents: read as the default permission and declares contents: read and pull-requests: read for the lint job.

Changes: PR CI permissions

Layer / File(s) Summary
Set workflow and lint permissions
.github/workflows/pr-ci.yml
The workflow sets contents: read as the default permission. The lint job declares contents: read and pull-requests: read.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~5 minutes

Change: Other

Merge Risk: ⚪ Minimal · up to eeba5

The workflow limits default token access to repository reads and gives lint the additional pull-request read access it needs. No concrete CI permission failure remains apparent, so the change is mergeable with normal checks.

### Architecture Summary

Architecture risk: 🔵 Low · up to eeba5

The changed surface does not map to a changed system, dependency edge, entrypoint, or external dependency.

Changed systems: None identified.

Architecture concerns
No architecture-level concerns identified.

Review details

Before / after behavior

  • observed — Modified behavior in .github/workflows/pr-ci.yml: Adds workflow-wide contents: read permissions as the default for every job; jobs may still declare their own permissions.
  • observed — Modified behavior in .github/workflows/pr-ci.yml: Adds job-level permissions to lint: contents: read and pull-requests: read.
Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the main change: setting the default pr-ci token permission to contents: read. It is concise and directly related to the workflow update.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 zizmor (1.30.1)
.github/workflows/pr-ci.yml

INFO zizmor: 🌈 zizmor v1.30.1
WARN audit:audit{input=Workflow(file://.github/workflows/pr-ci.yml)}: zizmor::audit::unsound_ternary: couldn't parse expression: matrix.provider-mode || ''parity''
fatal: no audit was performed
'template-injection' audit failed on file://.github/workflows/pr-ci.yml

Caused by:
0: error in 'template-injection' audit
1: failed to locate subfeature 'Subfeature { after: 143, fragment: Regex(Regex("matrix\.provider\-mode\s+\|\|\s+'parity'")) }' in feature ''"${RUNNER_TEMP}/devsy-ci-microsandbox" run --label "${{ matrix.label }}" --expected-count "${{ matrix.expected-spec-count }}" --provider-mode "${{ matrix.provider-mode || ''parity'' }}"''

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for devsydev canceled.

Name Link
🔨 Latest commit eeba5e7
🔍 Latest deploy log https://app.netlify.com/projects/devsydev/deploys/6ad076c24fcb82731f4c5a19

@skevetter
skevetter marked this pull request as ready for review October 15, 2026 08:12
@mergify

mergify Bot commented Oct 15, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant