Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions changelog.d/fixed-dependabot-alerts-permission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- The sec-check agent's Dependabot alerts call no longer 403s once the GitHub App is granted the new read-only Dependabot alerts permission (documented in the GitHub App setup guide); existing installations must re-accept it.
1 change: 1 addition & 0 deletions src/docs/github-app-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Repository permissions used by the dashboard setup UI are:
| Actions | Read and write (Read-only with `governor.ci.auto_approve_workflows: false`) | Inspect workflow runs; the default-on approval sweep approves gated `pull_request` runs for trusted bots, the App and dashboard-admitted heads with the App token ([#16603](https://github.com/hivecommons/hive/issues/16603)). If the sweep is off or refused and GitHub holds trusted Dependabot/App/clanker runs in `action_required`, Hive posts `/ok-to-test`; the repository-local `hive-ok-to-test` workflow uses its own `GITHUB_TOKEN` with Actions write to approve those runs. With Read and write, the contributor lane also approves held runs directly for a PR author the contributor registry admitted at a `require_tiers` tier (`auto_merge.contributor_lane.approve_held_runs_for_registry_trusted`). Optional: without it the hive marks such a PR `hive:unauthorized` and waits for a maintainer to click **Approve and run** ([#14589](https://github.com/hivecommons/hive/issues/14589)). |
| Administration | Read-only | Read branch-protection rules before the merge-request watcher lands a PR; Hive refuses closed if it cannot verify the base branch is protected. |
| Commit statuses | Read/write | Needed when `governor.labels.automerge` is the default `lgtm` on a repository running Prow's commit-bound `lgtm` (prow-github-actions v3): the queue action sets a `prow/lgtm` success status on the approved head so Prow does not strip the label. Without the write grant the label and approval still land, but every queue action takes the failure branch and posts a PR comment saying the status could not be set. Choose a distinct label such as `hive/auto-merge` if you would rather keep this at Read. |
| Dependabot alerts | Read-only | Required for the sec-check agent's Dependabot scanning (`gh api repos/<org>/<repo>/dependabot/alerts`); existing installations must re-accept the updated permission under Settings → Integrations → the app → Review request. Optional: without it the call returns 403 and the hive still mints tokens. |

| Workflows | Read/write | Let trusted-tier agents push branches that modify `.github/workflows/`, and let the contributor lane merge an owner-approved PR that edits `.github/workflows/**` (`auto_merge.contributor_lane.owner_approval_releases_workflow_fence`, [#14664](https://github.com/hivecommons/hive/issues/14664)); without the grant such a merge is refused and the hive marks the PR `hive:unauthorized` (`merge-workflow-path`) and parks it at 090 for a maintainer's **Merge** click. Without it GitHub also rejects any GitHub App-token push touching those files server-side ("refusing to allow a GitHub App to create or update workflow … without workflows permission") no matter what the token requests. Maintainer user credentials are separate and can still push workflow-file changes when their account has the normal repository rights; the Hive App cannot until this grant is added **and re-accepted on each installation** (an existing install must approve the new permission under Settings → Integrations → the app → Review request). See [`hive-open-pr`](hive-open-pr.md#workflow-file-pushes-rejected-by-app-tokens) for the agent delivery procedure and [#6985](https://github.com/hivecommons/hive/issues/6985) for the outstanding permission change. |
Organization permission:
Expand Down
14 changes: 14 additions & 0 deletions src/pkg/github/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ func (a *AppAuth) ScopedTokenForRepos(ctx context.Context, tier string, repos []
Contents: gh.Ptr("write"),
PullRequests: gh.Ptr("write"),
Metadata: gh.Ptr("read"),
// Dependabot alerts: read lets the sec-check agent call
// dependabot/alerts. Optional like Workflows: the mint below
// retries without it when the installation has not accepted it.
VulnerabilityAlerts: gh.Ptr("read"),
}
case "trusted", "merger":
perms = &gh.InstallationPermissions{
Expand Down Expand Up @@ -425,6 +429,15 @@ func (a *AppAuth) ScopedTokenForRepos(ctx context.Context, tier string, repos []
defer cancel()
jwtClient := newJWTClient(jwtToken, a.apiURL)
installToken, _, err := jwtClient.Apps.CreateInstallationToken(mintCtx, installationID, opts)
if err != nil && perms.VulnerabilityAlerts != nil && perms.Workflows == nil {
a.logger.Warn("scoped token mint with vulnerability_alerts:read failed; retrying without it — "+
"the sec-check Dependabot alerts call will 403 until the App grants Dependabot alerts read-only and the installation re-accepts it; see docs/github-app-setup.md",
"tier", tier, "error", err)
perms.VulnerabilityAlerts = nil
alertsCtx, alertsCancel := mintContext(ctx)
defer alertsCancel()
installToken, _, err = jwtClient.Apps.CreateInstallationToken(alertsCtx, installationID, opts)
}
if err != nil && perms.Workflows != nil {
// The installation has likely not accepted the Workflows permission
// on the App yet — GitHub refuses to mint a token requesting a
Expand Down Expand Up @@ -796,6 +809,7 @@ func installationPermissionMap(p *gh.InstallationPermissions) map[string]string
put("metadata", p.GetMetadata())
put("pull_requests", p.GetPullRequests())
put("secrets", p.GetSecrets())
put("vulnerability_alerts", p.GetVulnerabilityAlerts())
put("workflows", p.GetWorkflows())
return out
}
40 changes: 40 additions & 0 deletions src/pkg/github/workflows_permission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,43 @@ func TestScopedToken_WorkflowsFallbackWhenInstallationLacksGrant(t *testing.T) {
}
}
}

// The contributor tier requests Dependabot alerts read for the sec-check
// agent, and degrades to a token without it when the installation has not
// accepted the permission.
func TestScopedToken_ContributorVulnerabilityAlertsWithFallback(t *testing.T) {
var requests []map[string]string
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
perms := decodePermissions(t, r)
requests = append(requests, perms)
if perms["vulnerability_alerts"] != "" {
w.WriteHeader(http.StatusUnprocessableEntity)
json.NewEncoder(w).Encode(map[string]any{"message": "The permissions requested are not granted to this installation."})
return
}
json.NewEncoder(w).Encode(map[string]any{
"token": "scoped-token-contributor",
"expires_at": time.Now().Add(time.Hour).Format(time.RFC3339),
})
})
server := httptest.NewServer(mux)
defer server.Close()

auth := newWorkflowsTestAuth(t, server.URL)
if _, err := auth.ScopedToken(context.Background(), "contributor"); err != nil {
t.Fatalf("ScopedToken: %v", err)
}
if len(requests) != 2 {
t.Fatalf("mint requests = %d, want 2", len(requests))
}
if requests[0]["vulnerability_alerts"] != "read" {
t.Errorf("first mint vulnerability_alerts = %q, want read", requests[0]["vulnerability_alerts"])
}
if _, present := requests[1]["vulnerability_alerts"]; present {
t.Errorf("retry still requests vulnerability_alerts: %v", requests[1])
}
if requests[1]["pull_requests"] != "write" {
t.Errorf("retry pull_requests = %q, want write", requests[1]["pull_requests"])
}
}
Loading