Skip to content

Keep clone tokens out of git argv and repository config - #2084

Merged
ppXD merged 1 commit into
mainfrom
fix/keep-clone-tokens-out-of-git-argv
Oct 7, 2026
Merged

ppXD merged 1 commit into
mainfrom
fix/keep-clone-tokens-out-of-git-argv

Conversation

@ppXD

@ppXD ppXD commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Every tokened platform git command now names its remote by a URL without userinfo and carries the token only in its environment. That covers the workspace clone, soft-ref probe, pin fetch rungs, publish (lfs push, push, readback), the launch-base ls-remote, the branch integrator, the acceptance grader and the pack import. GIT_CONFIG_COUNT scopes two entries to the remote's scheme and authority: an empty helper that resets the operator's helpers, then an env-only helper that answers from CODESPACE_GIT_USERNAME and CODESPACE_GIT_PASSWORD with the shell's builtin printf (backend/src/CodeSpace.Core/Services/Agents/Workspace/TokenedGitCommand.cs).

    • As a result, no argv (/proc/<pid>/cmdline), no .git/config, no operator store/cache helper, no trace2 target and no authority the remote redirects to ever sees the token.
    • BuildAuthenticatedUrl is removed.
    • Auto-gc and auto-maintenance are switched off so no detached child keeps the environment.
  • The helper stops answering once the remote refuses the token. git-lfs answers a 401 by erasing the credential and asking again, with no limit. A helper that always answers kept an expired or revoked token retrying until the 300 s command timeout, at 15–30 failed logins per second. Now:

    • An erase writes an empty marker in a per-command owner-only directory (CODESPACE_GIT_STATE). The runner creates, binds and removes it through SandboxSpec.ConfigHomeEnvVars.
    • After that, get answers nothing and prints the remote refused this credential; not offering it again, so the command fails at once with the reason.
    • With no directory the helper never answers (fail closed).
  • Operator URL rewrites cannot move a tokened command. url.<remote>.insteadOf and url.<remote>.pushInsteadOf map the remote's URL to itself. A rule naming the whole URL is the longest prefix any rule can match, so a rule like url."git@github.com:".insteadOf https://github.com/ (switch to SSH) or a rule carrying an operator's own token no longer applies. This matches the behaviour before the change, when no rule could match a URL with a token in it.

  • Commands that download LFS objects through origin also carry the credential, since origin no longer holds it:

    • the integrator's base checkout, apply and reset;
    • the grader's base checkout and apply --3way (backend/src/CodeSpace.Core/Services/Supervisor/SupervisorAcceptanceGrader.cs). Before this, grading at an LFS base failed as "base revision not found".

    Rewriting origin after a clone stays as a belt. If it fails, the clone is still refused (fail closed), even though origin holds no credential.

Requires git 2.31 or later for GIT_CONFIG_COUNT (the worker image ships 2.43). An older git ignores it, finds no credential and fails rather than leak the token.

Test plan

  • Unit, TokenedGitCommandTests:
    • literal pins: helper text, variable names, state directory, the identity URL pin;
    • the helper run through a real /bin/sh: answers get, stays silent on store, after erase stops answering and says why, and answers nothing without a state directory;
    • a fixture check that the argv detector finds a Basic header (base64 of user:token) for any username.
  • Unit, call-site pins (provider, resolver, integrator, pack clone, grader): the grader's clone, checkout and apply run tokened; the strip and everything after it do not. Full unit suite: 12266 total, 12265 passed, 1 skipped.
  • Integration, TokenedGitCredentialHelperFlowTests. Real git and git-lfs, a loopback remote that authenticates reads, LFS and pushes, and the operator's store helpers in a scratch HOME. Passes on git 2.33.0, 2.50.1 and 2.56.0.
    • Grader rows: no token on any argv, on disk at any point during the run, or in the operator's store, with a positive control. A second row grades at an LFS base, with clone, checkout and apply controls.
    • Refused-token rows: a publish (lfs push) and a clone over LFS each fail in under 20 s, after at most 10 batch requests, with the reason in the message. The positive control, a helper that ignores erase, makes more than 50 requests before it is killed.
    • URL-rewrite rows: with operator rules sending fetches to a mirror and pushes to a sink, the launch-base probe, the clone and pin, the publish (LFS included) and the integration all still reach the real remote. The positive control: without the pin, the probe goes to the mirror and the push to the sink.
    • Disk-watch positive control: the watch also finds the token when it is written base64-encoded as http.extraHeader.
  • Integration, every class on the tokened git paths (provider, push, publish, integrator, pack, launch-base, oracle, supervisor grade, fold and gate): 454 of 454 on git 2.33. The push, integrator and grade-flow classes together with the two credential classes: 93 of 93 on 2.50 and on 2.56.
  • E2E: the non-real-model git suites, 37 of 37. Sandbox: GitWorkspaceIsolationE2ETests and AgentPublishIsolationE2ETests pass where macOS can run them.
  • Mutations go red:
    • a Basic header on the grader clone's argv;
    • clone --config http.extraHeader=… in the provider;
    • the URL pin dropped;
    • the state directory dropped.
  • Privileged Linux CI lane: tokened git commands under bubblewrap with the state directory bound, which cannot run on macOS.

Every tokened platform git command put the token in the remote URL on
its argv. Any host user can read that from /proc/<pid>/cmdline unless
/proc hides other users' processes, git wrote it into .git/config for
the length of a clone, and git answered a 401 from another authority
with it: a remote that redirected its ref advertisement to another host
was handed the token.

TokenedGitCommand now takes the credential separately. A tokened command
names the remote by its URL without userinfo. GIT_CONFIG_COUNT carries
two entries scoped to the remote's scheme and authority: an empty
credential helper that resets the operator's helpers, then an env-only
helper that answers get from CODESPACE_GIT_USERNAME and
CODESPACE_GIT_PASSWORD with the shell's builtin printf. A redirect to
another authority finds no helper that answers for it, so the command
fails instead of sending the token. The -c reset leaves argv: git reads
GIT_CONFIG_PARAMETERS after GIT_CONFIG_COUNT, so it would wipe the
helper. Auto-gc and auto-maintenance are off so no detached child keeps
the environment, and trace2 stays off.

git-lfs answers a 401 by erasing the credential and asking the helpers
again, with no limit, so a helper that always answers keeps a refused
token (expired or revoked) retrying until the command times out, tens
of failed logins a second. The helper records an erase as a marker in
a per-command owner-only directory the runner makes and removes
(ConfigHomeEnvVars), then answers nothing and says the remote refused
the credential. Without that directory it never answers.

With the token out of the URL, an operator's url.<base>.insteadOf or
pushInsteadOf rule matches a tokened remote for the first time, and
could move it to SSH under the host's key or to another credential. Two
more entries map the remote's URL to itself, the longest prefix any
rule can match, so a tokened command reaches its remote as it always
did.

The workspace clone, soft-ref probe, pin fetch rungs and publish (lfs
push, push, readback), the launch-base ls-remote, the branch integrator,
the acceptance grader and the pack import all go through it, and
BuildAuthenticatedUrl is gone. The integrator's base checkout, apply and
reset, and the grader's base checkout and apply, carry the credential
too: origin no longer does, and git-lfs asks the helpers for the objects
those commands download. Rewriting origin after a clone stays as a belt.
@ppXD
ppXD force-pushed the fix/keep-clone-tokens-out-of-git-argv branch from 4989d0d to 649b218 Compare October 7, 2026 13:47
@ppXD
ppXD merged commit 0673087 into main Oct 7, 2026
6 checks passed
@ppXD
ppXD deleted the fix/keep-clone-tokens-out-of-git-argv branch October 7, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant