Skip to content

Remove unnecessary contents: read from safe-output handlers - #48913

Merged
pelikhan merged 14 commits into
mainfrom
copilot/why-safe-outputs-require-contents-read
Jul 29, 2026
Merged

Remove unnecessary contents: read from safe-output handlers#48913
pelikhan merged 14 commits into
mainfrom
copilot/why-safe-outputs-require-contents-read

Conversation

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Nearly every safe_outputs job handler requested contents: read as an unconditional baseline, documented as "always included for repository context access." The safe_outputs job never reads repository file contents — the permission had no functional basis and forced operators with tightly-scoped GitHub Apps to justify a repository-contents read grant for pure output workloads.

Changes

Permission factory functions (permissions_factory.go)

Added 12 new factory functions omitting contents: read:

  • NewPermissionsIssuesWrite(), NewPermissionsPRWrite(), NewPermissionsDiscussionsWrite() and variants for issue+PR, issue+discussion, checks, security-events, and org-projects combinations.

Handler rewiring (safe_output_handlers.go, add_comment.go)

  • All ~25 pure-output handlers now use the new factories (issue, PR, discussion, label, check-run, code-scanning, project handlers).
  • upload-asset PermissionBuilder removed — safe_outputs never processes this handler type; actual git operations live in the separate publish_assets job (contents: write).
  • update-pull-request without update-branch now uses pull-requests: write only; with update-branch keeps contents: write.

Handlers retaining contents access

create-pull-request, merge-pull-request, push-to-pull-request-branch, update-pull-request (update-branch: true), update-release — all legitimately need contents: write.

Spec (safe-outputs-specification.md → v1.27.0)

  • Removed contents: read from all output-only handler permission tables.
  • Removed the "always included for repository context access" note.
  • Updated create_check_run changelog entry to reflect the corrected permission set.
  • Added v1.27.0 changelog entry.

Tests

Updated safe_outputs_permissions_test.go, safe_outputs_app_test.go, compile_outputs_comment_test.go, compile_outputs_label_test.go, and compiler_safe_outputs_job_test.go to assert the new minimal permission sets. App token tests now assert NotContains "permission-contents: read" for output-only handler combinations.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Document rationale for contents: read permission in GitHub Apps Remove unnecessary contents: read from safe-output handlers Jul 29, 2026
Copilot AI requested a review from pelikhan July 29, 2026 15:22
@pelikhan
pelikhan marked this pull request as ready for review July 29, 2026 15:29
Copilot AI review requested due to automatic review settings July 29, 2026 15:29
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Copilot AI 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.

Pull request overview

Removes unnecessary contents: read access from output-only safe-output handlers.

Changes:

  • Adds minimal permission factories and rewires handlers.
  • Keeps content permissions for repository-mutating operations.
  • Updates permission tests and specification v1.27.0.
Show a summary per file
File Description
pkg/workflow/safe_outputs_permissions_test.go Updates expected minimal permissions.
pkg/workflow/safe_outputs_app_test.go Verifies App tokens omit contents access.
pkg/workflow/safe_output_handlers.go Rewires handler permission factories.
pkg/workflow/permissions_factory.go Adds contents-free permission factories.
pkg/workflow/compiler_safe_outputs_job_test.go Updates compiled job expectations.
pkg/workflow/compile_outputs_label_test.go Updates label workflow permissions.
pkg/workflow/compile_outputs_comment_test.go Updates comment workflow permissions.
pkg/workflow/add_comment.go Removes baseline contents permission.
docs/src/content/docs/specs/safe-outputs-specification.md Publishes revised permission specification.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 9/9 changed files
  • Comments generated: 4
  • Review effort level: Medium

Comment on lines +264 to +266
return NewPermissionsChecksWritePRRead()
}
return NewPermissionsContentsReadChecksWrite()
return NewPermissionsChecksWrite()
**Version 1.27.0** (2026-07-29):

- **Removed**: `contents: read` from the required permissions of all output-only safe-output handlers. The permission was an unconditional baseline with no functional purpose in the `safe_outputs` job for handlers that only call issue, pull-request, discussion, checks, or security-events APIs. Operators who use tightly scoped GitHub Apps no longer need to justify a repository-contents read grant for pure output workloads.
- **Updated**: Permission tables for `create_issue`, `add_comment`, `close_issue`, `update_issue`, `assign_milestone`, `assign_to_user`, `unassign_from_user`, `assign_to_agent`, `link_sub_issue`, `set_issue_type`, `set_issue_field`, `comment_memory`, `create_discussion`, `update_discussion`, `close_discussion`, `hide_comment`, `add_labels`, `remove_labels`, `replace_label`, `add_reviewer`, `close_pull_request`, `mark_pull_request_as_ready_for_review`, `dismiss_pull_request_review`, `create_pull_request_review_comment`, `submit_pull_request_review`, `reply_to_pull_request_review_comment`, `resolve_pull_request_review_thread`, `update_pull_request` (without `update-branch`), `create_code_scanning_alert`, `autofix_code_scanning_alert`, `create_check_run`, `update_project`, `create_project`, and `create_project_status_update`.
**Notes**:

- Most complex permission set - requires security-events, contents, pull-requests, and actions scopes
- Most complex permission set — GitHub Actions Token requires security-events and actions scopes; GitHub App additionally requires contents and pull-requests scopes for branch/PR creation
Comment on lines +215 to +218
// NewPermissionsIssuesWrite creates permissions with issues: write only.
// Used for output-only handlers (create-issue, close-issue, etc.) that call the
// issues API without accessing repository file contents.
func NewPermissionsIssuesWrite() *Permissions {
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Caution

agentic threat detected
Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

No new or modified behavioral test functions in this PR. The PR only contains minor updates to existing test assertions (1-8 line changes per test file) to align with production code refactoring of safe-output permissions handling. No new behavioral test coverage was added.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions github-actions 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.

The permission reduction is correct and well-scoped. All changed handlers call only write APIs — no repository content reads. Key observations:

  • Each new NewPermissions* factory is narrowly named and documented.
  • upload_asset PermissionBuilder removal is correct — that handler belongs to publish_assets, not safe_outputs.
  • autofix-code-scanning-alert spec note correctly distinguishes GitHub Actions token vs GitHub App permissions.
  • Tests and spec updated consistently.

No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 32.8 AIC · ⌖ 4.72 AIC · ⊞ 5.3K

@github-actions github-actions 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.

Skills-Based Review 🧠

Applied /codebase-design and /improve-codebase-architecture — clean, well-scoped security improvement. Approving.

📋 Key Themes & Highlights

Key Themes

  • Principle of least privilege: Removing contents: read from ~25 output-only handlers is exactly the right call. The safe_outputs job has never needed repository file access for issue/PR/discussion writes.
  • Factory proliferation: 12 new factory functions added. This is a valid trade-off (explicit, searchable, documented) but worth watching as the set grows.
  • Spec alignment: safe-outputs-specification.md updated to v1.27.0 with a thorough changelog — spec and implementation stay in sync.

Positive Highlights

  • ✅ Every handler change is paired with a corresponding test assertion (NotContains "permission-contents: read")
  • upload_asset PermissionBuilder removal is safe: PermissionBuilder == nil is already guarded in safe_outputs_permissions.go
  • update-pull-request correctly preserves contents: write when update-branch: true — the conditional path is untouched
  • ✅ Project handlers retain issues: read via the new NewPermissionsOrganizationProjWriteIssuesRead() factory
  • ✅ Changelog entry accurately lists every affected handler

Minor Observations (non-blocking)

  • The factory naming scheme (NewPermissionsIssuesWriteDiscussionsWritePRWrite) grows verbose at three-permission combinations. A builder pattern (NewPermissions().With(Issues, Write).With(Discussions, Write)) could scale better for future additions — out of scope here.
  • No test explicitly covers the upload_asset handler contributing zero permissions after PermissionBuilder removal. Indirect coverage exists via the nil-guard in safe_outputs_permissions.go, but a targeted test case would add confidence.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 46.6 AIC · ⌖ 4.92 AIC · ⊞ 7K
Comment /matt to run again

@github-actions github-actions 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.

Review: no blocking issues found

This is a clean, mechanical permission-scoping change. Verified: all ~25 handler rewires correctly map to the new minimal-permission factories; upload-asset's PermissionBuilder removal is safe (git write lives in the separate publish_assets job, NewPermissionsContentsRead/NewPermissionsContentsReadProjectsWrite remain used elsewhere so no dead code); update-pull-request's conditional contents:write (only with update-branch: true) is correctly preserved and tested; new factory functions in permissions_factory.go are consistent with existing naming/behavior and cover all needed combinations without duplication; spec and test updates match the code changes.

💡 What I checked
  • Cross-referenced every handler diff in safe_output_handlers.go against its new factory function in permissions_factory.go.
  • Confirmed no handler that lost contents: read actually reads repository file contents (all are pure API-output handlers: issues, PRs, discussions, checks, security-events, projects).
  • Verified NewPermissionsContentsRead and NewPermissionsContentsReadProjectsWrite are still referenced elsewhere in the codebase (compiler_main_job_helpers.go, compiler_pre_activation_job.go, compiler_unlock_job.go, evals_job.go, threat_detection_job.go, tools.go), so removing the upload-asset PermissionBuilder does not orphan these functions or cause build issues.
  • Test assertions in safe_outputs_permissions_test.go and safe_outputs_app_test.go (NotContains "permission-contents: read") correctly validate the negative case.
  • Ran a background grumpy-coder sub-agent pass over the diff; it independently reported no issues, corroborating this assessment.

🔎 Code quality review by PR Code Quality Reviewer · aut00 · 68 AIC · ⌖ 7.13 AIC · ⊞ 7.8K
Comment /review to run again

…y safe-output handlers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (165 new lines in pkg/workflow/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/48913-remove-contents-read-from-safe-output-handlers.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-48913: Remove contents:read from Output-Only Safe-Output Handlers

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 48913-remove-contents-read-from-safe-output-handlers.md for PR #48913).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 66.8 AIC · ⌖ 9.96 AIC · ⊞ 8.7K ·
Comment /review to run again

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

- compiler_safe_outputs_steps.go: use target-aware minimal permissions
  for create-check-run GitHub App token (NewPermissionsChecksWrite or
  NewPermissionsChecksWritePRRead when target is set)
- safe_outputs_permissions.go: add stepsRequireContentsRead to detect
  actions/checkout in user-provided safe-output steps and conditionally
  add contents: read to avoid private-repo checkout failures
- spec: remove incorrect contents: write and pull-requests: write from
  autofix_code_scanning_alert GitHub App permissions (handler calls
  code-scanning API only, no branch/PR creation)
- spec: add issues: read to create_project and update_project permission
  tables to match NewPermissionsOrganizationProjWriteIssuesRead factory
- Tests: add TestComputePermissionsForSafeOutputs_Checkout and
  TestSafeOutputsCreateCheckRunAppTokenMinimalPermissions

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot update safe outputs specification, add tests for minimal permissions

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot recompile

pelikhan and others added 3 commits July 29, 2026 18:19
…ct-status-update, merge-pull-request, update-release, and create-agent-session handlers; fix spec note for create_project_status_update

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot update safe outputs specification, add tests for minimal permissions

Done in commit 4bc9bf6.

Tests added to safe_outputs_permissions_test.go:

  • create-check-run without target → checks: write only
  • create-check-run with target → checks: write + pull-requests: read
  • create-project-status-updateorganization-projects: write only
  • merge-pull-requestcontents: write + pull-requests: write
  • update-releasecontents: write only
  • create-agent-sessionissues: write only

Spec fix: The create_project_status_update notes section incorrectly said "Same permission requirements as create_project and update_project" — but create_project/update_project now require issues: read while create_project_status_update does not. Replaced with an accurate note clarifying this handler does not need issues: read.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot recompile

Done — recompiled all 266 workflow lock files in commit chore: recompile workflow lock files.

@pelikhan

Copy link
Copy Markdown
Collaborator

…owlist to match bash: [\"*\"] wildcard

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix https://github.com/github/gh-aw/actions/runs/30471194174/job/90641949587?pr=48913

Fixed in the latest commit. TestDailyAgentOfTheDayBlogWriterHasGitDenialMitigationAllowlist was checking for specific git allowlist entries that were removed when #48933 changed bash: to ["*"]. Updated the test to assert bash: ["*"] is present instead.

@pelikhan
pelikhan merged commit 893e828 into main Jul 29, 2026
29 checks passed
@pelikhan
pelikhan deleted the copilot/why-safe-outputs-require-contents-read branch July 29, 2026 17:02
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.84.0

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Why do issue/comment (and most) safe-outputs require contents: read for repository file access?

3 participants