You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
APM today does not distinguish between the user expressed a transport preference and the user expressed none. The same _clone_with_fallback chain (HTTPS-with-token -> SSH -> HTTPS-anonymous) runs for both cases. This is too aggressive for explicit URLs (induced protocol downgrade across trust roots) and not aggressive enough for shorthand (SSH never tried even when configured).
A two-line contract closes both the security gap and the DX gap:
If the user wrote a scheme, honor it. If they didn't, APM may pick - and must say what it picked.
Relationship to existing issues and PRs
This issue subsumes the user-facing manifestations of two open issues and complements (does not supersede) one in-flight PR.
Symptom subsumed. PR #665 already fixes the parsing bug (port preservation). This issue covers the deeper request: do not try HTTPS when the user explicitly wrote ssh://.
#665 should still merge as-is. It is strictly an improvement. This issue picks up where #665 leaves off.
#328 (Feature request: support SSH shorthand for private repos)
Symptom subsumed. Resolved by the "shorthand" branch of the contract: when no scheme is given, APM may try SSH (honoring git config insteadOf, env override, or --ssh flag).
The verbose git: object format workaround documented in #328 stays valid. This issue makes it unnecessary.
#700 (feat: support allow-insecure HTTP dependencies)
Complementary, NOT superseded. Different axis: #700 governs HTTP-vs-HTTPS (is insecure transport allowed at all). This issue governs SSH-vs-HTTPS (which secure transport to use). Both should ship; #700 first.
This issue intentionally adopts #700's design pattern: per-dep field in apm.yml + per-invocation CLI flag. Consistency between the two will make the supply-chain story coherent.
The general theme
APM today conflates "convenient package-manager UX" with "trustworthy supply-chain transport." Those need separable contracts.
When the user writes shorthand, APM is acting as a convenience CLI and is allowed to be helpful. When the user writes an explicit URL with scheme/host/port, APM is acting as a supply-chain pin and must honor it. Today the same code path serves both. Every other mainstream package manager already separates these (npm, cargo, pip git+ssh vs git+https, go, bundler).
The contract: Transport Selection v1
User input form
APM behavior
owner/repo[/path] shorthand
APM may pick. Order: APM_GIT_PROTOCOL env -> --ssh/--https flag -> resolved per-host token (HTTPS-with-token) -> git config url.<base>.insteadOf rewrite -> SSH -> HTTPS-anonymous. Diagnostic line names what was tried.
https://... or http://... URL
HTTPS only (HTTP gated by #700's --allow-insecure). Token then anonymous, both HTTPS. No SSH attempt.
ssh://... URL or git@host:... SCP shorthand
SSH only. No HTTPS attempt. Loud error naming the SSH URL on failure.
git: <any URL> object form
Honor scheme exactly per the rows above.
Optional escape hatch
APM_ALLOW_PROTOCOL_FALLBACK=1 (off by default) restores today's permissive behavior for orgs that depend on it during migration.
Why this matters (panel verdict)
supply-chain-security-expert: Silent cross-protocol fallback is a downgrade-by-induced-failure surface. SSH and HTTPS are different trust roots (known_hosts fingerprint vs system CA + credential helper) with different identities (SSH key vs PAT vs cached helper) and different revocation lifecycles. An attacker who can DoS or interrupt SSH negotiation can force APM into the HTTPS path, where ambient credentials may successfully pull a different repo. Honoring explicit scheme is required, not a nice-to-have.
auth-expert:AuthResolver already classifies hosts and resolves a per-host token. The current fallback chain bypasses that classification at clone time: when Method 1 fails for unrelated reasons (e.g. transient 502), Methods 2 and 3 retry with completely different auth surfaces and may "succeed" against the wrong identity. Honor-scheme turns the resolver from a suggestion back into a decision.
devx-ux-expert: Both #661 and #328 are, at root, the same UX bug: the error you get does not name the thing that failed. #661: user says SSH, error says HTTPS. #328: user says shorthand, error says "not accessible" with no hint that SSH was an option. The contract above plus a one-line diagnostic ("APM picked HTTPS for shorthand 'owner/repo'; SSH not attempted because no SSH key was detected. Override with --ssh or APM_GIT_PROTOCOL=ssh.") closes the experienced part of both reports.
apm-ceo: "Honor user transport, never silently downgrade" is a one-line positioning sentence to enterprise security teams auditing APM. They have npm fatigue. We should lead with it. Costs us one PR; pays back across every "but my SSH works fine in plain git" report we will get as APM grows in self-hosted Bitbucket and GitLab shops.
Branch _clone_with_fallback (src/apm_cli/deps/github_downloader.py:630) on explicit_scheme:
ssh -> SSH only.
https/http -> HTTPS only (HTTP additionally gated by allow_insecure).
None (shorthand) -> ordered chain per the contract.
Read git config --get-urlmatch url.<base>.insteadOf <url> once per install; honor rewrites for the shorthand branch.
Add --ssh / --https to apm install and apm add. Add APM_GIT_PROTOCOL env. Add APM_ALLOW_PROTOCOL_FALLBACK escape hatch.
Diagnostic improvements per devx-ux-expert.
Tests: explicit scheme honored on success and on failure (no silent fallback); shorthand picks heuristically; env and flag overrides; insteadOf honored; escape hatch restores legacy behavior.
CHANGELOG: under both Security (no silent downgrade) and Changed (shorthand may now try SSH).
Acceptance criteria
Explicit ssh:// URL never causes an HTTPS clone attempt.
Explicit https:// URL never causes an SSH clone attempt.
Shorthand owner/repo tries SSH when git config insteadOf rewrites https://github.com/ to git@github.com:, or when --ssh/APM_GIT_PROTOCOL=ssh is set.
Error messages name the exact attempted URL and protocol.
Transport Selection v1: honor user-specified transport, no silent downgrade
Blocked by
port/explicit-scheme parsing this issue builds on. Merge first to avoid rebase pain.Summary
APM today does not distinguish between the user expressed a transport preference and the user expressed none. The same
_clone_with_fallbackchain (HTTPS-with-token -> SSH -> HTTPS-anonymous) runs for both cases. This is too aggressive for explicit URLs (induced protocol downgrade across trust roots) and not aggressive enough for shorthand (SSH never tried even when configured).A two-line contract closes both the security gap and the DX gap:
Relationship to existing issues and PRs
This issue subsumes the user-facing manifestations of two open issues and complements (does not supersede) one in-flight PR.
[BUG] Ignores ssh://, tries https)ssh://.Feature request: support SSH shorthand for private repos)git config insteadOf, env override, or--sshflag).git:object format workaround documented in #328 stays valid. This issue makes it unnecessary.feat: support allow-insecure HTTP dependencies)apm.yml+ per-invocation CLI flag. Consistency between the two will make the supply-chain story coherent.The general theme
When the user writes shorthand, APM is acting as a convenience CLI and is allowed to be helpful. When the user writes an explicit URL with scheme/host/port, APM is acting as a supply-chain pin and must honor it. Today the same code path serves both. Every other mainstream package manager already separates these (npm, cargo, pip
git+sshvsgit+https, go, bundler).The contract: Transport Selection v1
owner/repo[/path]shorthandAPM_GIT_PROTOCOLenv ->--ssh/--httpsflag -> resolved per-host token (HTTPS-with-token) ->git config url.<base>.insteadOfrewrite -> SSH -> HTTPS-anonymous. Diagnostic line names what was tried.https://...orhttp://...URL--allow-insecure). Token then anonymous, both HTTPS. No SSH attempt.ssh://...URL orgit@host:...SCP shorthandgit: <any URL>object formAPM_ALLOW_PROTOCOL_FALLBACK=1(off by default) restores today's permissive behavior for orgs that depend on it during migration.Why this matters (panel verdict)
supply-chain-security-expert: Silent cross-protocol fallback is a downgrade-by-induced-failure surface. SSH and HTTPS are different trust roots (
known_hostsfingerprint vs system CA + credential helper) with different identities (SSH key vs PAT vs cached helper) and different revocation lifecycles. An attacker who can DoS or interrupt SSH negotiation can force APM into the HTTPS path, where ambient credentials may successfully pull a different repo. Honoring explicit scheme is required, not a nice-to-have.auth-expert:
AuthResolveralready classifies hosts and resolves a per-host token. The current fallback chain bypasses that classification at clone time: when Method 1 fails for unrelated reasons (e.g. transient 502), Methods 2 and 3 retry with completely different auth surfaces and may "succeed" against the wrong identity. Honor-scheme turns the resolver from a suggestion back into a decision.devx-ux-expert: Both #661 and #328 are, at root, the same UX bug: the error you get does not name the thing that failed. #661: user says SSH, error says HTTPS. #328: user says shorthand, error says "not accessible" with no hint that SSH was an option. The contract above plus a one-line diagnostic ("APM picked HTTPS for shorthand 'owner/repo'; SSH not attempted because no SSH key was detected. Override with --ssh or APM_GIT_PROTOCOL=ssh.") closes the experienced part of both reports.
apm-ceo: "Honor user transport, never silently downgrade" is a one-line positioning sentence to enterprise security teams auditing APM. They have npm fatigue. We should lead with it. Costs us one PR; pays back across every "but my SSH works fine in plain git" report we will get as APM grows in self-hosted Bitbucket and GitLab shops.
Implementation sketch
explicit_scheme: Optional[str]toDependencyReference(theportfield added by fix: preserve protocol (e.g. ssh:// or https://) and port in dependency URL #665 already implies this; small refactor consolidates the signal)._clone_with_fallback(src/apm_cli/deps/github_downloader.py:630) onexplicit_scheme:ssh-> SSH only.https/http-> HTTPS only (HTTP additionally gated byallow_insecure).None(shorthand) -> ordered chain per the contract.git config --get-urlmatch url.<base>.insteadOf <url>once per install; honor rewrites for the shorthand branch.--ssh/--httpstoapm installandapm add. AddAPM_GIT_PROTOCOLenv. AddAPM_ALLOW_PROTOCOL_FALLBACKescape hatch.insteadOfhonored; escape hatch restores legacy behavior.docs/src/content/docs/reference/dependencies.md+docs/src/content/docs/reference/authentication.md+packages/apm-guide/.apm/skills/apm-usage/dependencies.md(Rule 4 doc-sync).Acceptance criteria
ssh://URL never causes an HTTPS clone attempt.https://URL never causes an SSH clone attempt.owner/repotries SSH whengit config insteadOfrewriteshttps://github.com/togit@github.com:, or when--ssh/APM_GIT_PROTOCOL=sshis set.APM_ALLOW_PROTOCOL_FALLBACK=1restores today's behavior (regression escape hatch).Out of scope
~/.ssh/config, do not reinvent).Filed by the APM Review Panel after reviewing #661, #328, and #700 together. See the panel review on #665 for the precedent that surfaced this theme.