From 265e32d9c9a47e3d9405e59c13f0c971138a5579 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Wed, 24 Jun 2026 18:03:59 -0400 Subject: [PATCH] fix(agent-team): authenticate git push with Basic auth, not Bearer The box-side dispatch smoke test failed at `git clone` (exit 128, "could not read Username"): GitHub's git smart-HTTP transport authenticates an installation token via BASIC auth (username x-access-token), not Bearer. Bearer is the REST API form (minting + workflow_dispatch + run-list all use it correctly) but git rejects it. app_branch_pusher now sets the http.extraHeader to `Authorization: Basic `. Verified on the box: Bearer -> exit 128, Basic -> clone OK. _scrub now also redacts the base64 credential blob (it decodes to the token). Test updated to assert the Basic form and that the raw token never appears literally in the header. --- agent-team/agent_team/dispatcher.py | 22 ++++++++++++++-------- agent-team/tests/test_dispatcher.py | 12 +++++++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/agent-team/agent_team/dispatcher.py b/agent-team/agent_team/dispatcher.py index aa78b3a..dc84dcd 100644 --- a/agent-team/agent_team/dispatcher.py +++ b/agent-team/agent_team/dispatcher.py @@ -604,21 +604,27 @@ def app_branch_pusher(token_provider: Any, *, _run: Any = None) -> BranchPusher: token = token_provider.token() # Plain remote — the token is NEVER in the URL/argv/stored origin. remote = f"https://github.com/{owner}/{repo}.git" - # Auth is injected via http.extraHeader through git's GIT_CONFIG_* env - # vars (NOT argv) on the network steps (clone + push). Env is visible - # only to the same uid via /proc//environ, never via ps argv. The - # header key is SCOPED to the github.com host + # GitHub's git smart-HTTP transport authenticates an installation token via + # BASIC auth (username ``x-access-token``), NOT Bearer — Bearer is the REST + # API form and git rejects it ("could not read Username" → exit 128). Encode + # ``x-access-token:`` and inject it as an Authorization header through + # git's GIT_CONFIG_* env vars (NOT argv) on the network steps (clone + push). + # Env is visible only to the same uid via /proc//environ, never via ps + # argv. The header key is SCOPED to the github.com host # (``http.https://github.com/.extraHeader``, the GitHub-Actions checkout - # pattern) so git never sends the Authorization header to any other host - # it might be redirected to. + # pattern) so git never sends the Authorization header to any other host it + # might be redirected to. + basic = base64.b64encode(f"x-access-token:{token}".encode()).decode("ascii") auth_env = { "GIT_CONFIG_COUNT": "1", "GIT_CONFIG_KEY_0": "http.https://github.com/.extraHeader", - "GIT_CONFIG_VALUE_0": f"Authorization: Bearer {token}", + "GIT_CONFIG_VALUE_0": f"Authorization: Basic {basic}", } def _scrub(s: str) -> str: - return s.replace(token, "***") + # Scrub BOTH the raw token and the base64 credential blob (which decodes + # to the token) so neither can survive in any surfaced error message. + return s.replace(token, "***").replace(basic, "***") with tempfile.TemporaryDirectory() as tmp: tmpdir = Path(tmp) diff --git a/agent-team/tests/test_dispatcher.py b/agent-team/tests/test_dispatcher.py index 67df394..ebfb8ba 100644 --- a/agent-team/tests/test_dispatcher.py +++ b/agent-team/tests/test_dispatcher.py @@ -374,12 +374,18 @@ def test_app_branch_pusher_keeps_token_out_of_argv_and_uses_plain_remote() -> No for arg in call["cmd"]: assert "ghs_TESTTOKEN" not in arg - # Auth rides in GIT_CONFIG_* env on the network steps only (clone + push). + # Auth rides in GIT_CONFIG_* env on the network steps only (clone + push), as + # BASIC auth (username x-access-token) — git smart-HTTP rejects Bearer. + expected_basic = "Authorization: Basic " + base64.b64encode( + b"x-access-token:ghs_TESTTOKEN" + ).decode("ascii") clone_env = recorded[0]["env"] assert clone_env["GIT_CONFIG_KEY_0"] == "http.https://github.com/.extraHeader" - assert clone_env["GIT_CONFIG_VALUE_0"] == "Authorization: Bearer ghs_TESTTOKEN" + assert clone_env["GIT_CONFIG_VALUE_0"] == expected_basic push_env = recorded[-1]["env"] - assert push_env["GIT_CONFIG_VALUE_0"] == "Authorization: Bearer ghs_TESTTOKEN" + assert push_env["GIT_CONFIG_VALUE_0"] == expected_basic + # The raw token never appears literally in the auth header (it is base64'd). + assert "ghs_TESTTOKEN" not in clone_env["GIT_CONFIG_VALUE_0"] # The non-network steps (checkout/apply/commit) carry no auth env. for call in recorded[1:4]: assert call["env"] is None