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
8 changes: 8 additions & 0 deletions specs/github-adapter.feature
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ Feature: GitHub App adapter
And unsupported createPullRequest, addComment, and mergePullRequest mutations use GitHub's installation-compatible REST operations
And the GitHub credential is never returned

@journey:github-cross-installation-boundary @entrypoint:http
Scenario: GitHub operations stay inside one selected App installation
Given an Agent selected a GitHub App installation
When GitHub CLI tries to create a pull request in a repository outside that installation
Then the adapter rejects the operation before requesting a write credential
And explains that the target repository must belong to the selected App installation
But it does not request broader user credentials or another OAuth application

@journey:github-git-transport @entrypoint:http
Scenario: Native Git uses the GitHub installation through the adapter
Given the Agent has approved repository contents authority
Expand Down
2 changes: 1 addition & 1 deletion src/providers/github/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ function repositoryTarget(path: string, installation: GitHubAuthorizationContext
if (!match) return
const owner = decodeURIComponent(match[1] as string)
if (owner.toLowerCase() !== installation.accountLogin.toLowerCase()) {
throw forbidden('The repository owner is outside the selected GitHub installation.')
throw forbidden('The target repository must belong to the selected GitHub App installation.')
}
const repository = decodeURIComponent(match[2] as string)
if (
Expand Down
35 changes: 35 additions & 0 deletions test/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,41 @@ describe('GitHub adapter contract', () => {
})
})

it('[spec: github-adapter/github-cross-installation-boundary] rejects pull requests outside the selected installation', async () => {
const provider = fakeProvider()
provider.request = vi.fn(async () => Response.json({ data: { node: { nameWithOwner: 'upstream/example' } } }))
const response = await testApp({
provider,
authenticator: {
authenticate: vi.fn(async () => ({
...principal,
scopes: new Set(['metadata:read', 'pull_requests:write']),
})),
},
}).request('/github/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({
query:
'mutation PullRequestCreate($input: CreatePullRequestInput!) { createPullRequest(input: $input) { pullRequest { id url } } }',
variables: {
input: {
repositoryId: 'upstream-repository',
baseRefName: 'main',
headRefName: 'realmroot:codex/fix',
title: 'Fix adapter',
},
},
}),
})

expect(response.status).toBe(403)
await expect(response.json()).resolves.toMatchObject({
detail: 'The target repository must belong to the selected GitHub App installation.',
})
expect(provider.installationToken).toHaveBeenCalledTimes(1)
})

it('[spec: github-adapter/github-graphql-proxy] preserves the GitHub CLI addComment mutation', async () => {
const provider = fakeProvider()
provider.request = vi
Expand Down
Loading