git: ssh and ssh-only mode are ignored by six credential-bearing git legs #535

Open
opened 2026-09-17 00:23:17 +00:00 by hexajon · 0 comments
Owner

What happens

--git-protocol offers three modes: https (token plus HTTPS git, the default), ssh (token plus SSH git), and ssh-only (SSH git, no token stored or required).

Six git legs that contact a remote do not honour ssh or ssh-only:

Leg Call
internal/service/prune/run.go:345 fetch --prune
internal/cmd/pr/checkout.go:46 fetch origin
internal/cmd/pr/conflicts.go:67 fetch origin
internal/cmd/git/tag.go:194 push origin <tag>
internal/service/issue/develop.go:116 push -u origin <branch>
internal/mcp/tools_prs_content.go:218 fetch origin

In ssh mode these use the token over HTTPS, against the mode the user chose. In ssh-only, where no token is stored, they try to resolve credentials and fail, so those six commands do not work in that mode.

pull, fetch, and the legs finish drives do honour the mode. So whether the user's transport choice is respected depends on which command they run.

This is read from the source. I have not yet reproduced it against a live forge.

Where the inconsistency lives

internal/git has five places that build a credential-bearing command through setupAuthCmd. Three of them check the transport first, two do not:

internal/git/auth.go runner checks transport
:224 RunGitAuthDirWithCredentials no
:254 RunGitAuthPassthrough yes, at :240
:295 RunGitAuthDirCaptureStderr yes, at :287
:323 RunGitAuthDirCaptureStderrOnly no
:375 RunGitAuthDirWithStderr yes, at :359

RunGitAuth delegates to :224, so every caller of RunGitAuth inherits the miss. clone is a separate case worth noting: the dispatch resolves the SSH remote by reading remote get-url origin, and during a clone no origin exists yet.

This is the third occurrence

The header of internal/git/auth_ssh_dispatch_test.go records the earlier two rounds. The dispatch first covered only the push chokepoint and RunGitAuthDirWithStderr. Two further legs that finish drives were later found on the HTTPS path and fixed. Each round fixed the cases someone noticed at the time.

No decision record covers this

There is no ADR for git transport selection. 0013 covers credential storage, secure backend against plaintext. 0025 covers forge resolution and host-keyed credentials. Nothing in docs/decisions/ mentions git-protocol, ssh-only, or #294.

So the three modes exist in the code, in a CLI flag, in docs/CLAIMS.md claim 26, and in COMPAT.md, with no record stating what they guarantee. Claim 26 covers API surfaces only; its test does not reach the git transport legs. Each past fix was therefore made against no written contract, which is worth weighing when deciding how to close this.

Acceptance

Stated as behaviour, so the shape of the fix stays open:

  • In ssh and ssh-only, each of the six legs above reaches the remote over the SSH remote form, with no GIT_ASKPASS and no token on the wire.
  • In ssh-only those six commands work rather than failing on missing credentials.
  • https and unset mode are unchanged: askpass injected, no URL override.
  • Whatever clone ends up doing is deliberate and commented, not a side effect.
  • A fourth occurrence is prevented by something automated, not by care.

Related: #294 (the original SSH transport work) and #527 (collapsing the Run* variants), which touches the same code.

## What happens `--git-protocol` offers three modes: `https` (token plus HTTPS git, the default), `ssh` (token plus SSH git), and `ssh-only` (SSH git, no token stored or required). Six git legs that contact a remote do not honour `ssh` or `ssh-only`: | Leg | Call | |---|---| | `internal/service/prune/run.go:345` | `fetch --prune` | | `internal/cmd/pr/checkout.go:46` | `fetch origin` | | `internal/cmd/pr/conflicts.go:67` | `fetch origin` | | `internal/cmd/git/tag.go:194` | `push origin <tag>` | | `internal/service/issue/develop.go:116` | `push -u origin <branch>` | | `internal/mcp/tools_prs_content.go:218` | `fetch origin` | In `ssh` mode these use the token over HTTPS, against the mode the user chose. In `ssh-only`, where no token is stored, they try to resolve credentials and fail, so those six commands do not work in that mode. `pull`, `fetch`, and the legs `finish` drives do honour the mode. So whether the user's transport choice is respected depends on which command they run. This is read from the source. I have not yet reproduced it against a live forge. ## Where the inconsistency lives `internal/git` has five places that build a credential-bearing command through `setupAuthCmd`. Three of them check the transport first, two do not: | `internal/git/auth.go` | runner | checks transport | |---|---|---| | `:224` | `RunGitAuthDirWithCredentials` | no | | `:254` | `RunGitAuthPassthrough` | yes, at `:240` | | `:295` | `RunGitAuthDirCaptureStderr` | yes, at `:287` | | `:323` | `RunGitAuthDirCaptureStderrOnly` | no | | `:375` | `RunGitAuthDirWithStderr` | yes, at `:359` | `RunGitAuth` delegates to `:224`, so every caller of `RunGitAuth` inherits the miss. `clone` is a separate case worth noting: the dispatch resolves the SSH remote by reading `remote get-url origin`, and during a clone no origin exists yet. ## This is the third occurrence The header of `internal/git/auth_ssh_dispatch_test.go` records the earlier two rounds. The dispatch first covered only the push chokepoint and `RunGitAuthDirWithStderr`. Two further legs that `finish` drives were later found on the HTTPS path and fixed. Each round fixed the cases someone noticed at the time. ## No decision record covers this There is no ADR for git transport selection. `0013` covers credential storage, secure backend against plaintext. `0025` covers forge resolution and host-keyed credentials. Nothing in `docs/decisions/` mentions `git-protocol`, `ssh-only`, or #294. So the three modes exist in the code, in a CLI flag, in `docs/CLAIMS.md` claim 26, and in `COMPAT.md`, with no record stating what they guarantee. Claim 26 covers API surfaces only; its test does not reach the git transport legs. Each past fix was therefore made against no written contract, which is worth weighing when deciding how to close this. ## Acceptance Stated as behaviour, so the shape of the fix stays open: - In `ssh` and `ssh-only`, each of the six legs above reaches the remote over the SSH remote form, with no `GIT_ASKPASS` and no token on the wire. - In `ssh-only` those six commands work rather than failing on missing credentials. - `https` and unset mode are unchanged: askpass injected, no URL override. - Whatever `clone` ends up doing is deliberate and commented, not a side effect. - A fourth occurrence is prevented by something automated, not by care. Related: #294 (the original SSH transport work) and #527 (collapsing the `Run*` variants), which touches the same code.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
sixfold-space/madtea#535
No description provided.