Skip to content

Commit a879f39

Browse files
Copilotpelikhan
andauthored
fix: restrict github-app.permissions to explicit app-only scopes with read/none only
- Enumerate all GitHub App-only scopes explicitly instead of using additionalProperties - Only allow 'read' and 'none' values (not 'write') - Update override test to use a valid GitHub App-only scope (vulnerability-alerts)" Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3e32b3bc-7c8b-4f55-a88f-3d940a2ae8e6 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 81740c0 commit a879f39

2 files changed

Lines changed: 162 additions & 10 deletions

File tree

pkg/parser/schemas/main_workflow_schema.json

Lines changed: 153 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9680,10 +9680,159 @@
96809680
},
96819681
"permissions": {
96829682
"type": "object",
9683-
"description": "Optional extra permissions to merge into the minted token. These override the job-level permissions for the token (nested wins). Use this to add org-level permissions (e.g., members: read) that are not valid GitHub Actions scopes but are supported by GitHub Apps.",
9684-
"additionalProperties": {
9685-
"type": "string",
9686-
"enum": ["read", "write", "none"]
9683+
"description": "Optional extra GitHub App-only permissions to merge into the minted token. These are added on top of job-level permissions (nested wins). Use this for org-level permissions (e.g., members: read) that are not valid GitHub Actions scopes. Only 'read' and 'none' are allowed.",
9684+
"additionalProperties": false,
9685+
"properties": {
9686+
"administration": {
9687+
"type": "string",
9688+
"enum": ["read", "none"],
9689+
"description": "Permission level for repository administration (read/none). GitHub App-only permission for repository administration."
9690+
},
9691+
"codespaces": {
9692+
"type": "string",
9693+
"enum": ["read", "none"],
9694+
"description": "Permission level for Codespaces (read/none). GitHub App-only permission."
9695+
},
9696+
"codespaces-lifecycle-admin": {
9697+
"type": "string",
9698+
"enum": ["read", "none"],
9699+
"description": "Permission level for Codespaces lifecycle administration (read/none). GitHub App-only permission."
9700+
},
9701+
"codespaces-metadata": {
9702+
"type": "string",
9703+
"enum": ["read", "none"],
9704+
"description": "Permission level for Codespaces metadata (read/none). GitHub App-only permission."
9705+
},
9706+
"email-addresses": {
9707+
"type": "string",
9708+
"enum": ["read", "none"],
9709+
"description": "Permission level for user email addresses (read/none). GitHub App-only permission."
9710+
},
9711+
"environments": {
9712+
"type": "string",
9713+
"enum": ["read", "none"],
9714+
"description": "Permission level for repository environments (read/none). GitHub App-only permission."
9715+
},
9716+
"git-signing": {
9717+
"type": "string",
9718+
"enum": ["read", "none"],
9719+
"description": "Permission level for git signing (read/none). GitHub App-only permission."
9720+
},
9721+
"members": {
9722+
"type": "string",
9723+
"enum": ["read", "none"],
9724+
"description": "Permission level for organization members (read/none). Required for org team membership API calls."
9725+
},
9726+
"organization-administration": {
9727+
"type": "string",
9728+
"enum": ["read", "none"],
9729+
"description": "Permission level for organization administration (read/none). GitHub App-only permission."
9730+
},
9731+
"organization-announcement-banners": {
9732+
"type": "string",
9733+
"enum": ["read", "none"],
9734+
"description": "Permission level for organization announcement banners (read/none). GitHub App-only permission."
9735+
},
9736+
"organization-codespaces": {
9737+
"type": "string",
9738+
"enum": ["read", "none"],
9739+
"description": "Permission level for organization Codespaces (read/none). GitHub App-only permission."
9740+
},
9741+
"organization-copilot": {
9742+
"type": "string",
9743+
"enum": ["read", "none"],
9744+
"description": "Permission level for organization Copilot (read/none). GitHub App-only permission."
9745+
},
9746+
"organization-custom-org-roles": {
9747+
"type": "string",
9748+
"enum": ["read", "none"],
9749+
"description": "Permission level for organization custom org roles (read/none). GitHub App-only permission."
9750+
},
9751+
"organization-custom-properties": {
9752+
"type": "string",
9753+
"enum": ["read", "none"],
9754+
"description": "Permission level for organization custom properties (read/none). GitHub App-only permission."
9755+
},
9756+
"organization-custom-repository-roles": {
9757+
"type": "string",
9758+
"enum": ["read", "none"],
9759+
"description": "Permission level for organization custom repository roles (read/none). GitHub App-only permission."
9760+
},
9761+
"organization-events": {
9762+
"type": "string",
9763+
"enum": ["read", "none"],
9764+
"description": "Permission level for organization events (read/none). GitHub App-only permission."
9765+
},
9766+
"organization-hooks": {
9767+
"type": "string",
9768+
"enum": ["read", "none"],
9769+
"description": "Permission level for organization webhooks (read/none). GitHub App-only permission."
9770+
},
9771+
"organization-members": {
9772+
"type": "string",
9773+
"enum": ["read", "none"],
9774+
"description": "Permission level for organization members management (read/none). GitHub App-only permission."
9775+
},
9776+
"organization-packages": {
9777+
"type": "string",
9778+
"enum": ["read", "none"],
9779+
"description": "Permission level for organization packages (read/none). GitHub App-only permission."
9780+
},
9781+
"organization-personal-access-token-requests": {
9782+
"type": "string",
9783+
"enum": ["read", "none"],
9784+
"description": "Permission level for organization personal access token requests (read/none). GitHub App-only permission."
9785+
},
9786+
"organization-personal-access-tokens": {
9787+
"type": "string",
9788+
"enum": ["read", "none"],
9789+
"description": "Permission level for organization personal access tokens (read/none). GitHub App-only permission."
9790+
},
9791+
"organization-plan": {
9792+
"type": "string",
9793+
"enum": ["read", "none"],
9794+
"description": "Permission level for organization plan (read/none). GitHub App-only permission."
9795+
},
9796+
"organization-self-hosted-runners": {
9797+
"type": "string",
9798+
"enum": ["read", "none"],
9799+
"description": "Permission level for organization self-hosted runners (read/none). GitHub App-only permission."
9800+
},
9801+
"organization-user-blocking": {
9802+
"type": "string",
9803+
"enum": ["read", "none"],
9804+
"description": "Permission level for organization user blocking (read/none). GitHub App-only permission."
9805+
},
9806+
"repository-custom-properties": {
9807+
"type": "string",
9808+
"enum": ["read", "none"],
9809+
"description": "Permission level for repository custom properties (read/none). GitHub App-only permission."
9810+
},
9811+
"repository-hooks": {
9812+
"type": "string",
9813+
"enum": ["read", "none"],
9814+
"description": "Permission level for repository webhooks (read/none). GitHub App-only permission."
9815+
},
9816+
"single-file": {
9817+
"type": "string",
9818+
"enum": ["read", "none"],
9819+
"description": "Permission level for single file access (read/none). GitHub App-only permission."
9820+
},
9821+
"team-discussions": {
9822+
"type": "string",
9823+
"enum": ["read", "none"],
9824+
"description": "Permission level for team discussions (read/none). GitHub App-only permission."
9825+
},
9826+
"vulnerability-alerts": {
9827+
"type": "string",
9828+
"enum": ["read", "none"],
9829+
"description": "Permission level for Dependabot vulnerability alerts (read/none). GitHub App-only permission."
9830+
},
9831+
"workflows": {
9832+
"type": "string",
9833+
"enum": ["read", "none"],
9834+
"description": "Permission level for GitHub Actions workflow files (read/none). GitHub App-only permission."
9835+
}
96879836
},
96889837
"examples": [
96899838
{

pkg/workflow/github_mcp_app_token_test.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,8 @@ Test extra org-level permissions in GitHub App token.
443443
}
444444

445445
// TestGitHubMCPAppTokenExtraPermissionsOverrideJobLevel tests that extra permissions
446-
// under tools.github.github-app.permissions override job-level permissions (nested wins).
446+
// under tools.github.github-app.permissions can suppress a GitHub App-only scope
447+
// that was set at job level by overriding it with 'none' (nested wins).
447448
func TestGitHubMCPAppTokenExtraPermissionsOverrideJobLevel(t *testing.T) {
448449
compiler := NewCompilerWithVersion("1.0.0")
449450

@@ -452,6 +453,7 @@ on: issues
452453
permissions:
453454
contents: read
454455
issues: read
456+
vulnerability-alerts: read
455457
strict: false
456458
tools:
457459
github:
@@ -460,12 +462,12 @@ tools:
460462
app-id: ${{ vars.APP_ID }}
461463
private-key: ${{ secrets.APP_PRIVATE_KEY }}
462464
permissions:
463-
contents: write
465+
vulnerability-alerts: none
464466
---
465467
466468
# Test Workflow
467469
468-
Test that nested permissions override job-level (nested wins).
470+
Test that nested permissions override job-level GitHub App-only scopes (nested wins).
469471
`
470472

471473
tmpDir := t.TempDir()
@@ -481,10 +483,11 @@ Test that nested permissions override job-level (nested wins).
481483
require.NoError(t, err, "Failed to read lock file")
482484
lockContent := string(content)
483485

484-
// The nested permission (write) should win over the job-level permission (read)
485-
assert.Contains(t, lockContent, "permission-contents: write", "Nested contents: write should override job-level contents: read")
486-
assert.NotContains(t, lockContent, "permission-contents: read", "Job-level contents: read should be overridden by nested write")
486+
// The nested permission (none) should win over the job-level permission (read)
487+
assert.Contains(t, lockContent, "permission-vulnerability-alerts: none", "Nested vulnerability-alerts: none should override job-level: read")
488+
assert.NotContains(t, lockContent, "permission-vulnerability-alerts: read", "Job-level vulnerability-alerts: read should be overridden by nested none")
487489

488490
// Other job-level permissions should still be present
491+
assert.Contains(t, lockContent, "permission-contents: read", "Unaffected job-level contents permission should still be present")
489492
assert.Contains(t, lockContent, "permission-issues: read", "Unaffected job-level issues permission should still be present")
490493
}

0 commit comments

Comments
 (0)