Epic: feat(ssh): first-class SSH-only mode - never construct an HTTPS git URL, degrade API-only operations explicitly #187

Closed
opened 2026-07-17 03:53:10 +00:00 by hexajon · 1 comment
hexajon commented 2026-07-17 03:53:10 +00:00 (Migrated from codeberg.org)

Today SSH is a retrofit: auth use-ssh flips an existing remote after the fact, clone builds HTTPS URLs, auth login is token-first, and the docs treat SSH remotes as a you-manage-it side path. The enhancement: make SSH a first-class connection method you can choose from first contact - not a flip away from HTTPS, but a mode where madtea never puts a credential on the git wire and never constructs an HTTPS git URL at all.

The honest boundary

Two channels, different physics:

  • Git transport (clone, fetch, push, the push inside finish) can be 100% SSH. This is the real surface of the mode.
  • The REST API (issues, PRs, merge, labels, releases, most MCP tools) has no SSH surface on Forgejo/Gitea - it is HTTPS + token, and no client-side mode changes that. SSH-only therefore means these operations either degrade with a typed, teaching error, or ride the one genuine SSH avenue that exists (AGit, below).

So the mode is precisely: the token, if present, is used only for API calls over TLS; git never sees a credential; with no token at all, madtea still works fully as a git tool.

Scope sketch

  • Mode selection: a per-repo/global config (e.g. protocol = ssh) set at auth login time ("token + https git", "token + ssh git", or "ssh only, no token") or via a flag. Once set, every code path that builds a git URL builds the SSH form.
  • Clone: madtea clone git@host:owner/repo.git and madtea clone owner/repo under ssh mode clone over SSH natively - no credential resolution, no GIT_ASKPASS, no HTTPS fallback.
  • Onboarding/preflight: an auth-side check that a usable key exists (agent or ~/.ssh), that the forge host is in known_hosts (with the fingerprint-verification steer for first contact - TOFU is the one place SSH is weaker than TLS), and a ssh -T git@host-style verification naming the authenticated user. madtea still does not generate or manage keys.
  • Typed degradation without a token: in ssh-only mode, API-dependent commands fail with a specific error naming the operation, why it needs the HTTPS API, and the exact auth login step that enables it - never a generic "not configured".
  • finish: push leg over SSH; PR-create/merge legs use the token if present, else the degradation error (or AGit, below).
  • AGit avenue (evaluate): Forgejo supports the AGit flow - git push origin HEAD:refs/for/<branch> -o topic=... -o title=... creates a PR entirely over SSH, no API call. Worth evaluating as the PR-creation path for true token-less operation; merge would still be API/web.
  • Docs: setup guide gains an SSH-first path of equal standing with the token path; COMPARISON/COMPAT rows where behavior differs.

Security rationale

  • No credential ever embedded in a remote URL (kills the token-in-git remote -v leak class outright) and no bearer token on the git wire.
  • Keys can be agent-held, passphrase-protected, or hardware-backed; the private key never transits.
  • Counterweights to design for: known_hosts TOFU (hence the preflight), and the fact that madtea guards plaintext token storage (ADR 0013) but cannot guard a passphrase-less key file - the docs should say so plainly.

Acceptance (for the eventual build)

  • In ssh mode, no code path constructs an HTTPS git URL or invokes GIT_ASKPASS (test-pinned)
  • Zero-token ssh-only session: clone/fetch/push/finish-push all work; every API-dependent command produces the typed degradation error
  • Preflight verifies key + known_hosts + authenticated identity, with fingerprint guidance on first contact
  • AGit PR-creation evaluated with a written keep/drop decision
  • Setup docs present SSH-first as a peer of the token path

Related: ADR 0013 (credential storage), ADR 0017 (standard API target), docs/guides/setup.md SSH remotes section, auth use-ssh (the existing flip stays as the migration tool).

Today SSH is a retrofit: `auth use-ssh` flips an existing remote after the fact, `clone` builds HTTPS URLs, `auth login` is token-first, and the docs treat SSH remotes as a you-manage-it side path. The enhancement: make SSH a first-class connection method you can choose from first contact - not a flip away from HTTPS, but a mode where madtea never puts a credential on the git wire and never constructs an HTTPS git URL at all. ## The honest boundary Two channels, different physics: - **Git transport** (clone, fetch, push, the push inside finish) can be 100% SSH. This is the real surface of the mode. - **The REST API** (issues, PRs, merge, labels, releases, most MCP tools) has no SSH surface on Forgejo/Gitea - it is HTTPS + token, and no client-side mode changes that. SSH-only therefore means these operations either degrade with a typed, teaching error, or ride the one genuine SSH avenue that exists (AGit, below). So the mode is precisely: *the token, if present, is used only for API calls over TLS; git never sees a credential; with no token at all, madtea still works fully as a git tool.* ## Scope sketch - **Mode selection**: a per-repo/global config (e.g. `protocol = ssh`) set at `auth login` time ("token + https git", "token + ssh git", or "ssh only, no token") or via a flag. Once set, every code path that builds a git URL builds the SSH form. - **Clone**: `madtea clone git@host:owner/repo.git` and `madtea clone owner/repo` under ssh mode clone over SSH natively - no credential resolution, no GIT_ASKPASS, no HTTPS fallback. - **Onboarding/preflight**: an `auth`-side check that a usable key exists (agent or `~/.ssh`), that the forge host is in `known_hosts` (with the fingerprint-verification steer for first contact - TOFU is the one place SSH is weaker than TLS), and a `ssh -T git@host`-style verification naming the authenticated user. madtea still does not generate or manage keys. - **Typed degradation without a token**: in ssh-only mode, API-dependent commands fail with a specific error naming the operation, why it needs the HTTPS API, and the exact `auth login` step that enables it - never a generic "not configured". - **finish**: push leg over SSH; PR-create/merge legs use the token if present, else the degradation error (or AGit, below). - **AGit avenue (evaluate)**: Forgejo supports the AGit flow - `git push origin HEAD:refs/for/<branch> -o topic=... -o title=...` creates a PR entirely over SSH, no API call. Worth evaluating as the PR-creation path for true token-less operation; merge would still be API/web. - **Docs**: setup guide gains an SSH-first path of equal standing with the token path; COMPARISON/COMPAT rows where behavior differs. ## Security rationale - No credential ever embedded in a remote URL (kills the token-in-`git remote -v` leak class outright) and no bearer token on the git wire. - Keys can be agent-held, passphrase-protected, or hardware-backed; the private key never transits. - Counterweights to design for: `known_hosts` TOFU (hence the preflight), and the fact that madtea guards plaintext *token* storage (ADR 0013) but cannot guard a passphrase-less key file - the docs should say so plainly. ## Acceptance (for the eventual build) - [ ] In ssh mode, no code path constructs an HTTPS git URL or invokes GIT_ASKPASS (test-pinned) - [ ] Zero-token ssh-only session: clone/fetch/push/finish-push all work; every API-dependent command produces the typed degradation error - [ ] Preflight verifies key + known_hosts + authenticated identity, with fingerprint guidance on first contact - [ ] AGit PR-creation evaluated with a written keep/drop decision - [ ] Setup docs present SSH-first as a peer of the token path Related: ADR 0013 (credential storage), ADR 0017 (standard API target), `docs/guides/setup.md` SSH remotes section, `auth use-ssh` (the existing flip stays as the migration tool). <!-- madtea:section:deps-children --> - [ ] #292 - [ ] #293 - [ ] #294 - [ ] #295 - [ ] #296 - [ ] #298 - [ ] #299 <!-- /madtea:section:deps-children -->
hexajon commented 2026-07-22 18:40:26 +00:00 (Migrated from codeberg.org)

SSH-only mode has landed end to end. All the slices are merged:

  • #292 - protocol mode selection at login (https / ssh / ssh-only), the config key everything else reads.
  • #293 - native SSH clone: shorthand builds the git@host:owner/repo.git form and explicit SSH URLs clone as first contact, no HTTPS URL construction, no credential resolution (PR #345).
  • #294 - finish and the push/pull/fetch legs run over SSH with no credential on the git wire; the API legs use the token when present (PR #346).
  • #295 - one typed degradation error for API-dependent commands in ssh-only, naming the operation and the remedy (PR #341).
  • #296 - auth-side SSH preflight: key presence, known_hosts with the first-contact fingerprint steer, and an identity probe that names the authenticated user (PR #333).
  • #298 - AGit spike: keep, scoped to bare create/update.
  • #299 - SSH-first setup path documented as a peer of the token path, with the degradation behavior and the honest security notes (PR #348).
  • #327 - AGit PR creation in ssh-only: push refs/for/ with a deterministic topic, closes-linkage that honors the dependency safety check, and metadata that degrades rather than lying (PR #347).

The result is the mode I wanted: with no token at all, madtea is still a full git tool over SSH - clone, push, finish, and even PR creation via AGit - and every operation that genuinely needs the REST API says so plainly instead of failing obscurely. No credential ever touches the git wire or a remote URL. The one honest counterweight, documented in the setup guide, is that known_hosts trust-on-first-use is where SSH is weaker than TLS, and madtea guards token storage but cannot guard a passphrase-less key file.

Closing the epic - every child is done.

SSH-only mode has landed end to end. All the slices are merged: - #292 - protocol mode selection at login (https / ssh / ssh-only), the config key everything else reads. - #293 - native SSH clone: shorthand builds the git@host:owner/repo.git form and explicit SSH URLs clone as first contact, no HTTPS URL construction, no credential resolution (PR #345). - #294 - finish and the push/pull/fetch legs run over SSH with no credential on the git wire; the API legs use the token when present (PR #346). - #295 - one typed degradation error for API-dependent commands in ssh-only, naming the operation and the remedy (PR #341). - #296 - auth-side SSH preflight: key presence, known_hosts with the first-contact fingerprint steer, and an identity probe that names the authenticated user (PR #333). - #298 - AGit spike: keep, scoped to bare create/update. - #299 - SSH-first setup path documented as a peer of the token path, with the degradation behavior and the honest security notes (PR #348). - #327 - AGit PR creation in ssh-only: push refs/for/<base> with a deterministic topic, closes-linkage that honors the dependency safety check, and metadata that degrades rather than lying (PR #347). The result is the mode I wanted: with no token at all, madtea is still a full git tool over SSH - clone, push, finish, and even PR creation via AGit - and every operation that genuinely needs the REST API says so plainly instead of failing obscurely. No credential ever touches the git wire or a remote URL. The one honest counterweight, documented in the setup guide, is that known_hosts trust-on-first-use is where SSH is weaker than TLS, and madtea guards token storage but cannot guard a passphrase-less key file. Closing the epic - every child is done.
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.

Dependencies

No dependencies set.

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