From 0d23974aeb7f689556ad886ffdee8a2abcb945a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:15:54 +0000 Subject: [PATCH 1/3] Initial plan From 1b05abb451727ec297743d418817ae8dbb705d58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:22:56 +0000 Subject: [PATCH 2/3] Document GitHub App per-job permission narrowing and permission requirements Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../docs/reference/frontmatter-full.md | 11 ++- .../content/docs/reference/safe-outputs.md | 68 ++++++++++++++++++- docs/src/content/docs/reference/tokens.mdx | 5 +- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 8874bae7ee5..b1f2fcdfa1a 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -1784,7 +1784,10 @@ cache: [] # Safe output processing configuration that automatically creates GitHub issues, # comments, and pull requests from AI workflow output without requiring write -# permissions in the main job +# permissions in the main job. When using GitHub App tokens (app:), permissions +# are automatically narrowed per-job to match only what's needed, and tokens are +# auto-revoked at job end. Multiple safe outputs in the same workflow receive the +# union of their required permissions. # (optional) safe-outputs: # List of allowed domains for URI filtering in AI workflow output. URLs from other @@ -3412,8 +3415,10 @@ safe-outputs: github-token: "${{ secrets.GITHUB_TOKEN }}" # GitHub App credentials for minting installation access tokens. When configured, - # a token will be generated using the app credentials and used for all safe output - # operations. + # tokens are automatically minted per-job with permissions narrowed to match the + # job's permissions block. Tokens are auto-revoked at job end. This enables safe + # use of a broadly-permissioned GitHub App because each job only receives the + # specific permissions it needs. # (optional) app: # GitHub App ID. Should reference a variable (e.g., ${{ vars.APP_ID }}). diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index cf066f68b3c..687fdf34d14 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -19,6 +19,55 @@ safe-outputs: The agent requests issue creation; a separate job with `issues: write` creates it. +## Required Permissions by Safe Output Type + +Each safe output type requires specific GitHub permissions. When using GitHub App tokens (`app:`), permissions are **automatically narrowed per-job** to match only what's needed. The table below shows the minimum permissions required for each safe output type: + +| Safe Output Type | `contents` | `issues` | `pull-requests` | `discussions` | `actions` | `security-events` | `organization-projects` | Notes | +|------------------|------------|----------|-----------------|---------------|-----------|-------------------|-------------------------|-------| +| `create-issue` | read | write | - | - | - | - | - | | +| `update-issue` | read | write | - | - | - | - | - | | +| `close-issue` | read | write | - | - | - | - | - | | +| `link-sub-issue` | read | write | - | - | - | - | - | | +| `create-discussion` | read | write | write | - | - | - | - | With `fallback-to-issue: true` (default) | +| `create-discussion` | read | - | - | write | - | - | - | With `fallback-to-issue: false` | +| `update-discussion` | read | - | - | write | - | - | - | | +| `close-discussion` | read | - | - | write | - | - | - | | +| `add-comment` | read | write | write | write | - | - | - | Supports issues, PRs, and discussions | +| `hide-comment` | read | write | write | write | - | - | - | Supports issues, PRs, and discussions | +| `add-labels` | read | write | write | - | - | - | - | For issues and PRs | +| `remove-labels` | read | write | write | - | - | - | - | For issues and PRs | +| `create-pull-request` | write | write | write | - | - | - | - | With `fallback-as-issue: true` (default) | +| `create-pull-request` | write | - | write | - | - | - | - | With `fallback-as-issue: false` | +| `push-to-pull-request-branch` | write | write | write | - | - | - | - | | +| `update-pull-request` | read | - | write | - | - | - | - | | +| `close-pull-request` | read | - | write | - | - | - | - | | +| `mark-pull-request-as-ready-for-review` | read | - | write | - | - | - | - | | +| `create-pull-request-review-comment` | read | - | write | - | - | - | - | | +| `add-reviewer` | read | - | write | - | - | - | - | | +| `update-release` | write | - | - | - | - | - | - | | +| `dispatch-workflow` | - | - | - | - | write | - | - | | +| `assign-to-agent` | read | write | - | - | - | - | - | | +| `create-agent-session` | read | write | - | - | - | - | - | | +| `assign-milestone` | read | write | write | - | - | - | - | For issues and PRs | +| `assign-to-user` | read | write | - | - | - | - | - | | +| `create-code-scanning-alert` | read | - | - | - | - | write | - | | +| `autofix-code-scanning-alert` | read | - | - | - | read | write | - | | +| `create-project` | read | - | - | - | - | - | write | | +| `update-project` | read | - | - | - | - | - | write | | +| `create-project-status-update` | read | - | - | - | - | - | write | | +| `upload-asset` | write | - | - | - | - | - | - | | + +> [!NOTE] +> **Permission Union**: When multiple safe outputs are configured in the same workflow, the safe output job receives the **union** of all required permissions. For example, configuring both `create-issue:` and `create-pull-request:` results in `contents: write`, `issues: write`, and `pull-requests: write`. + +> [!TIP] +> **Fallback Fields**: Some safe output types support fallback mechanisms that affect required permissions: +> - `create-pull-request`: Set `fallback-as-issue: false` to avoid requiring `issues: write` permission +> - `create-discussion`: Set `fallback-to-issue: false` to avoid requiring `issues: write` permission +> +> Without these fields set to `false`, the default behavior includes fallback capabilities which require additional permissions. + ## Available Safe Output Types > [!NOTE] @@ -628,7 +677,7 @@ Exposes outputs: `status-update-id`, `project-id`, `status`. ### Pull Request Creation (`create-pull-request:`) -Creates PRs with code changes. Falls back to issue if creation fails (e.g., org settings block it). `expires` field (same-repo only) auto-closes after period: integers (days) or `2h`, `7d`, `2w`, `1m`, `1y` (hours < 24 treated as 1 day). +Creates PRs with code changes. By default, falls back to creating an issue if PR creation fails (e.g., org settings block it). Set `fallback-as-issue: false` to disable this fallback and avoid requiring `issues: write` permission. `expires` field (same-repo only) auto-closes after period: integers (days) or `2h`, `7d`, `2w`, `1m`, `1y` (hours < 24 treated as 1 day). ```yaml wrap safe-outputs: @@ -641,6 +690,7 @@ safe-outputs: if-no-changes: "warn" # "warn" (default), "error", or "ignore" target-repo: "owner/repo" # cross-repository base-branch: "vnext" # target branch for PR (default: github.ref_name) + fallback-as-issue: false # disable issue fallback (default: true) ``` The `base-branch` field specifies which branch the pull request should target. This is particularly useful for cross-repository PRs where you need to target non-default branches (e.g., `vnext`, `release/v1.0`, `staging`). When not specified, defaults to the workflow's branch (`github.ref_name`). @@ -656,7 +706,7 @@ safe-outputs: ``` > [!NOTE] -> PR creation may fail if "Allow GitHub Actions to create and approve pull requests" is disabled in Organization Settings. Fallback creates issue with branch link. +> PR creation may fail if "Allow GitHub Actions to create and approve pull requests" is disabled in Organization Settings. By default (`fallback-as-issue: true`), fallback creates an issue with branch link and requires `issues: write` permission. Set `fallback-as-issue: false` to disable fallback and only require `contents: write` + `pull-requests: write`. ### Close Pull Request (`close-pull-request:`) @@ -1336,6 +1386,20 @@ safe-outputs: create-issue: ``` +#### How GitHub App Tokens Work + +When you configure `app:` for safe outputs, tokens are **automatically managed per-job** for enhanced security: + +1. **Per-job token minting**: Each safe output job automatically mints its own token via `actions/create-github-app-token` with permissions explicitly scoped to that job's needs +2. **Permission narrowing**: Token permissions are narrowed to match the job's `permissions:` block - only the permissions required for the safe outputs in that job are granted +3. **Automatic revocation**: Tokens are explicitly revoked at job end via `DELETE /installation/token`, even if the job fails +4. **Safe shared configuration**: A broadly-permissioned GitHub App can be safely shared across workflows because tokens are narrowed per-job + +> [!TIP] +> **Why this matters**: You can configure a single GitHub App at the organization level with broad permissions (e.g., `contents: write`, `issues: write`, `pull-requests: write`), and each workflow job will only receive the specific subset of permissions it needs. This provides least-privilege access without requiring per-workflow App configuration. + +**Example**: If your workflow only uses `create-issue:`, the minted token will have `contents: read` + `issues: write`, even if your GitHub App has broader permissions configured. + ### Maximum Patch Size (`max-patch-size:`) Limits git patch size for PR operations (1-10,240 KB, default: 1024 KB): diff --git a/docs/src/content/docs/reference/tokens.mdx b/docs/src/content/docs/reference/tokens.mdx index 737e014be0d..84191adecaa 100644 --- a/docs/src/content/docs/reference/tokens.mdx +++ b/docs/src/content/docs/reference/tokens.mdx @@ -232,7 +232,9 @@ gh aw secrets set APP_PRIVATE_KEY --value "$(cat path/to/private-key.pem)" **How it works**: -At workflow start, a token is automatically minted with permissions matching your agent job's `permissions:` field. The token is passed to the GitHub MCP server and automatically revoked at workflow end (even on failure). +At workflow start, a token is automatically minted with **permissions matching your job's `permissions:` field**. The token is passed to the GitHub MCP server and automatically revoked at workflow end (even on failure). + +This is the same per-job narrowing behavior used by `safe-outputs.app:` - see the [Safe Outputs documentation](/gh-aw/reference/safe-outputs/#github-app-token-app) for a detailed explanation of how GitHub App tokens are narrowed per-job. **Token precedence and fallback**: @@ -243,6 +245,7 @@ At workflow start, a token is automatically minted with permissions matching you - The compiler automatically sets `GITHUB_MCP_SERVER_TOKEN` and passes it as `GITHUB_PERSONAL_ACCESS_TOKEN` (local/Docker) or an `Authorization: Bearer` header (remote). - In most cases, you do not need to set this token separately. Use `GH_AW_GITHUB_TOKEN` instead. - **GitHub App advantages**: Short-lived tokens (auto-revoked at workflow end), no credential rotation needed, automatic permission calculation, better auditability. +- **Per-job permission narrowing**: When using a GitHub App (via `tools.github.app:` or `safe-outputs.app:`), tokens are automatically narrowed to match the job's `permissions:` block. This means you can safely configure a broadly-permissioned GitHub App at the organization level, and each job will only receive the specific permissions it needs. - If using a GitHub App for both safe-outputs and GitHub MCP server, you can configure them independently for different permission levels. - Set the resource owner to the organization when the repository is organization-owned. - `GITHUB_TOKEN` is not supported in remote mode. Use `GH_AW_GITHUB_TOKEN` or a GitHub App instead. From e6d307539a2cf5828feb5b0d0f3b06948b17c772 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:34:12 +0000 Subject: [PATCH 3/3] Remove detailed permissions table as requested Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../content/docs/reference/safe-outputs.md | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 687fdf34d14..beb763ed669 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -19,55 +19,6 @@ safe-outputs: The agent requests issue creation; a separate job with `issues: write` creates it. -## Required Permissions by Safe Output Type - -Each safe output type requires specific GitHub permissions. When using GitHub App tokens (`app:`), permissions are **automatically narrowed per-job** to match only what's needed. The table below shows the minimum permissions required for each safe output type: - -| Safe Output Type | `contents` | `issues` | `pull-requests` | `discussions` | `actions` | `security-events` | `organization-projects` | Notes | -|------------------|------------|----------|-----------------|---------------|-----------|-------------------|-------------------------|-------| -| `create-issue` | read | write | - | - | - | - | - | | -| `update-issue` | read | write | - | - | - | - | - | | -| `close-issue` | read | write | - | - | - | - | - | | -| `link-sub-issue` | read | write | - | - | - | - | - | | -| `create-discussion` | read | write | write | - | - | - | - | With `fallback-to-issue: true` (default) | -| `create-discussion` | read | - | - | write | - | - | - | With `fallback-to-issue: false` | -| `update-discussion` | read | - | - | write | - | - | - | | -| `close-discussion` | read | - | - | write | - | - | - | | -| `add-comment` | read | write | write | write | - | - | - | Supports issues, PRs, and discussions | -| `hide-comment` | read | write | write | write | - | - | - | Supports issues, PRs, and discussions | -| `add-labels` | read | write | write | - | - | - | - | For issues and PRs | -| `remove-labels` | read | write | write | - | - | - | - | For issues and PRs | -| `create-pull-request` | write | write | write | - | - | - | - | With `fallback-as-issue: true` (default) | -| `create-pull-request` | write | - | write | - | - | - | - | With `fallback-as-issue: false` | -| `push-to-pull-request-branch` | write | write | write | - | - | - | - | | -| `update-pull-request` | read | - | write | - | - | - | - | | -| `close-pull-request` | read | - | write | - | - | - | - | | -| `mark-pull-request-as-ready-for-review` | read | - | write | - | - | - | - | | -| `create-pull-request-review-comment` | read | - | write | - | - | - | - | | -| `add-reviewer` | read | - | write | - | - | - | - | | -| `update-release` | write | - | - | - | - | - | - | | -| `dispatch-workflow` | - | - | - | - | write | - | - | | -| `assign-to-agent` | read | write | - | - | - | - | - | | -| `create-agent-session` | read | write | - | - | - | - | - | | -| `assign-milestone` | read | write | write | - | - | - | - | For issues and PRs | -| `assign-to-user` | read | write | - | - | - | - | - | | -| `create-code-scanning-alert` | read | - | - | - | - | write | - | | -| `autofix-code-scanning-alert` | read | - | - | - | read | write | - | | -| `create-project` | read | - | - | - | - | - | write | | -| `update-project` | read | - | - | - | - | - | write | | -| `create-project-status-update` | read | - | - | - | - | - | write | | -| `upload-asset` | write | - | - | - | - | - | - | | - -> [!NOTE] -> **Permission Union**: When multiple safe outputs are configured in the same workflow, the safe output job receives the **union** of all required permissions. For example, configuring both `create-issue:` and `create-pull-request:` results in `contents: write`, `issues: write`, and `pull-requests: write`. - -> [!TIP] -> **Fallback Fields**: Some safe output types support fallback mechanisms that affect required permissions: -> - `create-pull-request`: Set `fallback-as-issue: false` to avoid requiring `issues: write` permission -> - `create-discussion`: Set `fallback-to-issue: false` to avoid requiring `issues: write` permission -> -> Without these fields set to `false`, the default behavior includes fallback capabilities which require additional permissions. - ## Available Safe Output Types > [!NOTE]