Auto-scope GitHub App installation tokens to workspace repositories#1012
Auto-scope GitHub App installation tokens to workspace repositories#1012omercnet wants to merge 4 commits intokelos-dev:mainfrom
Conversation
Support optional repository scoping when generating GitHub App installation tokens. When TaskSpec.Repositories is set and the workspace uses GitHub App authentication, the controller passes the repository list to the GitHub API, producing a token that can only access the specified repositories. This enforces least-privilege access per task, preventing agents from accessing repositories beyond what the triggering user or workflow intended.
There was a problem hiding this comment.
2 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/manifests/install-crd.yaml">
<violation number="1" location="internal/manifests/install-crd.yaml:602">
P1: Task CRD adds `spec.repositories`, but TaskSpawner `taskTemplate` schema does not expose `repositories`, preventing spawned tasks from configuring repository-scoped GitHub App tokens.</violation>
</file>
<file name="internal/githubapp/token.go">
<violation number="1" location="internal/githubapp/token.go:155">
P2: Scoped installation-token requests send a JSON body but do not set `Content-Type: application/json`, which can break or nullify repository scoping.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Add Content-Type: application/json header when sending a JSON body to the GitHub installation token API. Without it, the API may silently ignore the repositories field. Add Repositories field to TaskTemplate so TaskSpawners can configure repo-scoped tokens for spawned Tasks. Wire the field through the task builder. Strengthen test assertions to verify Content-Type is sent when a body is present and absent when it is not.
There was a problem hiding this comment.
1 issue found across 7 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/githubapp/token_test.go">
<violation number="1" location="internal/githubapp/token_test.go:508">
P2: The nil-options test only checks for `Content-Type == "application/json"`, so it misses regressions where any other Content-Type is sent even though the test expects no Content-Type header at all.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| // Repository names are relative to the installation owner | ||
| // (e.g., "my-repo", not "org/my-repo"). | ||
| // +optional | ||
| Repositories []string `json:"repositories,omitempty"` |
There was a problem hiding this comment.
Because a taks is already scoped to a workspace which lists a repository is there a reason to duplicate that information into the taskSpawner rather than use the workspace repo to scope the token?
There was a problem hiding this comment.
Good question. The workspace does list a repo, but the installation token the GitHub App generates has access to every repo the App is installed on — not just the workspace repo. So without explicit scoping, an agent working on `org/frontend` can also read/write `org/infrastructure`.
Auto-deriving from the workspace repo would cover the common case well. The explicit `repositories` field is for cases where they diverge:
- Agent needs access to additional repos (cross-repo dependency updates)
- Workspace points to a fork but the agent also needs upstream API access
- Orchestrator wants to enforce per-user access boundaries (e.g., a chatbot restricting the token to repos the triggering user can reach)
Happy to add auto-scoping from the workspace as the default, with `repositories` as an opt-in override for the above cases. Would that work for you?
There was a problem hiding this comment.
I won't speak for @gjkim42 but I think using the existing repo field to scope the token, then expand to: #842 when that lands.
The upstream access is also solved by ensuring the token has access to remotes on the workspace.
Orchestrator wants to enforce per-user access boundaries (e.g., a chatbot restricting the token to repos the triggering user can reach)
This one is trickier.
In general I think we have the fields needed to scope the tokens already. We may want to put it behind a configuration flag to enable or disable (for backwards compatibility and can remove that in V1, for example).
There was a problem hiding this comment.
Agreed — removed Repositories from both TaskSpec and TaskTemplate. The controller now auto-derives the repo list from workspace.Spec.Repo + workspace.Spec.Remotes[].URL and passes them to the installation token API.
Net result: -70 lines, no new API surface. Cross-repo access via readOnlyWorkspaces (#842) can extend the token scope when it lands.
Remove the explicit Repositories field from TaskSpec and TaskTemplate. Instead, auto-scope GitHub App installation tokens to the repos already declared on the workspace: spec.repo plus any spec.remotes[].url. This avoids API surface duplication — the workspace already declares which repos the agent needs. Cross-repo access will be addressed by readOnlyWorkspaces (kelos-dev#842).
What type of PR is this?
/kind feature
What this PR does / why we need it:
When a Workspace uses GitHub App authentication, the controller generates an installation token with the App's full installation-wide permissions. An agent working on
org/frontendcan also read/writeorg/infrastructureor any other repository the App can reach.This PR auto-scopes installation tokens to the repos already declared on the Workspace —
spec.repoplusspec.remotes[].url. No new API fields, no configuration needed.Changes:
internal/githubapp/token.go— addTokenOptionsstruct with optionalRepositoriesfield, updateGenerateInstallationTokento accept scoping options and setContent-Type: application/jsonwhen a body is presentinternal/controller/task_controller.go— derive repo list from workspace'sRepo+Remotes[].URLand pass as token optionsinternal/githubapp/token_test.go— tests for scoped tokens, nil opts, empty opts, and Content-Type header assertionsNo new API surface. The repo list is derived automatically from the Workspace. Cross-repo access will be addressed by
readOnlyWorkspaces(#842).Which issue(s) this PR is related to:
Fixes #1011
Special notes for your reviewer:
TokenOptionsstruct usesjson:"repositories,omitempty"matching the GitHub API schemaparseGitHubRepohelpermake updateregenerates CRDs;make verifyandmake testpass cleanDoes this PR introduce a user-facing change?