Problem
When --allow-protocol-fallback is enabled and a dependency has a custom port, _build_repo_url passes the same dep_ref.port to both build_ssh_url and build_https_clone_url. For servers where SSH and HTTPS run on different ports, the cross-protocol fallback produces an incorrect URL.
Concrete scenario: Bitbucket Datacenter
Bitbucket DC defaults: SSH on port 7999, HTTPS on port 7990.
- ssh://git@bitbucket.corp.com:7999/project/repo.git
| Attempt |
URL produced |
Result |
| SSH |
ssh://git@bitbucket.corp.com:7999/project/repo.git |
Correct |
| HTTPS fallback |
https://bitbucket.corp.com:7999/project/repo.git |
Wrong — HTTPS is on 7990, not 7999 |
Other affected scenarios
| Setup |
SSH attempt |
HTTPS fallback |
Result |
| GitLab (SSH:2222, HTTPS:443) |
ssh://host:2222/... ✅ |
https://host:2222/... ❌ |
Hits SSH daemon |
| Same-port server (e.g. Gitea:3000) |
✅ |
✅ |
Works by accident |
Current mitigations
Strict mode (default since #778) prevents this from manifesting for explicit ssh:// and https:// URLs — only the declared protocol is attempted. The bug only surfaces when users explicitly opt in via --allow-protocol-fallback or APM_ALLOW_PROTOCOL_FALLBACK=1.
The test test_https_attempt_preserves_same_port_across_protocols (test_auth_scoping.py:423) codifies "same port across protocols" as intended behavior — which is correct for same-port servers but wrong for the Bitbucket DC scenario.
Root cause
DependencyReference has a single port: Optional[int] field shared across all protocols. There is no ssh_port / https_port distinction. TransportAttempt and TransportSelector have no port awareness — port handling lives entirely in _build_repo_url which blindly passes dep_ref.port to whichever builder it calls.
Design space
The fundamental issue: when crossing protocols, we cannot guess the correct port for the other protocol.
Option A: Warn + multi-port fallback cascade
When dep_ref.port is set and cross-protocol fallback triggers:
- Try with same port (covers same-port servers)
- If that fails, try with default port (covers servers where the other protocol uses default port)
- Emit a warning: "Custom port {port} was specified for {scheme}://; the fallback protocol may use a different port."
Pro: no schema change. Con: doesn't cover BB DC (HTTPS:7990 is neither 7999 nor 443).
Option B: fallback field in object form
- git: ssh://git@bitbucket.corp.com:7999/project/repo.git
fallback: https://bitbucket.corp.com:7990/project/repo.git
ref: v1.0
Pro: fully explicit, covers any URL difference (not just port — also path differences for ADO, proxies, etc.). Con: schema expansion.
Option C: Per-protocol port fields in object form
- git: bitbucket.corp.com/project/repo
ssh_port: 7999
https_port: 7990
Pro: explicit. Con: only addresses port, not other URL differences; requires host field to be meaningful; less general than Option B.
Option D: Status quo + documentation
Strict mode already prevents the problem for explicit URLs. Document that --allow-protocol-fallback with custom ports may produce incorrect fallback URLs, and recommend pinning the protocol that matches the port.
Pro: zero code change. Con: doesn't help users who genuinely need fallback across different ports.
Files involved
src/apm_cli/deps/github_downloader.py — _build_repo_url (line 664), _clone_with_fallback
src/apm_cli/deps/transport_selection.py — TransportSelector.select, TransportAttempt (no port awareness)
src/apm_cli/utils/github_host.py — build_ssh_url, build_https_clone_url
src/apm_cli/models/dependency/reference.py — single port field
Context
Follow-up from PR #665 review. The "same port across protocols" behavior was a deliberate choice in #665 to fail loudly rather than silently hitting a different service. Strict-by-default transport (#778) subsequently removed most of the exposure. This issue tracks the residual gap for the --allow-protocol-fallback path.
Refs: #661, #665, #731, #778
Problem
When
--allow-protocol-fallbackis enabled and a dependency has a custom port,_build_repo_urlpasses the samedep_ref.portto bothbuild_ssh_urlandbuild_https_clone_url. For servers where SSH and HTTPS run on different ports, the cross-protocol fallback produces an incorrect URL.Concrete scenario: Bitbucket Datacenter
Bitbucket DC defaults: SSH on port 7999, HTTPS on port 7990.
- ssh://git@bitbucket.corp.com:7999/project/repo.gitssh://git@bitbucket.corp.com:7999/project/repo.githttps://bitbucket.corp.com:7999/project/repo.gitOther affected scenarios
ssh://host:2222/...✅https://host:2222/...❌Current mitigations
Strict mode (default since #778) prevents this from manifesting for explicit
ssh://andhttps://URLs — only the declared protocol is attempted. The bug only surfaces when users explicitly opt in via--allow-protocol-fallbackorAPM_ALLOW_PROTOCOL_FALLBACK=1.The test
test_https_attempt_preserves_same_port_across_protocols(test_auth_scoping.py:423) codifies "same port across protocols" as intended behavior — which is correct for same-port servers but wrong for the Bitbucket DC scenario.Root cause
DependencyReferencehas a singleport: Optional[int]field shared across all protocols. There is nossh_port/https_portdistinction.TransportAttemptandTransportSelectorhave no port awareness — port handling lives entirely in_build_repo_urlwhich blindly passesdep_ref.portto whichever builder it calls.Design space
The fundamental issue: when crossing protocols, we cannot guess the correct port for the other protocol.
Option A: Warn + multi-port fallback cascade
When
dep_ref.portis set and cross-protocol fallback triggers:Pro: no schema change. Con: doesn't cover BB DC (HTTPS:7990 is neither 7999 nor 443).
Option B:
fallbackfield in object formPro: fully explicit, covers any URL difference (not just port — also path differences for ADO, proxies, etc.). Con: schema expansion.
Option C: Per-protocol port fields in object form
Pro: explicit. Con: only addresses port, not other URL differences; requires
hostfield to be meaningful; less general than Option B.Option D: Status quo + documentation
Strict mode already prevents the problem for explicit URLs. Document that
--allow-protocol-fallbackwith custom ports may produce incorrect fallback URLs, and recommend pinning the protocol that matches the port.Pro: zero code change. Con: doesn't help users who genuinely need fallback across different ports.
Files involved
src/apm_cli/deps/github_downloader.py—_build_repo_url(line 664),_clone_with_fallbacksrc/apm_cli/deps/transport_selection.py—TransportSelector.select,TransportAttempt(no port awareness)src/apm_cli/utils/github_host.py—build_ssh_url,build_https_clone_urlsrc/apm_cli/models/dependency/reference.py— singleportfieldContext
Follow-up from PR #665 review. The "same port across protocols" behavior was a deliberate choice in #665 to fail loudly rather than silently hitting a different service. Strict-by-default transport (#778) subsequently removed most of the exposure. This issue tracks the residual gap for the
--allow-protocol-fallbackpath.Refs: #661, #665, #731, #778