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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ discovery publishes the subset GitHub documents for installation access tokens,
preserving alternative permission sets as OR and each set's required permissions
as AND. For every request, the adapter resolves the original method and path and
mints only one least-privileged permission set satisfied by the Realmroot token.
When GitHub documents a metadata-only alternative, it is used only as a fallback;
an available satisfied repository-domain permission is preferred so private
repository operations are not attempted with public-metadata authority.

GitHub requires both `contents:write` and `workflows:write` when the Contents API
writes under `.github/workflows`; the adapter enforces that condition from the
Expand Down
1 change: 1 addition & 0 deletions specs/github-adapter.feature
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Feature: GitHub App adapter
Given an Agent token satisfies one or more permission alternatives for a GitHub REST operation
When the Agent calls the original GitHub REST path through the adapter
Then the adapter selects one least-privileged satisfied permission set for that method and path
But a metadata-only alternative is a fallback when no satisfied domain permission alternative exists
And the short-lived GitHub installation credential contains only that selected permission set
And unrelated scopes in the Agent token are not minted into the GitHub credential
And slash-delimited Git reference names resolve to their documented operation
Expand Down
13 changes: 9 additions & 4 deletions src/providers/github/operation-permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ export function resolveGitHubOperationPermissions(input: {
if (available.length === 0) {
throw forbidden('The connected GitHub App does not grant the permissions required for this operation.')
}
const satisfied = available
.filter((requirement) => requirement.every((scope) => scopeSatisfies(scope, input.scopes)))
.sort(compareRequirements)
const selected = satisfied[0]
const satisfied = available.filter((requirement) => requirement.every((scope) => scopeSatisfies(scope, input.scopes)))
const domainSatisfied = satisfied.filter((requirement) => !metadataOnly(requirement))
const candidates = domainSatisfied.length > 0 ? domainSatisfied : satisfied
candidates.sort(compareRequirements)
const selected = candidates[0]
if (!selected) {
throw insufficientScope(
'The Agent token does not grant the permissions required for this operation.',
Expand All @@ -50,6 +51,10 @@ export function resolveGitHubOperationPermissions(input: {
return scopesToPermissions(new Set(selected), input.available)
}

function metadataOnly(requirement: readonly string[]) {
return requirement.length > 0 && requirement.every((scope) => parseScope(scope).permission === 'metadata')
}

function workflowFileRequirements(
method: string,
template: string,
Expand Down
11 changes: 11 additions & 0 deletions test/providers/github-operation-permissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ describe('GitHub operation permissions', () => {
).toEqual({ issues: 'write' })
})

it('[spec: github-adapter/github-operation-authority] prefers repository-domain authority over the metadata fallback', () => {
expect(
resolveGitHubOperationPermissions({
method: 'GET',
path: '/repos/saltbo/wakatoken/pulls',
scopes: new Set(['metadata:read', 'pull_requests:write']),
available: { metadata: 'read', pull_requests: 'write' },
}),
).toEqual({ pull_requests: 'read' })
})

it('[spec: github-adapter/github-native-tool-scope-challenge] reports every available scope alternative', () => {
try {
resolveGitHubOperationPermissions({
Expand Down
Loading