Skip to content

fix(core): match SSH URL host generically, not just github.com (closes #1614)#1656

Merged
Wirasm merged 1 commit into
coleam00:devfrom
truffle-dev:fix/clone-ssh-host-alias-1614
May 13, 2026
Merged

fix(core): match SSH URL host generically, not just github.com (closes #1614)#1656
Wirasm merged 1 commit into
coleam00:devfrom
truffle-dev:fix/clone-ssh-host-alias-1614

Conversation

@truffle-dev

@truffle-dev truffle-dev commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: normalizeRepoUrl() and registerRepository() only matched git@github.com: literally. SSH URLs with any other host (custom aliases, GHE, Gitea, GitLab, Bitbucket) were left as git@<host>:owner/repo, producing workspace paths with literal git@<host>: segments. On Windows the colon makes mkdir fail with ENOTDIR; on Unix the owner extraction is malformed.
  • Why it matters: any user with a non-default SSH host alias (single-machine multi-account is the common case) cannot clone or register a repository at all on Windows, and gets a git@host:owner directory on Unix.
  • What changed: two-site swap of cleaned.startsWith('git@github.com:') for the SCP-style regex /^git@([^:]+):(.+)$/. The github.com path is identical; non-github SSH hosts now convert to path-safe HTTPS.
  • What did not change: GH_TOKEN injection still gates on workingUrl.includes('github.com') so non-github HTTPS URLs are not authenticated with a GH token. Local-path and HTTPS code paths are untouched.

UX Journey

Before

User remote                       Archon                        Result
───────────                       ──────                        ──────
git@github.com:o/r          ───▶  startsWith match  ───▶  https://github.com/o/r  ✓
git@gh-work:o/r             ───▶  startsWith MISS   ───▶  git@gh-work:o/r         ✗
git@gitlab.company.com:t/p  ───▶  startsWith MISS   ───▶  git@gitlab.company.com:t/p  ✗
                                                          (workspace mkdir fails on Windows
                                                           with ENOTDIR; owner is malformed
                                                           on Unix)

After

User remote                       Archon                        Result
───────────                       ──────                        ──────
git@github.com:o/r          ───▶  /^git@([^:]+):(.+)$/  ───▶  https://github.com/o/r        ✓
git@gh-work:o/r             ───▶  /^git@([^:]+):(.+)$/  ───▶  https://gh-work/o/r           ✓
git@gitlab.company.com:t/p  ───▶  /^git@([^:]+):(.+)$/  ───▶  https://gitlab.company.com/t/p  ✓

Architecture Diagram

Before

   ┌─────────────────────────────────────┐
   │  cloneRepository / registerRepo     │
   │                                     │
   │  ┌─────────────────────────────┐    │
   │  │ normalizeRepoUrl  (clone)   │    │
   │  │  if startsWith('git@        │    │
   │  │     github.com:')           │    │
   │  │    replace literal prefix   │    │
   │  └─────────────────────────────┘    │
   │                                     │
   │  ┌─────────────────────────────┐    │
   │  │ registerRepository (reg)    │    │
   │  │  if startsWith('git@        │    │
   │  │     github.com:')           │    │
   │  │    replace literal prefix   │    │
   │  └─────────────────────────────┘    │
   └─────────────────────────────────────┘

After

   ┌─────────────────────────────────────┐
   │  cloneRepository / registerRepo     │
   │                                     │
   │  ┌─────────────────────────────┐    │
   │  │ normalizeRepoUrl  (clone)   │    │
   │  │  [~ /^git@([^:]+):(.+)$/]   │    │
   │  │    rebuild https://$1/$2    │    │
   │  └─────────────────────────────┘    │
   │                                     │
   │  ┌─────────────────────────────┐    │
   │  │ registerRepository (reg)    │    │
   │  │  [~ /^git@([^:]+):(.+)$/]   │    │
   │  │    rebuild https://$1/$2    │    │
   │  └─────────────────────────────┘    │
   └─────────────────────────────────────┘

Connection inventory:

From To Status Notes
cloneRepository normalizeRepoUrl unchanged same call shape
registerRepository (inline ssh→https) modified regex instead of literal
normalizeRepoUrl (inline ssh→https) modified regex instead of literal
cloneRepository GH_TOKEN injection unchanged still gates on includes('github.com')

Label Snapshot

  • Risk: risk: low
  • Size: size: XS
  • Scope: core
  • Module: core:handlers/clone

Change Metadata

  • Change type: bug
  • Primary scope: core

Linked Issue

Validation Evidence (required)

bun test packages/core/src/handlers/clone.test.ts
# 46 pass, 0 fail, 75 expect() calls, 449ms

bun run type-check
# all packages: Exited with code 0

bun x prettier --check .
# All matched files use Prettier code style!

bun x eslint --max-warnings 0 --no-warn-ignored \
  packages/core/src/handlers/clone.ts \
  packages/core/src/handlers/clone.test.ts
# (clean)

The full-monorepo bun run lint OOMs the Node ESLint process on this branch and on a clean checkout of upstream/main (pre-existing on this machine), so it is run scoped to the two touched files. The same OOM also blocks running the full test suite to completion; the file-scoped clone.test.ts run passes all 46 tests including the two new ones for custom-alias and non-github SSH hosts.

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No (GH_TOKEN injection logic is untouched; still gates on workingUrl.includes('github.com'), so a git@gh-work:o/r URL converted to https://gh-work/o/r will not receive a GitHub token; a custom alias whose name contains the substring github.com would, but that is pre-existing behavior in this file)
  • File system access scope changed? No

Compatibility / Migration

  • Backward compatible? Yes. The github.com case produces the byte-identical URL https://github.com/owner/repo it did before.
  • Config/env changes? No
  • Database migration needed? No

Human Verification (required)

Verified scenarios in clone.test.ts:

  • git@github.com:owner/repo.githttps://github.com/owner/repo (regression test, unchanged behavior)
  • git@github.com:owner/repo.git with GH_TOKEN=ghp_… → token-injected HTTPS URL (regression test, unchanged behavior)
  • git@gh-work:owner/repo.githttps://gh-work/owner/repo (new test)
  • git@gitlab.example.com:team/project.githttps://gitlab.example.com/team/project (new test)

Edge cases checked:

  • Existing SSH-not-converted assertions (not.toContain('git@')) still hold under the regex.
  • Existing GH_TOKEN test for git@github.com: still hits the github.com substring guard.

What was not verified:

  • A Windows machine (the original reporter's failure surface). The path-safety claim is structural: the only character the regex emits in the host slot is [^:], so the output cannot contain a colon.
  • IPv6-literal hosts in SSH URLs (git@[::1]:owner/repo). Out of scope and not part of any existing call site.

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: any caller of cloneRepository(url) or registerRepository(localPath) whose remote is an SSH URL. github.com remotes are byte-identical.
  • Potential unintended effects: a custom SSH alias whose name contains the literal substring github.com (e.g. git@github.com-work:o/r) would now produce https://github.com-work/o/r, which on the existing workingUrl.includes('github.com') check would still trigger GH_TOKEN injection. This is the same loose-substring behavior the file has today on HTTPS URLs; not a regression.
  • Guardrails/monitoring: two unit tests guard the new shapes; existing tests guard the github.com shape.

Rollback Plan (required)

Risks and Mitigations

  • Risk: a remote URL that begins with the literal characters git@ but is not an SSH URL (e.g. a username happens to be git in a non-SSH context) would match the regex. Mitigation: SCP-style SSH grammar is <user>@<host>:<path>, and the git@ prefix plus colon plus non-empty path is the canonical form for git SSH remotes; the previous code also matched only on the git@ prefix, so the input class accepted here is strictly a superset of what the previous code accepted.

Summary by CodeRabbit

  • New Features
    • Extended SSH repository URL support for custom Git host aliases and non-GitHub Git services. SSH URLs are now automatically converted to HTTPS format across repository operations.

Review Change Stack

The SSH-to-HTTPS converter in normalizeRepoUrl() and registerRepository()
only matched `git@github.com:` literally. Custom SSH host aliases, GitHub
Enterprise, Gitea, GitLab, and Bitbucket SSH URLs were left unchanged,
which produced workspace paths containing literal `git@<host>:` segments.
On Windows the colon makes mkdir fail with ENOTDIR; on Unix the owner
extraction is malformed.

Replace the literal-host check with an SCP-style regex
`/^git@([^:]+):(.+)$/` at both call sites. The github.com case still
converts identically; new hosts (custom aliases, GHE, GitLab, Bitbucket)
now produce path-safe HTTPS URLs.

Closes coleam00#1614.
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Repository clone handlers now detect and convert generic SSH-form URLs (git@<host>:<path>) to HTTPS equivalents, replacing the prior GitHub-only behavior. Both normalizeRepoUrl and registerRepository apply this transformation, enabling workspace registration with custom SSH aliases and non-GitHub Git hosts. Test coverage validates the conversion for both cases.

Changes

SSH URL Normalization for Repository Registration

Layer / File(s) Summary
Generic SSH→HTTPS conversion in repository handlers
packages/core/src/handlers/clone.ts
normalizeRepoUrl and registerRepository replace hardcoded git@github.com: checks with regex pattern matching any git@<host>:<path> SSH URL and converting it to https://<host>/<path> format.
SSH URL conversion test coverage
packages/core/src/handlers/clone.test.ts
Two new tests under the "SSH URL conversion" suite verify conversion of SSH URLs with custom host aliases and non-GitHub hosts, confirming the resulting clone URL uses HTTPS form without the git@ prefix.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A hop, a skip, through git's SSH dance,
No more GitHub-only stance!
Custom hosts and aliases play,
HTTPS converts the way,
Colons flee from Windows' glance!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: replacing a hardcoded github.com SSH check with generic host matching via regex. It references the specific issue (#1614) and is directly related to the code modifications.
Description check ✅ Passed The description is comprehensive and follows the template well, including problem/why it matters/what changed, UX journey before/after, architecture diagrams, validation evidence with test results, security impact analysis, compatibility confirmation, human verification with specific test cases, and rollback plan.
Linked Issues check ✅ Passed The PR fully addresses issue #1614 requirements: replaces hardcoded git@github.com checks with generic SCP-style regex /^git@([^:]+):(.+)$/ in both normalizeRepoUrl() and registerRepository(), converting any git@: to https:///, and adds test coverage for custom-alias and non-github SSH hosts.
Out of Scope Changes check ✅ Passed All changes are directly within scope: the regex replacement in two locations within clone.ts and corresponding test additions in clone.test.ts. No unrelated modifications to GH_TOKEN logic, other files, or unrelated functionality are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/handlers/clone.test.ts (1)

253-285: ⚡ Quick win

Add matching non-github/custom-host coverage for registerRepository too.

Great additions for cloneRepository; please add one analogous test for registerRepository (remote origin as git@<host>:<owner>/<repo>.git) so both changed normalization call sites are protected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/handlers/clone.test.ts` around lines 253 - 285, Add a new
unit test in packages/core/src/handlers/clone.test.ts that mirrors the two
SSH->HTTPS cases for cloneRepository but calls registerRepository instead;
specifically mock createCodebase (mockCreateCodebase) to return a Codebase with
repository_url set to the expected HTTPS form, call
registerRepository('git@host:owner/repo.git') for both a custom-host alias case
and a non-github host case, then inspect spyExecFileAsync.mock.calls to find the
git command that sets origin (the call where args[0] === 'git' and args[1]?.[0]
=== 'remote' or similar) and assert the remote URL contains the HTTPS URL (e.g.,
'https://gh-work/owner/repo' or 'https://gitlab.example.com/team/project') and
does not contain 'git@'; keep the test structure and mocks consistent with
existing cloneRepository tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/handlers/clone.ts`:
- Around line 178-180: The current SSH-to-HTTPS normalization uses
/^git@([^:]+):(.+)$/.exec(workingUrl) and rewrites workingUrl, which allows
lookalike hosts (e.g., github.com.evil) to pass a later
workingUrl.includes('github.com') check and receive GH_TOKEN; to fix, extract
the SSH host (sshMatch[1]) into a variable and either only rewrite to HTTPS when
that host is exactly 'github.com' or replace the later includes('github.com')
gate with a strict hostname check using URL parsing (e.g., new
URL(workingUrl).hostname === 'github.com'); update the code around the sshMatch
handling and the GH_TOKEN conditional to use this exact-host check so tokens are
only sent to the real github.com.

---

Nitpick comments:
In `@packages/core/src/handlers/clone.test.ts`:
- Around line 253-285: Add a new unit test in
packages/core/src/handlers/clone.test.ts that mirrors the two SSH->HTTPS cases
for cloneRepository but calls registerRepository instead; specifically mock
createCodebase (mockCreateCodebase) to return a Codebase with repository_url set
to the expected HTTPS form, call registerRepository('git@host:owner/repo.git')
for both a custom-host alias case and a non-github host case, then inspect
spyExecFileAsync.mock.calls to find the git command that sets origin (the call
where args[0] === 'git' and args[1]?.[0] === 'remote' or similar) and assert the
remote URL contains the HTTPS URL (e.g., 'https://gh-work/owner/repo' or
'https://gitlab.example.com/team/project') and does not contain 'git@'; keep the
test structure and mocks consistent with existing cloneRepository tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5cc34aab-64ba-4d55-b1f9-b349abcbc595

📥 Commits

Reviewing files that changed from the base of the PR and between dffefd6 and b511fe4.

📒 Files selected for processing (2)
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts

Comment on lines +178 to +180
const sshMatch = /^git@([^:]+):(.+)$/.exec(workingUrl);
if (sshMatch) {
workingUrl = `https://${sshMatch[1]}/${sshMatch[2]}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent GH_TOKEN leakage to lookalike hosts after generic SSH normalization.

Line 178-Line 180 now normalizes any git@<host>:... to HTTPS, which makes the later workingUrl.includes('github.com') gate unsafe. A host like git@github.com.evil:org/repo.git becomes https://github.com.evil/... and will receive GH_TOKEN.

Suggested fix
-  if (ghToken && workingUrl.includes('github.com')) {
+  let isGithubHost = false;
+  try {
+    const parsed = new URL(workingUrl.startsWith('http') ? workingUrl : `https://${workingUrl}`);
+    isGithubHost = parsed.hostname === 'github.com';
+  } catch {
+    isGithubHost = false;
+  }
+
+  if (ghToken && isGithubHost) {
🧰 Tools
🪛 OpenGrep (1.20.0)

[ERROR] 178-178: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/handlers/clone.ts` around lines 178 - 180, The current
SSH-to-HTTPS normalization uses /^git@([^:]+):(.+)$/.exec(workingUrl) and
rewrites workingUrl, which allows lookalike hosts (e.g., github.com.evil) to
pass a later workingUrl.includes('github.com') check and receive GH_TOKEN; to
fix, extract the SSH host (sshMatch[1]) into a variable and either only rewrite
to HTTPS when that host is exactly 'github.com' or replace the later
includes('github.com') gate with a strict hostname check using URL parsing
(e.g., new URL(workingUrl).hostname === 'github.com'); update the code around
the sshMatch handling and the GH_TOKEN conditional to use this exact-host check
so tokens are only sent to the real github.com.

@Wirasm

Wirasm commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Review Summary

Verdict: ready-to-merge

This is a clean, well-scoped bug fix. The PR replaces hardcoded git@github.com: string matching with a generic SCP-style regex that properly handles SSH URLs from any host (custom aliases, GHE, Gitea, GitLab, Bitbucket). Tests are thorough, the PR body is comprehensive, and the implementation follows codebase conventions. No blocking issues.

Blocking issues

None.

Suggested fixes

None.

Minor / nice-to-have

None.

Compliments

  • Targeted and focused: only two locations modified, minimal blast radius
  • Thorough validation: 46 tests passing, with coverage for custom-host aliases and non-github hosts
  • Well-documented PR body: complete template with UX Journey, Architecture Diagram, and Security Impact sections

Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review.

@Wirasm Wirasm merged commit 4423dad into coleam00:dev May 13, 2026
4 checks passed
@coleam00 coleam00 mentioned this pull request May 14, 2026
cropse pushed a commit to cropse/Archon that referenced this pull request May 19, 2026
…m00#1656)

The SSH-to-HTTPS converter in normalizeRepoUrl() and registerRepository()
only matched `git@github.com:` literally. Custom SSH host aliases, GitHub
Enterprise, Gitea, GitLab, and Bitbucket SSH URLs were left unchanged,
which produced workspace paths containing literal `git@<host>:` segments.
On Windows the colon makes mkdir fail with ENOTDIR; on Unix the owner
extraction is malformed.

Replace the literal-host check with an SCP-style regex
`/^git@([^:]+):(.+)$/` at both call sites. The github.com case still
converts identically; new hosts (custom aliases, GHE, GitLab, Bitbucket)
now produce path-safe HTTPS URLs.

Closes coleam00#1614.
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.

Bug: SSH URLs with custom host aliases fail workspace registration on Windows

2 participants