fix(agent-team): dispatch via GitHub App so P3 reaches CI (run_id resolves) #63
2 changed files with 23 additions and 11 deletions
|
|
@ -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/<pid>/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:<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/<pid>/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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Reference in a new issue