Support external Git credential providers for non-GitHub repositories

Open
#404 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
38/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
go

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.

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:

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

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:

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:

{"version":1,"remote_url":"https://git.example.com/project/repository"}

The plugin writes one newline-terminated JSON object to stdout:

{"version":1,"authorization":"Bearer example-token","expires_at":"2026-08-10T12:00:00Z"}

Proposed schemas:

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:

GIT_CONFIG_KEY_<n>=http.<canonical-repository-url>.extraHeader
GIT_CONFIG_VALUE_<n>=Authorization: <provider-value>

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.
Dominant language
Go
Stars
41
Forks
14
Avg merge
23h 22m
Merged PRs (30d)
2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from block/cachew

All issues in block/cachew

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.