Skip to content

Auto-scope GitHub App installation tokens to workspace repositories#1012

Open
omercnet wants to merge 4 commits intokelos-dev:mainfrom
omercnet:feature/repo-scoped-tokens
Open

Auto-scope GitHub App installation tokens to workspace repositories#1012
omercnet wants to merge 4 commits intokelos-dev:mainfrom
omercnet:feature/repo-scoped-tokens

Conversation

@omercnet
Copy link
Copy Markdown

@omercnet omercnet commented Apr 19, 2026

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/frontend can also read/write org/infrastructure or any other repository the App can reach.

This PR auto-scopes installation tokens to the repos already declared on the Workspace — spec.repo plus spec.remotes[].url. No new API fields, no configuration needed.

Changes:

  • internal/githubapp/token.go — add TokenOptions struct with optional Repositories field, update GenerateInstallationToken to accept scoping options and set Content-Type: application/json when a body is present
  • internal/controller/task_controller.go — derive repo list from workspace's Repo + Remotes[].URL and pass as token options
  • internal/githubapp/token_test.go — tests for scoped tokens, nil opts, empty opts, and Content-Type header assertions

No 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:

  • The TokenOptions struct uses json:"repositories,omitempty" matching the GitHub API schema
  • Repo names are extracted from workspace URLs using the existing parseGitHubRepo helper
  • When no repos are derived (empty workspace), the request body remains empty — identical to current behavior
  • make update regenerates CRDs; make verify and make test pass clean

Does this PR introduce a user-facing change?

GitHub App installation tokens are now automatically scoped to the repositories declared in the Workspace (spec.repo and spec.remotes), enforcing least-privilege access per task.

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.
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 19, 2026

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added kind/feature Categorizes issue or PR as related to a new feature needs-triage needs-priority needs-actor release-note labels Apr 19, 2026
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/manifests/install-crd.yaml Outdated
Comment thread internal/githubapp/token.go
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.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/githubapp/token_test.go Outdated
Comment thread api/v1alpha1/task_types.go Outdated
// Repository names are relative to the installation owner
// (e.g., "my-repo", not "org/my-repo").
// +optional
Repositories []string `json:"repositories,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).
@omercnet omercnet changed the title Add repository-scoped GitHub App installation tokens Auto-scope GitHub App installation tokens to workspace repositories Apr 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Categorizes issue or PR as related to a new feature needs-actor needs-priority needs-triage release-note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-scope GitHub App installation tokens to workspace repositories

3 participants