Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pkg/workflow/github_app_permissions_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,15 @@ func TestConvertPermissionsToAppTokenFields_GitHubAppOnly(t *testing.T) {
},
},
{
name: "discussions permission is NOT mapped (actions/create-github-app-token does not declare permission-discussions)",
name: "discussions permission IS mapped (actions/create-github-app-token reads all INPUT_PERMISSION-* env vars)",
permissions: func() *Permissions {
p := NewPermissions()
p.Set(PermissionDiscussions, PermissionWrite)
return p
}(),
absentFields: []string{"permission-discussions"},
expectedFields: map[string]string{
"permission-discussions": "write",
},
},
{
name: "models permission is NOT mapped (no GitHub App equivalent)",
Expand Down
19 changes: 11 additions & 8 deletions pkg/workflow/safe_outputs_app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,17 @@ func convertPermissionsToAppTokenFields(permissions *Permissions) map[string]str
if level, ok := permissions.Get(PermissionStatuses); ok {
fields["permission-statuses"] = string(level)
}
// Note: PermissionDiscussions ("discussions") is intentionally NOT mapped to "permission-discussions"
// here. The actions/create-github-app-token action does NOT declare "permission-discussions" as a
// supported input (see the generated inputs in its action.yml). Passing an unsupported input would
// be silently ignored, meaning the discussions scope would never be explicitly set. GitHub App
// installation tokens inherit the full set of app-installation permissions by default, so the token
// will have discussions access whenever the GitHub App installation itself was granted that permission.
// Repository-level discussions operations should therefore work without an explicit permission-discussions
// field.
// Note: "permission-discussions" is not a declared input in actions/create-github-app-token's action.yml,
// but the action reads ALL INPUT_PERMISSION-* env vars via process.env (see lib/get-permissions-from-inputs.js).
// GitHub Actions sets INPUT_PERMISSION-DISCUSSIONS for any `with: permission-discussions:` field, so
// the value IS forwarded to the GitHub API despite the "Unexpected input" warning.
// Crucially, when ANY permission-* input is specified the action scopes the token to ONLY those permissions
// (returning undefined → inherit-all only when zero permission-* inputs are present). Since the compiler
// always emits other permission-* fields, omitting permission-discussions causes the minted token to
// lack discussions access even when the GitHub App installation has that permission.
if level, ok := permissions.Get(PermissionDiscussions); ok {
fields["permission-discussions"] = string(level)
}

// GitHub App-only permissions (not available in GitHub Actions GITHUB_TOKEN).
// Use GetExplicit() so that shorthand permissions like "read-all" do not accidentally
Expand Down
18 changes: 9 additions & 9 deletions pkg/workflow/safe_outputs_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ Test workflow without safe outputs.
assert.Nil(t, workflowData.SafeOutputs, "SafeOutputs should be nil")
}

// TestSafeOutputsAppTokenDiscussionsPermission tests that discussions permission is handled correctly
// in the GitHub App token minting step.
// TestSafeOutputsAppTokenDiscussionsPermission tests that discussions permission is included
// in the GitHub App token minting step when create-discussion is configured.
//
// The actions/create-github-app-token action does NOT declare "permission-discussions" as a supported
// input (its generated action.yml only has "permission-team-discussions" for org-level team discussions).
// Therefore "permission-discussions" must NOT be emitted in the token mint step — the GitHub App
// installation token inherits the app's discussion permission from the installation itself.
// Although actions/create-github-app-token does not declare "permission-discussions" in its action.yml,
// the action reads ALL INPUT_PERMISSION-* env vars and forwards them to the GitHub API. When any
// permission-* input is specified, the token is scoped to only those permissions, so omitting
// permission-discussions would exclude discussions access from the minted token.
func TestSafeOutputsAppTokenDiscussionsPermission(t *testing.T) {
compiler := NewCompilerWithVersion("1.0.0")

Expand Down Expand Up @@ -155,9 +155,9 @@ Test workflow with discussions permission.
// Convert steps to string for easier assertion
stepsStr := strings.Join(job.Steps, "")

// permission-discussions is NOT a valid input to actions/create-github-app-token and must not
// appear in the token mint step. Discussions access is inherited from the GitHub App installation.
assert.NotContains(t, stepsStr, "permission-discussions:", "GitHub App token must not use permission-discussions (not a valid action input)")
// permission-discussions must be present because when any permission-* input is set,
// actions/create-github-app-token scopes the token to only those permissions.
assert.Contains(t, stepsStr, "permission-discussions: write", "GitHub App token should include discussions write permission")
// Other explicitly supported permission inputs should still be present
assert.Contains(t, stepsStr, "permission-contents: read", "GitHub App token should include contents read permission")
assert.Contains(t, stepsStr, "permission-issues: write", "GitHub App token should include issues write permission (create-discussion falls back to issue)")
Expand Down
Loading