Skip to content

fix(forge): detect SaaS forges from the remote host only, and ignore empty push tokens - #1132

Merged
BryanFRD merged 1 commit into
mainfrom
fix/forge-detect-host-only
Sep 22, 2026
Merged

BryanFRD merged 1 commit into
mainfrom
fix/forge-detect-host-only

Conversation

@BryanFRD

Copy link
Copy Markdown
Contributor

Closes #1131. Follow-up to the two review nits on #1124, which merged before they could go in.

Detect SaaS forges from the host only

detect_forge_from_url did url.contains("github.com") and friends, so a repository path such as https://git.acme.com/tools/gitlab.com-exporter.git resolved to GitLab. It now takes the host through extract_host and matches it against the known SaaS hosts, exactly or as a subdomain:

  • git.acme.com/tools/gitlab.com-exporter.git, git@git.acme.com:mirrors/github.com.git: no match, so self-hosted detection (probe or configured forge) takes over as it should
  • notgithub.com, gitlab.com.evil.io: no match, since the suffix has to sit on a label boundary
  • ssh.github.com, ssh://git@gitlab.com:22/...: still resolve

This is the one function behind the push credential (git::auth), the API forge (forge_target), web_base_url and doctor, so the reviewer's "fix it inside detect_forge_from_url" option covers every caller at once. The probe cache is already keyed by host, so it is unaffected.

extract_host now accepts any scheme:// rather than only http(s)://. Before, an ssh:// URL only worked through the user@host: fallback, which needs an @. file:///... still yields no host.

Ignore empty token variables on the push side

GITHUB_TOKEN: ${{ secrets.MISSING }} exports an empty string. token_for_url counted it as present, paid a probe on a self-hosted host, and handed git an empty password. Empty values are now treated as unset, matching forge::resolve_token.

Tests

  • forge (pure, no network): a SaaS domain in the path picks no forge, lookalike hosts are rejected, and a subdomain and an ssh:// URL still resolve. The existing detect_forge_from_url cases pass unchanged.
  • git: all three token variables set to "" give no credential.

Full suite: 2468 passed.

Not changed: extract_repo_slug still locates the slug with contains("github.com/"). It only affects slug parsing on a self-hosted remote whose path contains that string, not which forge or credential is used, so it is left for a separate change if it ever matters.

@BryanFRD
BryanFRD enabled auto-merge (squash) September 22, 2026 08:13

@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.

Detection change is correct: host-only matching with a label-boundary check, exact or subdomain, and the previously-wrong cases are covered by tests. The extract_host widening to any scheme:// is a strict improvement for ssh://host/... with no user, and file:///... still yields no host. Empty-token handling now matches forge::resolve_token.

One qualification on the description rather than the diff: token_for_url ends with github_token.map(...), which hands GITHUB_TOKEN to any host once detection returns None. For the motivating remote https://git.acme.com/tools/github.com-exporter.git, the push credential is therefore unchanged by this PR: the GitHub token still goes to git.acme.com. Pre-existing and out of scope here, but git::auth only actually benefits when the probe comes back GitLab, not in general.

Nit: extract_host does not lowercase, so https://GitHub.com/o/r.git detects nothing. Identical to the old behaviour, since contains was case-sensitive too, but valid_host would be the natural place to normalise if you want it closed.

Comment thread src/forge/mod.rs
.strip_prefix("https://")
.or_else(|| url.strip_prefix("http://"))
{
if let Some((_, rest)) = url.split_once("://") {

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: routing detection through extract_host drops scp-style remotes that omit the user. github.com:owner/repo.git is valid git syntax and used to resolve through contains; the else if branch below requires an @, so it now yields no host, detect_forge_from_url returns None, and the probe fallback gets no host either.

Fix would be in the else if branch rather than here: split on the first : and strip userinfo from the left part, which also handles git@host:path/with@sign that the current rsplit('@') gets wrong. The cost is that a local C:\path would then parse as host C, which valid_host accepts, so it is a real tradeoff. Fine to leave if that remote form is out of scope.

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 #1135: the scp branch now follows git's url_is_local_not_ssh rule (no / before the first :, and not a drive letter), which keeps C:\path local and also fixes the host:path/with@sign case. Lowercasing went into valid_host as suggested. The GITHUB_TOKEN-to-any-host point is filed as #1134.

@BryanFRD
BryanFRD merged commit f7b50cc into main Sep 22, 2026
30 checks passed
@BryanFRD
BryanFRD deleted the fix/forge-detect-host-only branch September 22, 2026 08:17
ferrflow Bot added a commit that referenced this pull request Sep 22, 2026
## [7.23.0] - 2026-09-22

### Features

- feat(docs): ship the package schema in the doc package (#1130)
- feat(schema): add a standalone schema for included package files (#1127)

### Bug Fixes

- fix(forge): detect SaaS forges from the remote host only, and ignore empty push tokens (#1132)
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(forge): the repo path can pick the forge, and an empty token counts as one

1 participant