fix: keep the gitea token out of chezmoi init's argv and .git/config
The endpoint hands back a clone URL with the read-only token inline, and that
URL went straight onto `chezmoi init`'s command line. Two durable exposures
followed, neither of which the existing redaction touched -- it only kept the
token out of the trace line:
- /proc/<pid>/cmdline is world-readable, so any account on the box could read
the live token for as long as the clone ran
- the resulting .git/config recorded the credential-bearing remote and kept
it until the tree was deleted
Split the credential out of the URL before anything executes. The token goes to
$STATE/private-credentials at 600 in git-credential-store format; the URL that
reaches argv and .git/config is clean. The helper is passed via GIT_CONFIG_*
for the clone and then written into the clone's own config, so a later
`chezmoi update` still authenticates without the token being stored.
Verified: clone succeeds with the clean URL, .git/config contains no token, and
a subsequent fetch authenticates from the credential file alone. Suite 101/101.
This commit is contained in:
+30
-1
@@ -130,9 +130,38 @@ is "private init does not use run()" "0" \
|
||||
# Two, not one: the dry-run branch traces it too, and a dry run that printed a
|
||||
# live token would be the worse leak of the pair.
|
||||
is "both traced URLs are redacted" "2" \
|
||||
"$(grep -c 'redact_url "\$p_repo"' $D)"
|
||||
"$(grep -c 'redact_url "\$p_clean"' $D)"
|
||||
# Not printing the token was never enough. p_repo is the URL as the endpoint
|
||||
# hands it over, credential inline; putting THAT on chezmoi's command line
|
||||
# publishes it to /proc/<pid>/cmdline, which every account on the box can read
|
||||
# for as long as the clone runs, and then into the clone's .git/config, which
|
||||
# keeps it. Only the split-out p_clean may reach an argv.
|
||||
is "the credential-bearing URL never reaches an argv" "0" \
|
||||
"$(grep -c 'chezmoi init.*\$p_repo' $D)"
|
||||
is "the token goes to a credential file instead" "1" \
|
||||
"$(grep -c "umask 077; printf '%s\\\\n' \"\\\$p_cred\" > \"\\\$PRIV_CRED\"" $D)"
|
||||
is "…at mode 600" "1" \
|
||||
"$(grep -c 'chmod 600 "\$PRIV_CRED"' $D)"
|
||||
# Exercise the real implementation lifted straight out of dotup. A copy of the
|
||||
# sed expression here would keep passing after someone edited the original.
|
||||
split() { p_repo=$1; eval "$(sed -n '/p_cred=\$(printf/p;/p_clean=\$(printf/p' $D)"
|
||||
printf '%s %s\n' "${p_cred:-<none>}" "$p_clean"; }
|
||||
is "split lifts the credential out of the URL" \
|
||||
"https://ben:deadbeefcafe@git.example.com https://git.example.com/x.git" \
|
||||
"$(split 'https://ben:deadbeefcafe@git.example.com/x.git')"
|
||||
# A port must survive into the credential line: git matches the store entry on
|
||||
# host AND port, so dropping :3000 would silently stop authenticating.
|
||||
is "…keeping the port" \
|
||||
"http://u:p@example.com:3000 http://example.com:3000/a/b.git" \
|
||||
"$(split 'http://u:p@example.com:3000/a/b.git')"
|
||||
# No userinfo means nothing to store and nothing to strip — ssh remotes and
|
||||
# unauthenticated https must pass through byte-identical.
|
||||
is "…and leaves a credential-free remote untouched" \
|
||||
"<none> https://git.example.com/x.git" \
|
||||
"$(split 'https://git.example.com/x.git')"
|
||||
is "…including scp-style ssh" \
|
||||
"<none> git@git.example.com:ben/x.git" \
|
||||
"$(split 'git@git.example.com:ben/x.git')"
|
||||
rd() { eval "$(sed -n '/^redact_url()/p' $D)"; redact_url "$1"; }
|
||||
is "redact_url strips userinfo" "https://<redacted>@git.example.com/x.git" \
|
||||
"$(rd 'https://ben:deadbeefcafe@git.example.com/x.git')"
|
||||
|
||||
Reference in New Issue
Block a user