Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **BREAKING:** after consumers re-vendor the shared gh-aw `apm.md`, its import requires an explicit `target` instead of deprecated `all`; `apm-action` otherwise writes `all` into the isolated `apm.yml`, where it degrades to auto-detection without harness markers. Set the workflow engine's target and recompile; see the [gh-aw migration recipe](https://microsoft.github.io/apm/integrations/gh-aw/#shared-apmmd-import-recommended). (#2706)
- Re-vendored shared gh-aw workflows now default to APM 0.28.0 for both pack and restore, the version used for the recorded `microsoft/apm-action@v1.10.0` compatibility proof, not the latest CLI release; an explicit `apm-version` still overrides it. (#2706)

### Fixed

- Installing a private Git dependency no longer fails with `Unable to verify Git URL rewrite safety` when a `url.<ssh-target>.insteadOf` rule rewrites the fetched HTTPS URL to SSH. The `http.extraHeader` URL-match probe now runs only for HTTP(S) effective URLs, because Git rejects SCP-style targets such as `git@github.com:owner/repo`. (#2906)

### Security

- The shared gh-aw APM pack job now declares `contents: read` (previously `permissions: {}`), the minimum the explicit built-in-token path needs. No write scope is added, and the token is not forwarded to restore or agent jobs. (#2706)
Expand Down
6 changes: 4 additions & 2 deletions src/apm_cli/utils/git_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,9 @@ def _has_applicable_http_authorization(
headers: Sequence[GitConfigEntry],
env: dict[str, str],
) -> bool:
"""Ask Git which URL-scoped extra headers apply to one remote."""
"""Ask Git which URL-scoped extra headers apply to one HTTP(S) remote."""
if urlsplit(remote_url).scheme.lower() not in {"http", "https"}:
return False
Comment thread
arnaudoisel marked this conversation as resolved.
direct_header = env.get("GIT_HTTP_EXTRAHEADER", "")
if _is_valid_http_extraheader_value(direct_header) and _is_credential_bearing_http_header(
direct_header
Expand Down Expand Up @@ -657,7 +659,7 @@ def _urlmatched_header_group(
if result.returncode == 1:
return ()
if result.returncode != 0 or not isinstance(result.stdout, bytes):
raise GitUrlRewriteProbeError("Git URL-match probe failed")
raise GitUrlRewriteProbeError(f"Git URL-match probe exited with status {result.returncode}")
selected = result.stdout.rstrip(b"\0\n")
prefix = b"X-Apm-Config-Probe: "
if not selected.startswith(prefix):
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/cache/test_git_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
git_remote_refs,
git_subprocess_env,
git_subprocess_error_text,
git_url_has_authorization,
reset_git_cache,
set_git_authorization_header,
)
Expand Down Expand Up @@ -918,6 +919,45 @@ def test_clone_rejects_ssh_remote_rewritten_to_http(self, tmp_path) -> None:
env=env,
)

def test_clone_allows_scp_ssh_rewrite_while_a_header_is_injected(self, tmp_path) -> None:
env = {
"PATH": os.environ["PATH"],
"GIT_CONFIG_COUNT": "2",
"GIT_CONFIG_KEY_0": "http.extraheader",
"GIT_CONFIG_VALUE_0": "Authorization: Basic sentinel",
"GIT_CONFIG_KEY_1": "url.git@git.example.com:.insteadOf",
"GIT_CONFIG_VALUE_1": "https://git.example.com/",
}
with (
patch.dict(os.environ, {"PATH": os.environ["PATH"]}, clear=True),
patch(
"apm_cli.utils.git_env.subprocess.run",
side_effect=_run_real_git_config_and_fake_clone,
) as run,
):
clone_git_worktree(
"https://git.example.com/acme/repo",
tmp_path / "clone",
env=env,
)

argv = run.call_args_list[-1].args[0]
assert "clone" in argv
assert [urlsplit(arg).hostname for arg in argv if urlsplit(arg).scheme == "https"] == [
"git.example.com"
]

def test_scp_ssh_url_reports_no_http_authorization_without_probing_git(self) -> None:
headers = (GitConfigEntry("command", "http.extraheader", "Authorization: Basic sentinel"),)
with patch(
"apm_cli.utils.git_env._git_config_run",
side_effect=AssertionError("the URL-match probe must not run for a non-HTTP URL"),
) as probe:
authorized = git_url_has_authorization("git@git.example.com:acme/repo", headers)

assert authorized is False
probe.assert_not_called()

@pytest.mark.parametrize(
("replacement", "message"),
(
Expand Down