161 lines
10 KiB
Markdown
161 lines
10 KiB
Markdown
# 0047 — Separate GitHub deployment, workspace automation, and personal identities
|
|
|
|
> Amended by
|
|
> [ADR-2026-07-21-workspace-selectable-github-app-registrations](2026-07-21-workspace-selectable-github-app-registrations.md):
|
|
> Kandev stores multiple App registrations and every workspace explicitly selects one registration
|
|
> and installation. The automation and personal identity boundaries remain unchanged.
|
|
>
|
|
> Amended by
|
|
> [ADR-2026-07-27-task-git-credential-policy](2026-07-27-task-git-credential-policy.md):
|
|
> workspace automation identity and task Git credential routing are separate settings. Background
|
|
> automation still uses the workspace connection; tasks may explicitly inherit executor
|
|
> credentials instead of receiving the managed broker contract.
|
|
>
|
|
> Amended by
|
|
> [ADR-2026-08-02-new-workspace-github-access-defaults](2026-08-02-new-workspace-github-access-defaults.md):
|
|
> operator-authorized workspace creation may snapshot the host's active authenticated `gh`
|
|
> host/login as that new workspace's explicit named CLI automation connection. Member-created
|
|
> workspaces never receive the operator identity automatically.
|
|
|
|
- Status: accepted
|
|
- Date: 2026-07-19
|
|
- Area: backend, frontend, security
|
|
- Related: [Workspace GitHub Authentication](../specs/integrations/github-authentication.md),
|
|
[ADR 0030](0030-workspace-scoped-integration-settings.md)
|
|
|
|
## Context
|
|
|
|
Kandev currently constructs one GitHub client for the entire installation. It discovers a `gh` CLI
|
|
login, environment token, or globally named secret and then reuses that client for interactive
|
|
queries, workspace watches, cleanup, CI automation, and executor token injection. Repository scope
|
|
settings are workspace-owned, but the identity that enforces them is not.
|
|
|
|
Self-hosted companies need a different trust model from a local single-user installation. An
|
|
organization may install a GitHub App to authorize repository automation centrally, while an
|
|
individual still needs authenticated-viewer queries and human attribution. GitHub exposes these as
|
|
different principals: installation access tokens act as the App installation, while App user access
|
|
tokens act on behalf of a user. Installation tokens cannot be treated as "the authenticated user."
|
|
|
|
GitHub App installation tokens expire after one hour, so copying one into an executor environment
|
|
at task launch is not a complete transport design. The App private key cannot be exposed to agents
|
|
to let them mint replacements.
|
|
|
|
## Decision
|
|
|
|
GitHub authentication has three ownership layers and is resolved by purpose:
|
|
|
|
1. **Deployment App registration catalog.** A deployment may store multiple GitHub App
|
|
registrations, but none is globally active. Each workspace explicitly selects a registration
|
|
and verified installation. The App ID, client ID, private key, client secret, webhook secret,
|
|
slug, and public callback URL remain registration-manager configuration. Workspace and user APIs
|
|
never read or return secret material. Reusing a registration is an explicit sharing choice;
|
|
creating separate registrations provides independent root credentials and bot identities.
|
|
2. **Workspace-owned automation connection.** Each workspace selects exactly one automation source:
|
|
PAT, a named `gh` CLI host/login, a verified App installation, or the temporary
|
|
`legacy_shared` migration source. Background work and managed-mode task agents use this
|
|
identity; executor-inherited tasks deliberately use executor-visible Git credentials instead.
|
|
3. **User-owned personal connection within a workspace.** At most one verified App user connection
|
|
exists per `(workspace_id, user_id)`. It provides authenticated-viewer semantics and human
|
|
attribution. It is never injected into an agent. The current runtime uses `default-user`; future
|
|
authentication can replace the current-user resolver without changing credential ownership.
|
|
|
|
The GitHub service resolves a credential using `(workspace_id, user_id, purpose)` and returns a
|
|
principal description and capability set with the client. Purpose is explicit: automation,
|
|
personal read, personal mutation, or git transport. A request never reads a process-global client.
|
|
Caches, rate limits, and singleflight keys include workspace, credential generation, and principal.
|
|
|
|
For local `gh` CLI auth, Kandev stores only host/login and runs
|
|
`gh auth token --hostname <host> --user <login>`. It does not call `gh auth switch`, and child
|
|
processes used for discovery strip inherited `GH_TOKEN` and `GITHUB_TOKEN` so ambient environment
|
|
state cannot override the selected account. Derived CLI tokens remain memory-only.
|
|
|
|
For managed git transport, an executor gets a Git credential helper and one
|
|
task/workspace/repository/generation-bound broker lease per attached GitHub repository. The helper
|
|
asks Kandev for a current automation token on each matching credential request. App installation
|
|
tokens are minted with GitHub's repository restriction for the redeemed repository and can refresh
|
|
during long tasks. The broker cannot mint after disconnect or revocation. App private keys and
|
|
personal tokens never enter the executor.
|
|
|
|
PAT/CLI automation uses the same helper contract, but those provider-issued bearer tokens cannot be
|
|
cryptographically narrowed after redemption. The lease and exact HTTPS host/path matching prevent
|
|
accidental cross-repository redemption; the agent subprocess remains trusted with every repository
|
|
and operation granted to the bearer token. A PATH-prepended `gh` shim redeems the primary
|
|
repository lease for each child invocation and isolates host `gh` configuration. This gives
|
|
App-backed `gh` commands primary-repository scope while Git's helper can select every attached
|
|
repository lease. An explicit profile `GITHUB_TOKEN` or `GH_TOKEN` is an unmanaged operator
|
|
override and bypasses broker selection.
|
|
|
|
Review watches persist the verified target login captured from a human connection at creation.
|
|
Installation-token polling renders explicit review qualifiers for that login rather than `@me`.
|
|
An arbitrary typed login is not accepted as current-user proof.
|
|
|
|
App setup and personal authorization use single-use state, PKCE for the user web flow, verified
|
|
installation/user association, signed webhooks, and delivery-ID deduplication. Installation tokens
|
|
are minted with an App JWT, cached in memory until shortly before expiry, and never persisted.
|
|
Personal access/refresh tokens are encrypted in the secret store and refreshed atomically.
|
|
|
|
Existing workspaces receive an explicit `legacy_shared` row on migration so upgrades preserve the
|
|
current behavior. New workspaces do not. Leaving legacy mode is irreversible, and legacy mode is
|
|
not a target architecture: it is a visible compatibility bridge while users reconfigure each
|
|
workspace. This narrowly amends ADR 0030's migration rule for GitHub authentication only.
|
|
Only `legacy_shared` may consult the active host `gh` account, backend `GITHUB_TOKEN`/`GH_TOKEN`, or
|
|
old globally named token secret; managed workspace connections do not use ambient auth as fallback.
|
|
|
|
## Consequences
|
|
|
|
- A different primary GitHub account requires a different workspace. Each workspace still has one
|
|
automation identity; multiple App registrations do not add arbitrary N-account routing inside a
|
|
workspace.
|
|
- A hosted workspace can use an App for organization-controlled automation and a personal user
|
|
token for `My GitHub` and human-attributed actions.
|
|
- An App-only workspace can still create PRs, review, merge, and run watches when permissions allow,
|
|
but GitHub attributes those actions to the App and `My GitHub` remains unavailable.
|
|
- Personal access is intersected with the workspace repository scope and automation-visible
|
|
repositories, so attaching a broad user token cannot expand the workspace trust boundary.
|
|
- Every GitHub service entry point and background record must supply or derive a workspace. Missing
|
|
ownership fails closed instead of falling back to another connection.
|
|
- Health is no longer one install-wide GitHub boolean. Status is workspace-specific and reports
|
|
automation, personal identity, capabilities, and rate limits separately.
|
|
- Executors gain renewable GitHub credentials without receiving long-lived deployment or personal
|
|
secrets.
|
|
- App-backed executor access is provider-enforced per repository. PAT/CLI executor access remains
|
|
bounded by the provider token itself, so agents are part of the workspace automation trust
|
|
boundary even though lease redemption is repository-matched.
|
|
- The GitHub REST/GraphQL token client becomes token-source-neutral; PAT, installation, and user
|
|
tokens share transport behavior but retain distinct principal metadata.
|
|
|
|
## Alternatives Considered
|
|
|
|
### One connection per workspace, with no personal overlay
|
|
|
|
Rejected. A company would have to choose between centrally managed App automation and user-specific
|
|
queries/attribution, or create duplicate workspaces for the same organizational repository set.
|
|
|
|
### Arbitrary multiple connections with per-repository routing
|
|
|
|
Rejected. It requires conflict resolution for watches, cache ownership, mutations, and agent git
|
|
credentials without a demonstrated user need. One automation identity plus an optional personal
|
|
identity covers the confirmed local and hosted scenarios.
|
|
|
|
### Use the App installation as the authenticated user
|
|
|
|
Rejected. GitHub treats an installation as the App principal. It can query explicit login
|
|
qualifiers and perform permitted mutations, but it does not supply authenticated-viewer semantics
|
|
or human attribution.
|
|
|
|
### Inject installation tokens at task launch
|
|
|
|
Rejected. Installation tokens expire after one hour, so long-running tasks would fail. Giving the
|
|
executor the App private key to self-refresh would cross the deployment secret boundary.
|
|
|
|
### Continue using the active `gh` account
|
|
|
|
Rejected. `gh auth switch` changes host-global state and allows one workspace or process to affect
|
|
another. Named account resolution is deterministic and does not mutate the user's CLI configuration.
|
|
|
|
### Immediately disconnect all existing workspaces
|
|
|
|
Rejected. It would break upgrades and background automation without giving operators time to
|
|
configure each workspace. The explicit legacy mode preserves behavior while making the remaining
|
|
migration visible.
|