fix(git-env): skip the HTTP header probe for non-HTTP effective URLs - #2906
fix(git-env): skip the HTTP header probe for non-HTTP effective URLs#2906Arnaud (arnaudoisel) wants to merge 5 commits into
Conversation
An `insteadOf` rule that rewrites the fetched HTTPS URL to SSH, such as `url."git@github.com:".insteadOf = https://github.com/`, made every private dependency download fail on the authenticated retry. `_validated_git_url_rewrite_policy` asked whether an HTTP `extraHeader` applied to the effective URL. With an SCP-style target that probe ran `git config --get-urlmatch http.extraHeader git@github.com:owner/repo`, which git rejects with `invalid URL scheme name or missing '://' suffix` and exit status 128, so the probe raised `GitUrlRewriteProbeError`. An HTTP header can never reach a non-HTTP transport, so the answer is already known: report no authorization and do not spawn the probe. The remaining non-zero branch now names the exit status instead of only saying the probe failed. Fixes microsoft#2898
@microsoft-github-policy-service agree company="Lucca" |
There was a problem hiding this comment.
🟡 Changes recommended
_has_applicable_http_authorization now calls urlsplit() without guarding ValueError, which can surface as an unwrapped exception for malformed URLs and bypass the module’s consistent rewrite-safety error handling.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes a regression in Git URL rewrite safety validation where the HTTP extraHeader URL-match probe was being executed against SCP-style SSH effective URLs (e.g., git@github.com:owner/repo), which Git rejects, breaking authenticated private Git dependency installs.
Changes:
- Skip the HTTP header URL-match probe when the effective URL is not HTTP(S).
- Improve probe failure diagnostics by including the probe exit status.
- Add unit regressions covering SCP-style rewrites during authenticated retries and asserting the probe is not spawned for non-HTTP URLs.
File summaries
| File | Description |
|---|---|
src/apm_cli/utils/git_env.py |
Adds an HTTP(S)-scheme guard before probing --get-urlmatch, and improves probe error messaging. |
tests/unit/cache/test_git_env.py |
Adds regression tests for SCP-style SSH rewrites under injected auth headers and ensures the probe is skipped. |
CHANGELOG.md |
Documents the fix under Unreleased/Fixed. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@microsoft-github-policy-service terminate |
|
@microsoft-github-policy-service agree |
The non-HTTP guard added in the previous commit was the first urlsplit
applied to the effective URL, and it is evaluated as an argument to
validate_resolved_git_url_rewrite -- so before that function's
try/except. An insteadOf rule whose replacement carries unbalanced
brackets, such as url."https://[::1/".insteadOf, therefore surfaced a raw
ValueError("Invalid IPv6 URL") instead of the module's
"Unable to verify Git URL rewrite safety", and the CPython message for a
bracketed non-address embeds the host unredacted.
Guarding the scheme lookup cannot fail open: both callers pass the same
URL straight to validate_resolved_git_url_rewrite, which re-splits it
inside its try and raises the wrapped error.
Preserve arnaudoisel's original commits while integrating current main. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Auth Expert | 0 | 0 | 0 | No auth regression found: the non-HTTP scheme guard preserves HTTPS credential boundaries, real retry behavior, and resolver-level same-host SSH rewrite handling. |
| CLI Logging Expert | 0 | 0 | 0 | Probe-failure and unsafe-rewrite recovery are now separated cleanly; no CLI logging follow-up in the scoped final diff. |
| DevX UX Expert | 0 | 0 | 0 | Scoped install-failure UX fix is now accurate, actionable, and mirrored in the user docs; ship. |
| Doc Writer | 0 | 0 | 0 | No docs fault remains: CHANGELOG and both auth guides now match the HTTP(S)-only probe boundary and probe-failure recovery; ship. |
| OSS Growth Hacker | 0 | 0 | 0 | This lands as a clean friction-removal fix with truthful changelog/docs support; no OSS-growth follow-up needed before merge. |
| performance-expert | 0 | 0 | 0 | Ship: src/apm_cli/utils/git_env.py:594-606 adds an O(1) scheme guard that skips one git config probe subprocess on non-HTTP(S) rewrites; no new loops or cache-layer regressions. |
| Python Architect | 0 | 0 | 1 | Scoped fix preserves the shared rewrite-safety owner and keeps the fail-closed auth boundary intact; ship. |
| Supply Chain Security | 0 | 0 | 0 | Change narrows the HTTP header probe to HTTP(S), preserves fail-closed rewrite validation and credential boundaries, and adds regression coverage; ship. |
| Test Coverage | 0 | 1 | 0 | Real Git-backed regression traps cover the SCP rewrite/auth retry fix, but the PR body still lacks the required Scenario Evidence table. The body-only follow-up is now resolved. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Architecture
classDiagram
direction LR
class GitEnvUrlRewritePolicy {
<<IOBoundary>>
+_validated_git_url_rewrite_policy(remote_url, env, git_dir, worktree)
+validate_resolved_git_url_rewrite(remote_url, effective_url, has_authorization, managed_auth_intent)
+_has_applicable_http_authorization(remote_url, headers, env)
+_urlmatched_header_group(remote_url, headers, env)
}
class GitNetworkEnvBuilder {
<<ProceduralBoundary>>
+git_network_env(remote_url, overrides, git_dir, worktree)
+_build_git_auth_fence(transport_url, snapshot, env, intent_snapshot, managed_auth_intent)
}
class GitConfigInsteadOfResolver {
<<Adapter>>
+resolve(candidate_url) str|None
+_load_rewrites() tuple[list[tuple], tuple]
}
class GitConfigEntry {
<<ValueObject>>
+scope str
+key str
+value str
}
class _GitConfigSnapshot {
<<ValueObject>>
+entries tuple[GitConfigEntry,...]
+rewrites tuple[tuple[str,str],...]
+http_headers tuple[GitConfigEntry,...]
}
class _GitAuthFence {
<<ValueObject>>
+remote_url str
+reset_headers bool
+suppress_helpers bool
+safe_headers tuple[str,...]
+managed_header str|None
}
GitConfigInsteadOfResolver ..> GitEnvUrlRewritePolicy : delegates
GitEnvUrlRewritePolicy ..> GitConfigEntry : inspects
GitEnvUrlRewritePolicy ..> _GitConfigSnapshot : returns
GitNetworkEnvBuilder ..> GitEnvUrlRewritePolicy : reuses owner
GitNetworkEnvBuilder ..> _GitConfigSnapshot : materializes
GitNetworkEnvBuilder ..> _GitAuthFence : produces
note for GitEnvUrlRewritePolicy "No OO change in this PR.\nThis procedural boundary remains the canonical owner for rewrite safety and HTTP header applicability."
note for GitNetworkEnvBuilder "Collect then render:\nvalidate the snapshot, then materialize the child env."
class GitEnvUrlRewritePolicy:::touched
classDef touched fill:#fff3b0,stroke:#d47600
Recommendation
Advisory code stance: ship this change as-is. Do not treat that as a recommendation to merge immediately; first clear the six hosted action_required workflows and whatever maintainer authorization or policy step is blocking them, then merge once those external gates are satisfied.
Full per-persona findings
Python Architect
- [nit] No architecture follow-up needed.
Design patterns - Used in this PR: Adapter plus dataclass-as-value-object --
GitConfigInsteadOfResolver.resolve()continues to delegate rewrite safety and HTTP header applicability to the sharedgit_env.pyowner, whileGitConfigEntryand_GitConfigSnapshotcarry validated config facts across callers. - Pragmatic suggestion: none -- the current shape is the simplest correct design at this scope.
CLI Logging Expert
No findings.
DevX UX Expert
No findings.
Supply Chain Security
No findings.
OSS Growth Hacker
No findings.
Auth Expert
No findings.
Doc Writer
No findings.
Test Coverage
- [recommended] Add the required Scenario Evidence table to the PR body.
This is a behavior-change bug-fix PR, so the PR description should carry the Scenario Evidence table required by .github/skills/pr-description-skill/assets/scenario-evidence-rubric.md. I probed the supplied PR body with rg for 'Scenario Evidence' and found no matching section or table in files/pr-context.json. The code-side regression coverage now looks sufficient by boundary: the added tests run real Git config subprocesses through clone_git_worktree, GitConfigInsteadOfResolver plus TransportSelector, and AuthResolver.try_with_fallback plus git_network_env, and the targeted exact-head nodeids passed locally (11 passed in 2.59s). The remaining gap is governance of that evidence in the PR body, so a maintainer cannot audit scenario-to-test mapping from the description alone.
Suggested: Add a Scenario Evidence table that names the user-facing SCP rewrite retry scenario, the preserved HTTPS origin rejection controls, the exact test paths/nodeids, and the APM principles each row defends.
Proof (unknown):(no test ref)-- proves: The PR body maps each user-visible scenario to the automated tests that defend it. [devx,oss]
rg 'Scenario Evidence' /Users/danielmeppiel/.copilot/session-state/5ccd713d-99fe-4376-871d-d73087091011/files/pr-context.json -> no matches
Performance Expert
No findings. The non-HTTP path avoids the unnecessary probe subprocess.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
Reservations carried from strategic-alignment
- Preserve fail-closed handling of injected HTTP authorization headers and existing trust boundaries; only skip probes in protocols where HTTP headers cannot apply. -- Addressed by the scheme guard and mutation-proven HTTPS origin rejection.
- Bound repair to transport/probe handling, not broad AuthResolver or credential injection changes. -- Existing owner extended; no auth resolver production changes.
- Regression must cover real SCP matching rewrite with authenticated retry and preserve no-header, unmatched-rule, ssh:// and HTTPS controls. -- Real Git config fixtures exercise the resolver, selector, child environment, and actual authenticated retry.
- Legitimate failures should explain probe failure without telling users to remove standard insteadOf config. -- Probe-specific inspection guidance is separate from proven-unsafe rewrite recovery.
Folded in this run
- Add real Git config resolver, transport selector, and authenticated-retry consumer regressions with SCP/no-header/unmatched/ssh/HTTPS controls. --
221b7debb0. - Separate probe-failure inspection guidance from proven-unsafe rewrite recovery and protect exit-status/redaction diagnostics. --
221b7debb0. - Synchronize Starlight and apm-guide authentication documentation. --
221b7debb0. - Add the six-scenario evidence table and honest validation scope to the PR body for this exact head (metadata-only fold). --
221b7debb0.
Copilot signals reviewed
3967028719: LEGIT malformed-host wrapping concern, already fixed and thread-resolved by the contributor in7ab2b0de. The suggested invalid-port example was imprecise; the bracketed-host concern was reproduced. Two fetch rounds, no new inline findings.
Regression-trap evidence
Removing the protocol guard produced five SCP failures (including real resolver and authenticated retry consumers); removing the parser wrapper produced one failure; restoring old diagnostics produced two failures; a blanket header-probe skip produced one HTTPS-origin failure. All mutations were restored.
Local validation
Exact head 221b7debb069e057861b2a50f96bcc4e9918feff: 253 targeted tests passed. All seven local CI lint gates plus architecture boundaries passed after integrating main e38261c5db4d893d6ddebc3925742e4e3bd2ba74.
Version 2 owner evidence detects one owner: Git child-process repository location and URL rewrite safety. Four exact-head functional test IDs cover it; schema validation and strict semantic verification passed. This is an existing-owner extension, not a centralization or authority split.
CI and mergeability
Hosted CI is not green. All six Actions runs for this exact head concluded action_required; the check rollup exposes only the passing CLA. CI run. Maintainer authorization/policy action is required; the exact policy is not established by the API evidence.
| PR | Head | Code stance | Iterations | Folds | Deferrals | Copilot rounds | CI | mergeable | mergeStateStatus |
|---|---|---|---|---|---|---|---|---|---|
| #2906 | 221b7debb0 |
ship_now | 2 | 4 | 0 | 2 | action_required | MERGEABLE | BLOCKED |
Convergence
No scoped code follow-up remains. The code advisory is positive, but this PR is not ready to merge until hosted workflows and repository policy are satisfied. No approval, bypass, or merge was performed. This comment replaces the initial advisory rather than adding another panel comment.
Address panel follow-ups on PR microsoft#2906 with real Git config resolver and authenticated-retry regressions, HTTP origin controls, and probe-specific recovery guidance. Preserve unsafe-rule recovery for proven policy failures and synchronize authentication documentation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Docs sync advisoryVerdict: in_place * Pages affected: 2 * LLM calls: 10/15 SummaryClassifier verdict was Applied patches
Validation
Artifacts
|
|
Daniel Meppiel (@danielmeppiel) Sergio Sisternes (@sergio-sisternes-epam) can we please get this reviewed / fixed soon? This is currently a blocker for us to use apm at Electronic Arts. I can help contribute to / provide more details around it if needed to speed things up. Thanks |
fix(git-env): skip HTTP header probes for non-HTTP effective URLs
TL;DR
Fixes private Git dependency downloads when a standard
insteadOfrule rewrites HTTPS to SCP-style SSH. HTTP header applicability is checked only for HTTP(S) effective URLs; existing rewrite admission and credential boundaries remain enforced. Genuine probe failures now identify the exit status and recommend inspecting configuration rather than deleting a rule not proven unsafe.Note
Fixes #2898. Original fix and reproduction by Arnaud (@arnaudoisel); the original commits are preserved. Follow-up consumer regressions, diagnostics, and documentation are included in this PR.
Problem (WHY)
http.extraHeadercaused Git's URL-match probe to receive an SCP target such asgit@github.com:owner/repo.fatal: invalid URL scheme name or missing '://' suffix. This surfaced asUnable to verify Git URL rewrite safetyduring the reported private dependency installation.The contributor's #2898 reproduction requires a matching rewrite, an injected authorization header, and an actual download. A public dependency without an injected header does not trigger the same probe.
The contributor measured Git 2.55.0 with a dummy command-scoped header:
https://github.com/o/rssh://git@github.com/o/rgit@github.com:o/rApproach (WHAT)
GitConfigInsteadOfResolver.Implementation (HOW)
src/apm_cli/utils/git_env.pytests/unit/cache/test_git_env.pytests/unit/test_transport_selection.pyssh://, and authenticated HTTPS origin rejection.tests/unit/core/test_public_github_anonymous_first.pydocs/src/content/docs/getting-started/authentication.mdpackages/apm-guide/.apm/skills/apm-usage/authentication.mdCHANGELOG.mdDiagrams
The new non-HTTP branch skips only header applicability; both branches still reach rewrite validation.
flowchart LR R["resolve_git_url_rewrite"] --> H["_has_applicable_http_authorization"] H --> S{"HTTP or HTTPS?"} S -->|"yes"| P["Git URL-match probe"] S -->|"no"| N["Return no HTTP authorization"] P --> V["validate_resolved_git_url_rewrite"] N --> V V --> C["Validated child Git environment"] classDef changed stroke-dasharray: 5 5; class S,N changed;Trade-offs
AuthResolveror credential selection. Same-host SSH remains credential-free; HTTPS origin checks remain active.ValueErroris deferred to the shared validator so malformed targets still fail with the wrapped, redacted message; it is not treated as an accepted rewrite.Benefits
Validation
Exact head:
221b7debb069e057861b2a50f96bcc4e9918feff; integrated main:e38261c5db4d893d6ddebc3925742e4e3bd2ba74.UV_FROZEN=true uv run --extra dev pytest -q tests/unit/cache/test_git_env.py tests/unit/core/test_git_transport_policy.py tests/unit/test_transport_selection.py tests/unit/core/test_public_github_anonymous_first.py tests/unit/install/test_validation_strict_transport.py tests/unit/cache/test_proxy_compat.pyAll seven local CI lint gates and the architecture boundary lint passed on this head. Mutation checks produced five SCP failures without the protocol guard, one malformed-URL failure without its wrapper, two diagnostic failures with old wording, and one HTTPS failure with a blanket probe skip. All mutations were restored before the final run.
Warning
Hosted CI has not run successfully on this head. CI, CodeQL, Spec conformance, Merge Gate, NOTICE Drift Check, and Deploy Docs concluded
action_required. See CI run 34413898041. Maintainer action is required; local results are not a substitute for hosted CI.Scenario Evidence
tests/unit/test_transport_selection.py::TestGitConfigInsteadOfResolver::test_real_resolver_allows_https_to_scp_ssh_rewrite(regression-trap for #2898)tests/unit/core/test_public_github_anonymous_first.py::test_public_github_authenticated_retry_drops_header_after_real_ssh_rewrite(regression-trap for #2898)ssh://configurations retain their transport selection.tests/unit/test_transport_selection.py::TestGitConfigInsteadOfResolvertests/unit/test_transport_selection.py::TestGitConfigInsteadOfResolver::test_real_resolver_checks_http_authorization_for_https_rewritetests/unit/cache/test_git_env.py::TestGitSubprocessEnv::test_malformed_rewrite_target_keeps_the_wrapped_safety_errortests/unit/cache/test_git_env.py::TestGitSubprocessEnv::test_http_urlmatch_failure_reports_status_without_raw_configHere, integration denotes real Git configuration and production cross-module boundaries, independent of the existing test directory names.
How to test
test_real_resolver_consumer_drops_dummy_http_header_after_scp_rewritefor a fixture-backed demonstration; no real token or global Git config change is needed.Spec conformance (OpenAPM v0.1)
Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com