Support external Git credential providers for non-GitHub repositories
- Dominant language
- Go
- Stars
- 41
- Forks
- 12
- PR merge metrics
- No merged PRs in 30d
Description
## Background
We want to reuse Cachew for private Git repositories hosted outside GitHub. Authentication for these repositories is not based on a GitHub App, so the existing GitHub-specific integration cannot provide the required credentials.
Cachew should support repository-scoped external credential providers without embedding provider-specific authentication logic in the main process. Providers should return a complete HTTP authorization value so repositories can use schemes such as Basic or Bearer authentication.
## Proposed configuration
Add a repeatable `git-credential-command` block:
```hcl
git-credential-command "private-git" {
command = [
"/usr/local/bin/private-git-credential",
"--provider-option", "value",
]
remotes = [
"https://git.example.com/project/repository",
]
timeout = "5s"
refresh-before = "5m"
}
```
Configured repository URLs are canonicalized and matched exactly. A provider's credential must not be applied to a repository that was not assigned to it.
## Proposed internal interface
```go
type Credential struct {
Authorization string
URLScope string
}
type Provider interface {
Credential(
ctx context.Context,
repositoryURL string,
) (credential Credential, matched bool, err error)
}
```
`Authorization` is the complete HTTP authorization value, for example `Basic ...` or `Bearer ...`. `URLScope` is the canonical repository URL to which it may be applied.
Providers can be composed in order, stopping at the first match. Existing in-process authentication integrations can be adapted to this interface without invoking a plugin binary.
A matched provider failure should fail closed rather than falling back to unauthenticated access. Unmatched repositories retain the existing authentication behavior.
## Plugin interface
A plugin implements credential acquisition for one canonical repository URL:
```go
type CommandResult struct {
Authorization string
ExpiresAt time.Time
}
type CommandHandler interface {
Credential(ctx context.Context, remoteURL string) (CommandResult, error)
}
```
A helper library can expose `ServeCommand`, request/response encoding functions, and an optional CLI entry point for Go implementations. Plugins may be written in any language as long as they implement the protocol.
## Main process and plugin protocol
Cachew executes the configured command directly without a shell and applies the configured timeout. One request and one response are exchanged per invocation.
Cachew writes one newline-terminated JSON object to stdin:
```json
{"version":1,"remote_url":"https://git.example.com/project/repository"}
```
The plugin writes one newline-terminated JSON object to stdout:
```json
{"version":1,"authorization":"Bearer example-token","expires_at":"2026-08-10T12:00:00Z"}
```
Proposed schemas:
```go
const ProtocolVersion = 1
type Request struct {
Version int `json:"version"`
RemoteURL string `json:"remote_url"`
}
type Response struct {
Version int `json:"version"`
Authorization string `json:"authorization"`
ExpiresAt time.Time `json:"expires_at"`
}
```
Protocol requirements:
- `remote_url` is the canonical upstream repository URL.
- `authorization` contains the complete HTTP `Authorization` header value.
- `expires_at` is a future RFC 3339 timestamp.
- Requests and responses are size-limited and strictly decoded.
- Unknown fields, extra JSON values, unsupported versions, invalid authorization values, and expired credentials are rejected.
- Plugin failures, timeouts, unsuccessful exits, and invalid responses fail the matched operation.
## Credential lifecycle
Successful credentials are cached only in memory:
1. Canonicalize the requested repository URL and select its exact provider match.
2. Return an unexpired cached credential when available.
3. Refresh credentials within the configured `refresh-before` window.
4. Coalesce concurrent refreshes for the same provider and repository.
5. Scope the returned authorization to the canonical repository URL.
6. Discard credentials after expiration; never persist them.
## Applying credentials
The authorization should be applied to Git as repository-scoped HTTP configuration through `GIT_CONFIG_*` environment entries:
```text
GIT_CONFIG_KEY_=http..extraHeader
GIT_CONFIG_VALUE_=Authorization:
```
This supports Basic and Bearer credentials without a shell-form credential helper or credentials in Git subprocess arguments. This part is related to #402.
The same provider should authenticate:
- Background clone, fetch, and mirror operations.
- Direct upstream Git HTTP requests.
- Cache-miss and stale-reference fallbacks.
- Push requests.
- Git LFS requests.
## Security properties
- Exact canonical repository matching and URL scoping.
- No shell execution for provider commands.
- No credentials in subprocess command-line arguments.
- In-memory caching only.
- Credentials are never logged or written to stderr.
- Strict protocol and authorization-value validation.
- Fail-closed behavior for matched provider failures.
Contributor guide
Research direction
The issue names no implementation files or tests; start by tracing the existing GitHub authentication and repository-scoped Git HTTP paths, including the work related to #402. Check the background clone, fetch, mirror, push, Git LFS, and cache-miss or stale-reference entry points. Done means all listed operations use exact, scoped credentials with strict protocol validation and fail-closed behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication, backend, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100