Skip to content

fix(auth): pick the push credential from the resolved forge, not the host name - #1124

Merged
BryanFRD merged 1 commit into
mainfrom
fix/push-auth-resolved-forge
Sep 22, 2026
Merged

BryanFRD merged 1 commit into
mainfrom
fix/push-auth-resolved-forge

Conversation

@BryanFRD

Copy link
Copy Markdown
Contributor

Closes #1121.

The git push picked its credential by checking whether the remote's host contains gitlab. The API side resolves the forge properly: the configured forge, or in auto mode the URL and then the /api/v4/version probe. On a self-hosted GitLab like git.company.com the two disagreed. API calls authenticated as GitLab, while the push took the GitHub branch: it never read GITLAB_TOKEN, and used x-access-token with GITHUB_TOKEN, or FERRFLOW_TOKEN under the wrong username.

Change

The push now uses the same resolution as the API.

  • git::Remote { name, forge } replaces the bare remote_name: &str on every push and fetch entry point (push, push_tags, force_push_tags, force_push_branch, release_branch_foreign_commit, reset_branch_to_remote, delete_tag_if_unchanged, fetch_tags). Callers build it with Remote::of(&config.workspace), so the configured forge travels with the remote name it belongs to.
  • configure_git_command / token_for_url take that forge:
    • explicit forge in the config: used as is, including over the host name (forge: github on gitlab-mirror.acme.com pushes with GITHUB_TOKEN)
    • auto, host containing gitlab: GitLab, as before and with no network
    • auto, otherwise: detect_forge_with_probe, the function build_forge already uses. Its per-host cache is shared, so a run that has already probed for the API side does not probe again.
  • No probe without a token. When none of FERRFLOW_TOKEN, GITLAB_TOKEN and GITHUB_TOKEN is set, token_for_url returns before resolving anything. A run with no token pays nothing, and local path remotes never reach the probe.

GitHub and GitLab hosts behave exactly as before. The existing tests pass unchanged, apart from receiving ForgeKind::Auto.

Not changed

The issue notes that self-hosted Gitea/Forgejo likely has the same gap. It does: every non-GitLab forge still pushes as x-access-token with GITHUB_TOKEN or FERRFLOW_TOKEN, and GITEA_TOKEN is not read on the push side. Fixing that is a separate behaviour change (which variable, which username), so it is left out. Now that the resolved forge is available in token_for_url, it is a small follow-up.

Tests

With an explicit forge on a host that names no forge, so no network is involved:

Full suite: 2435 passed. The git::tests runtime is unchanged compared with main.

Docs: the forge row in configuration/config-file.md (EN, FR) says the same detection now picks the push credential.

@BryanFRD
BryanFRD enabled auto-merge (squash) September 22, 2026 07:36

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is right: carrying Remote { name, forge } through the push and fetch entry points puts the credential choice on the same resolution the API already uses, and the explicit-forge-beats-host-name ordering in forge_of is the behaviour the config documents. Threading it as a Copy struct instead of adding a parameter everywhere keeps the call sites honest, and the early return before any resolution means a tokenless run and local path remotes still touch no network.

Two nits inline, both in auth.rs, neither blocking. One is a real behaviour change in the auto path: detect_forge_with_probe matches forge names anywhere in the URL, so the repo name can now pick the push credential, which is exactly what a_repository_name_does_not_pick_the_forge was written to prevent. It is narrow (needs a SaaS hostname inside the path of a self-hosted remote) and the API side already behaves that way, so it is consistency rather than a new class of bug, but the one-line fix is in the comment.

Leaving Gitea/Forgejo to a follow-up is the right call, and the "Not changed" section says so clearly enough that nobody will read this as covering it.

Note that CI is still running (Test, Coverage, Build Release Binary, opengrep in progress at review time), so the 2435-passing claim is unverified from here.

Comment thread src/git/auth.rs
fn forge_of(url: &str, configured: ForgeKind) -> Option<ForgeKind> {
match configured {
ForgeKind::Auto if is_gitlab(url) => Some(ForgeKind::Gitlab),
ForgeKind::Auto => crate::forge::detect_forge_with_probe(url),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: detect_forge_with_probe falls through to detect_forge_from_url, which matches github.com / gitlab.com / codeberg.org anywhere in the string, path included. This file deliberately went the other way: is_gitlab matches on host_of(url) only, and a_repository_name_does_not_pick_the_forge asserts a repo name must not select the credential. The auto branch now gives that up.

Concretely, remote https://git.acme.com/tools/gitlab.com-exporter.git with forge: auto resolves to GitLab off the path: with FERRFLOW_TOKEN set the push username flips from x-access-token to oauth2, and with only GITHUB_TOKEN set token_for_url returns None, so the push goes out with no credential at all. The existing test does not catch it because all four of its URLs are on github.com.

Detecting on the host keeps the probe cache key identical and leaves the SaaS short-circuits working:

Suggested change
ForgeKind::Auto => crate::forge::detect_forge_with_probe(url),
ForgeKind::Auto => crate::forge::extract_host(url)
.and_then(|host| crate::forge::detect_forge_with_probe(&format!("https://{host}/"))),

(forge_target in preview.rs passes the full URL too, so the API side has the same quirk. Worth fixing there in the same pass, or inside detect_forge_from_url.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken in #1132 (this PR had already merged).

Comment thread src/git/auth.rs
Comment on lines +45 to +47
let ferrflow_token = std::env::var("FERRFLOW_TOKEN").ok();
let gitlab_token = std::env::var("GITLAB_TOKEN").ok();
let github_token = std::env::var("GITHUB_TOKEN").ok();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: an empty variable counts as present here. GITHUB_TOKEN: ${{ secrets.MISSING }} exports "", which passes the new guard and buys a network probe (up to 3 × 2s on a self-hosted host) for a credential that is then handed to git as an empty password. forge::resolve_token already filters empties; matching it makes the guard mean what it says.

Suggested change
let ferrflow_token = std::env::var("FERRFLOW_TOKEN").ok();
let gitlab_token = std::env::var("GITLAB_TOKEN").ok();
let github_token = std::env::var("GITHUB_TOKEN").ok();
let ferrflow_token = std::env::var("FERRFLOW_TOKEN").ok().filter(|t| !t.is_empty());
let gitlab_token = std::env::var("GITLAB_TOKEN").ok().filter(|t| !t.is_empty());
let github_token = std::env::var("GITHUB_TOKEN").ok().filter(|t| !t.is_empty());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken in #1132 (this PR had already merged).

@BryanFRD
BryanFRD merged commit cfe3ef9 into main Sep 22, 2026
31 checks passed
@BryanFRD
BryanFRD deleted the fix/push-auth-resolved-forge branch September 22, 2026 07:40
ferrflow Bot added a commit that referenced this pull request Sep 22, 2026
## [7.22.1] - 2026-09-22

### Bug Fixes

- fix(release): fall back to github-actions[bot] when no git identity is set (#1128)
- fix(auth): pick the push credential from the resolved forge, not the host name (#1124)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(auth): git push on a self-hosted GitLab without "gitlab" in its host ignores GITLAB_TOKEN

1 participant