fix(install): align validation auth chain with install#941
Open
a1icja wants to merge 4 commits intomicrosoft:mainfrom
Open
fix(install): align validation auth chain with install#941a1icja wants to merge 4 commits intomicrosoft:mainfrom
a1icja wants to merge 4 commits intomicrosoft:mainfrom
Conversation
Author
|
@microsoft-github-policy-service agree |
Contributor
There was a problem hiding this comment.
Pull request overview
Aligns apm install <owner>/<repo>/<subdir>#<ref> validation behavior with the actual install/clone authentication fallbacks, reducing false-negative validation failures when the GitHub Contents API rejects a token but git clone would succeed.
Changes:
- Extend
validate_virtual_package_existswith a Contents API directory-exists probe and a gatedgit ls-remoteauth chain fallback (mirroring_clone_with_fallback). - Thread verbose logging through validation so probe attempts are visible under
--verbose. - Add targeted tests for the
ls-remotefallback chain and document the updated validation/auth behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/deps/github_downloader.py |
Adds new validation probes and git ls-remote fallback helpers to reconcile validation with install auth behavior. |
src/apm_cli/install/validation.py |
Passes through verbose_callback so the new probes emit --verbose diagnostics. |
tests/test_github_downloader.py |
Introduces tests pinning the new ls-remote fallback chain behavior. |
docs/src/content/docs/getting-started/authentication.md |
Documents that CLI validation now follows the same auth chain as install. |
CHANGELOG.md |
Adds an Unreleased Fixed entry for the validation/auth alignment change. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(install): align validation auth chain with install
TL;DR
apm install owner/repo/sub#BRANCHwould false-fail validation when the GitHub Contents API rejected the env-var PAT butgit clone(driven by the system credential helper) would have succeeded — the same package lands fine when added toapm.ymldirectly because that path skips validation entirely. This PR teachesvalidate_virtual_package_existstwo new fallbacks: a directory-exists Contents API probe, and agit ls-remotechain that mirrors_clone_with_fallback's auth attempts. Validation now agrees with install for any virtual subdirectory dep that carries an explicit#ref; path-typo rejection on the default branch is preserved.Note
The
ls-remotefallback is gated ondep_ref.reference is not Noneso unsuffixedowner/repo/sub(no explicit#ref) still fails fast on path typos — the gate keeps that signal.Problem (WHY)
apm install foo/bar/gee#sho --verboseerrors withnot accessible or doesn't existwhile the equivalent entry inapm.ymlinstalls cleanly. Verbose log shows the resolver found a per-org PAT (source=GITHUB_APM_PAT_FOO, type=classic) butvalidate_virtual_package_existsstill returned False.validate_virtual_package_existsonly walks the GitHub Contents API._clone_with_fallbackwalks an authenticated PAT attempt then a plain HTTPS attempt that lets the system credential helper supply the token — two stages of auth-asymmetry today.Why this matters: validation is the gate on a deterministic action, so it must agree with that action. PROSE: "Grounding outputs in deterministic tool execution transforms probabilistic generation into verifiable action." — when the validator's view of "accessible" diverges from
git clone's view, the deterministic layer no longer dominates and the install pipeline never gets to run.Approach (WHAT)
validate_virtual_package_exists: when no marker file is found, requestGET /repos/{owner}/{repo}/contents/{path}?ref={ref}for the directory itself. A 200 means install will find the path.git ls-remote --heads --tags <url> <ref>fallback gated ondep_ref.reference is not None. Walks the same auth chain as_clone_with_fallback, so any token / credential that clones is accepted by validation.verbose_callbackfrom_validate_package_existsintovalidate_virtual_package_existsso each probe attempt is visible under--verbose([+] path@ref/[x] path@ref (reason)) without re-running withprintstatements.Implementation (HOW)
src/apm_cli/deps/github_downloader.py—validate_virtual_package_existsrewritten with a_probe(path)helper, marker probes, and two new fallbacks. Three new private helpers:_directory_exists_at_ref,_ref_exists_via_ls_remote,_ssh_attempt_allowed. The auth chain in_ref_exists_via_ls_remotedeliberately mirrors_clone_with_fallback's ordering and env builders — per-attempt env selection (self.git_envfor the token attempt vs_build_noninteractive_git_envfor the credential-helper attempt) is preserved so the locked-down env is only used for the token-bearing attempt.src/apm_cli/install/validation.py— one-line change: passverbose_callback=verbose_logso the new per-probe diagnostics flow through--verbose.tests/test_github_downloader.py— addsTestRefExistsViaLsRemote(8 tests) pinning the auth chain: token short-circuits, 403-on-token falls back to credential helper, no-token skips first attempt, all-fail returns False, empty output is a miss not a hit, Artifactory short-circuits, SSH skipped by default, SSH added whenProtocolPreference.SSHis set.CHANGELOG.md/docs/src/content/docs/getting-started/authentication.md— one Fixed entry under[Unreleased]and a paragraph describing that validation walks the same chain as install.Diagrams
Legend: control flow inside
validate_virtual_package_existsfor a virtual-subdirectory dep — markers fast-path where present, the two new fallbacks reconcile validation with_clone_with_fallback. Look first at theRefGatenode — that is the new gate that preserves path-typo rejection for unsuffixed deps.flowchart TD Start[validate_virtual_package_exists] --> Markers{Probe markers:<br/>apm.yml, SKILL.md,<br/>plugin.json, README.md} Markers -->|hit| Pass[return True] Markers -->|all 404| DirProbe{Contents API<br/>directory-exists<br/>at ref} DirProbe -->|200| Pass DirProbe -->|404 or non-GitHub| RefGate{dep_ref.reference<br/>is not None?} RefGate -->|no| Fail[return False] RefGate -->|yes| LsRemote{git ls-remote<br/>chain} LsRemote -->|attempt 1: token + git_env| Pass LsRemote -->|attempt 2: plain HTTPS<br/>credential helper| Pass LsRemote -->|attempt 3: SSH<br/>only if --ssh or<br/>--allow-protocol-fallback| Pass LsRemote -->|all fail| FailTrade-offs
ls-remotefallback on explicit#ref. Chose to requiredep_ref.reference is not None; rejected unconditional fallback because path-typo rejection on the default branch is a useful fast signal. Without the gate, a typo likeowner/repo/skilswould pass validation just because the repo and default branch resolve.git, rejected "go straight to ls-remote" because the Contents API path stays correct for the common case (public repos, fully-authorized PATs) and avoids spawning a git subprocess.ProtocolPreference.SSHor--allow-protocol-fallback, rejected always-attempt because users without SSH configured would see noisy validation output and possibly hung passphrase prompts.BatchMode=yesplusConnectTimeout=10further protects users who DO opt in.GitHubPackageDownloader, not a new class. Chose to colocate the new helpers with the existing auth state; rejected a separateValidatorclass because the auth resolver, git env, and protocol prefs already live on the downloader and a sibling class would duplicate that state.Benefits
apm install foo/bar/gee#sho) now succeeds: validation defers to install for any explicit-ref virtual subdir dep.apm install <pkg>.ls-remoteauth chain (token short-circuit, 403 fallback, SSH gating, empty-output miss, Artifactory bypass) so a future refactor cannot silently regress lenience.--verbosenow shows every probe attempt ([+] path@ref/[x] path@ref (reason)); future user reports are debuggable without re-running withprintstatements.github_downloader.py(not subject to the install-engine LOC budget) andcommands/install.pyis untouched.Validation
pytest— 8 new + 24 existing validation/architecture tests:New `TestRefExistsViaLsRemote` test list (all 8 PASSED)
Pre-existing failures observed on `main` (NOT introduced by this PR)
Reproduced on
mainwithgit stash && pytest <node-id>:tests/test_github_downloader.py::TestGitHubPackageDownloader::test_resolve_git_reference_commit— sandboxed clone of fake repo.tests/test_github_downloader.py::TestEnterpriseHostHandling::test_clone_fallback_respects_enterprise_host— same.tests/test_github_downloader.py::TestDownloaderCredentialFallback::test_credential_fill_used_when_no_env_token— pre-existing assertion ongithub_tokencache.tests/unit/test_install_command.py::TestInstallGlobalFlag::test_global_without_packages_and_no_manifest_errors— output-string assertion that depends on terminal width.How to test
GITHUB_APM_PAT_<ORG>) and a separate, more-authorized credential cached viagh auth setup-git/ OS keychain, runapm install foo/bar/gee#sho --verboseagainst a virtual subdirectory dep on a non-default branch. Expect[+] ls-remote ok via plain HTTPS w/ credential helper(orvia authenticated HTTPS) in the verbose output and successful install.apm install <org>/<repo>/<wrong-sub-name> --verbose(no#ref) and confirm validation still fails fast withnot accessible or doesn't exist— the gate on explicit-ref preserves path-typo rejection..venv/bin/python -m pytest tests/test_github_downloader.py::TestRefExistsViaLsRemote -vand confirm all 8 tests pass in <1s.feat/install-branch-flag: confirm this PR contains only the validation/install auth-alignment changes (no--branchflag, no newapm_cli/install/branch_flag.py, nocommands/install.pyedit).Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com