Skip to content

Transport Selection v1: honor user-specified transport, no silent downgrade #778

@danielmeppiel

Description

@danielmeppiel

Transport Selection v1: honor user-specified transport, no silent downgrade

Blocked by

Summary

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.

Item Relationship Notes
#661 ([BUG] Ignores ssh://, tries https) 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.

Implementation sketch

  1. Add explicit_scheme: Optional[str] to DependencyReference (the port field added by fix: preserve protocol (e.g. ssh:// or https://) and port in dependency URL #665 already implies this; small refactor consolidates the signal).
  2. 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.
  3. Read git config --get-urlmatch url.<base>.insteadOf <url> once per install; honor rewrites for the shorthand branch.
  4. Add --ssh / --https to apm install and apm add. Add APM_GIT_PROTOCOL env. Add APM_ALLOW_PROTOCOL_FALLBACK escape hatch.
  5. Diagnostic improvements per devx-ux-expert.
  6. 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.
  7. Docs: 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).
  8. CHANGELOG: under both Security (no silent downgrade) and Changed (shorthand may now try SSH).

Acceptance criteria

Out of scope


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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedDeprecated: use status/accepted. Kept for issue history; will be removed in milestone 0.10.0.enhancementDeprecated: use type/feature. Kept for issue history; will be removed in milestone 0.10.0.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions