Files
kandev/docs/decisions/2026-07-31-provider-neutral-git-credential-broker.md
T

72 lines
3.9 KiB
Markdown

# ADR-2026-07-31-provider-neutral-git-credential-broker: Provider-Neutral Git Credential Broker
**Status:** accepted
**Date:** 2026-07-31
**Area:** backend, protocol, security
## Context
GitHub has lease mechanics in a GitHub-specific broker, while other paths inject raw
credentials into executor wiring. A repository-provider plugin needs clone, fetch, and
push credentials without persisting tokens in URLs, task state, environment variables,
logs, or process arguments. Self-managed repository URLs must retain exact host, path,
and context path.
## Decision
Kandev extracts generic lease issuance, redemption, authorization, expiry, and
revocation into `internal/gitcredentials/`. A composite resolver preserves current
GitHub behavior through an adapter and adds an optional plugin `ResolveGitCredential`
RPC. The plugin receives host-verified plugin/provider/workspace/task/session/
repository/exact-host/path scope and returns a generic short-lived credential shape;
provider-specific username/token formatting remains plugin-owned.
Credential-capable plugins also implement `GetGitCredentialBinding` for the same exact
scope. It returns an opaque non-secret connection generation. Kandev stores that value
when issuing a lease and compares it before and after each secret redemption; an empty,
unsupported, rotated, or disconnected binding revokes the lease. The host never
resolves a secret merely to inspect its generation.
Executors receive opaque short-lived helper leases. Redeeming a lease re-resolves
against the live provider, so OAuth refresh does not require a task restart. Leases are
scoped to exact workspace, task, session, repository, host, and path; they revoke on
teardown, plugin disable/error/uninstall, credential-generation change, and connection
reset. Disabling, failing, or uninstalling a plugin immediately revokes every lease for
each provider it declared; later redemption does not wait for expiry. Host matching is
normalized, while repository paths remain case-sensitive and exact. The helper carries
the requested path without appending or removing `.git`. Remote executor transport uses
HTTPS.
Host-local initial repository materialization uses the same verified scope before it
creates a checkout. The orchestrator and workspace-source materializer must carry the
canonical task ID, active session ID, and persisted repository ID explicitly into the
clone credential request; the cloner must not reconstruct them from provider data or
accept browser-selected identifiers. A plugin-provider clone without that complete
scope fails closed. GitHub's legacy workspace credential adapter remains compatible,
but generic plugin providers never receive a weaker initial-clone request than later
fetch/push lease redemption.
`RepositoryCloneURL` remains authoritative and is never reconstructed from provider
fragments.
Before creating a worktree from an existing plugin-provider checkout, the orchestrator
performs a strict origin refresh through that same exact scope. It then marks the
in-memory launch as already refreshed; local and worktree preparers consume local and
`origin/*` refs without issuing a second unauthenticated fetch or pull. A failed scoped
refresh fails the launch closed.
## Consequences
New repository providers can participate in Git operations without host credential
branches or secret leakage. The host must carry stricter lease scope and lifecycle
checks, and plugins must resolve credentials promptly while handling rotation. GitLab
is not migrated as part of this decision.
## Alternatives Considered
- Put provider tokens in clone URLs or executor environment variables: rejected
because both spread durable or observable secrets.
- Add a Bitbucket broker beside GitHub's: rejected because it repeats a cross-provider
security boundary.
- Reconstruct clone URLs from provider fields: rejected because Data Center context
paths and configured origins would be lost or silently changed.