fix(client): make our credential helper the only one the repo consults
Live QA against the beta forge failed its first push with 'Failed to authenticate user' while the config held a valid token. Cause: credential.helper is a list accumulated across system/global/repo config, and this machine has osxkeychain (Xcode gitconfig) plus store (~/.gitconfig). A stale entry for the forge host answered before our helper. The same list is a token leak in the other direction: git calls approve on every helper after a successful auth, so 'store' writes the forge token into ~/.git-credentials in plaintext -- undoing the 0600 config and the no-token-in-URL rule. Confirmed accidentally during QA when a verification clone with a URL-embedded token re-created exactly that entry. Fix: set an empty credential.helper first (git reads that as 'forget the inherited list'), then add ours -- in install_credential_helper and in the git clone inside get. 2 regression tests, one of which drives 'git credential fill' against a poisoned outer helper. 143 tests.
This commit is contained in:
@@ -544,7 +544,27 @@ def credential_helper_value():
|
||||
|
||||
|
||||
def install_credential_helper(folder):
|
||||
git(folder, "config", "credential.helper", credential_helper_value())
|
||||
"""Make OUR helper the only one this repo consults.
|
||||
|
||||
credential.helper is a LIST that accumulates across system, global and
|
||||
repo config, and git asks every helper in order. On a stock mac there are
|
||||
already two (`osxkeychain` from Xcode's gitconfig, `store` from many
|
||||
people's ~/.gitconfig), and they lose both ways:
|
||||
|
||||
* reading -- a stale entry for the forge host answers first, so pushes
|
||||
fail with "Failed to authenticate user" long after the token was
|
||||
rotated, and nothing in this tool's config explains why;
|
||||
* writing -- git calls `approve` on every helper after a successful
|
||||
auth, so `store` copies the forge token into ~/.git-credentials in
|
||||
PLAINTEXT. Keeping the token in a 0600 file and out of remote URLs
|
||||
is pointless if git hands it to a plaintext store on first use.
|
||||
|
||||
An empty value resets the inherited list, so replace-all-then-add leaves
|
||||
exactly one helper: this script.
|
||||
"""
|
||||
git(folder, "config", "--replace-all", "credential.helper", "")
|
||||
git(folder, "config", "--add", "credential.helper",
|
||||
credential_helper_value())
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -756,7 +776,12 @@ def clone_one(cfg, full_name, dest, mode=None):
|
||||
# persists it into the new repo's config. --origin names the remote
|
||||
# 'granthi' up front so `watch` picks the folder up without a rename.
|
||||
proc = subprocess.run(
|
||||
# The empty -c resets the inherited helper list (see
|
||||
# install_credential_helper) so a stale keychain/store entry cannot
|
||||
# answer for the forge host during the clone, and the token cannot
|
||||
# leak into a plaintext store afterwards.
|
||||
["git", "clone",
|
||||
"-c", "credential.helper=",
|
||||
"-c", f"credential.helper={credential_helper_value()}",
|
||||
"--origin", "granthi", clone_url, dest],
|
||||
capture_output=True, text=True)
|
||||
|
||||
Reference in New Issue
Block a user