Skip to content

Backups: keep URL credentials out of git argv and the clone's .git/config (T332) - #267

Merged
smarzban merged 6 commits into
mainfrom
tsk/t332-backups-keep-url-credentials
Oct 22, 2026
Merged

smarzban merged 6 commits into
mainfrom
tsk/t332-backups-keep-url-credentials

Conversation

@smarzban

@smarzban smarzban commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

T332 (from T317). A backup URL with credentials (https://me:TOKEN@host/x.git) was passed to git clone/ls-remote/remote set-url, so the token showed in ps while git ran and was written to the clone's .git/config under backup-repos/.

What changes

  • Every git call gets the URL without credentials (Remote::of, built on the existing without_credentials).
  • When an http(s) URL holds a login, calls that reach the remote (clone, fetch, push, the probe's ls-remote) add -c credential.helper= (reset) and -c credential.helper=!f() {…}; f, a shell helper that prints username/password from TSK_BACKUP_USERNAME/TSK_BACKUP_PASSWORD only when git asks for TSK_BACKUP_HOST (host:port as written). The secret travels in env only.
  • A login whose decoded user name or password holds a line break, CR, or NUL is refused before git runs (it would inject credential-protocol lines).
  • Login shapes, mirroring git: user:TOKEN@ → user / TOKEN; bare TOKEN@ → TOKEN / empty password. Userinfo is percent-decoded as git does (%40 → @).
  • ssh://git:pw@host reaches git as ssh://git@host (ssh never took the password from git anyway).
  • Old clones: sync_clone already ran remote set-url on every use; it now sets the clean URL, so .git/config is rewritten on next use with no user action.
  • URLs without credentials: nothing changes, users' helpers (gh, osxkeychain, manager) answer as before.
  • config.toml, *** redaction, redacted()/credentials(): untouched (PR Strip a bare https backup token when the URL does not start at the scheme #261 edits those; rebase expected).

Why a shell credential.helper and not GIT_ASKPASS

  • The helper string carries no path, so Git for Windows runs it through its own sh unchanged: no backslash/space quoting of a tsk.exe path.
  • No new tsk entry point (askpass mode in main), and it works under the existing credential.interactive=never (askpass is a prompt; a helper is not).
  • The reset keeps the user's helpers from answering with another account first, or storing the token in their keychain.

Username-only URLs (owner decision: option A)

https://alice@host/x.git is treated as a login like any other: git gets alice with an empty password, and the user's helpers are reset (never asked, never store). storage.md tells Azure DevOps / Bitbucket users with org@host URLs to leave the org@ part out so their credential manager logs them in.

Tests

Each new test was checked by reverting its fix by hand (results below).

Test Covers Fails when
clone_probe_fetch_and_push_log_in_through_the_helper_alone An in-test HTTP server (std TcpListener + git http-backend, 401 without Basic me:s3cr@et) serving a bare repo. Runs clone, backup add's probe (run_probe, ls-remote), push, then fetch of the existing clone + push. Asserts authorized upload-pack and receive-pack requests, two commits, no recorded git argv holds the token, no file under backup-repos/ holds it. push via plain git(); sync_clone fetch via git_command; clone via git_command; probe given target.location (all four verified)
a_login_with_a_line_break_never_reaches_git %0A, %0D, %00 in the login: run and probe refuse with "line break or NUL", no TAIL in either error, and no git call is made. the delimiter check removed
the_users_own_helpers_answer_only_for_a_url_without_a_login Real git credential fill with a user helper set as credential.helper and as credential.https://example.com.helper: a plain URL gets the user's login; a tokened URL gets only ours; a username-only URL gets alice + empty password, never the user's helper. the reset removed; the reset skipped for username-only logins
the_credential_helper_gives_the_login_to_its_host_only Right host gets the decoded login; another host gets nothing. login not passed
a_backup_never_puts_the_token_in_git_arguments_or_the_clone add → backup → list versions via insteadOf to a local repo (git never asks for a login here): argv and backup-repos/ token-free. URL not stripped
an_old_clone_holding_the_token_is_rewritten_on_next_use Old clone's .git/config rewritten. URL not stripped
a_remote_keeps_its_login_out_of_the_url Login shapes, percent-decoding. —
  • argv is recorded in-process (Command::get_args in run_git, a #[cfg(test)] thread-local like git_base's override), not a fake git on PATH: PATH is process-global under parallel lib tests.
  • credential.helper= (empty) clears URL-scoped credential.<url>.helper entries too: checked with git 2.54 and covered by the test above.
  • Not covered: the fetch inside backup_to_git's push-race retry (runs only after a rejected push); Windows (tests run on ubuntu and macOS; windows-gnu clippy passes); GitHub itself.
  • a_stored_error_never_holds_a_token now expects me@127.0.0.1 (git never sees the token). cli_backup::credentials_in_a_target_url_are_never_printed maps the clean URL with insteadOf.

Live smoke (macOS, isolated TSK_STATE_DIR)

Local git http-backend behind Basic auth (python, scratch), URL http://me:p%40ss%3AwTOKEN@127.0.0.1:18763/b.git:

  • tsk backup add, tsk backup, tsk restore all succeed; server log shows 401 then auth=ok on every ref and pack request; two commits pushed.
  • ps sampled every 20 ms during the run: the token appears only on the user's own tsk backup add … line; git, git-remote-http, and send-pack lines carry http://127.0.0.1:18763/b.git.
  • grep -r TOKEN backup-repos/: nothing. Origin is the clean URL.

Not run: Windows live (Git for Windows ! helpers are standard; windows-gnu clippy passes). GitHub live (brief forbids creating a repo), so bare TOKEN@ → empty password is untested against GitHub. Residual: an old clone's FETCH_HEAD/reflogs were not checked for a token (git anonymizes URLs there; backup-repos/ is 0700).

Checks

cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test green; cargo clippy --all-targets --target x86_64-pc-windows-gnu -- -D warnings green.

Docs: storage.md Credentials section; CHANGELOG Unreleased → Fixed.

@vercel

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
herdr-tsk Ready Ready Preview Oct 22, 2026 12:44pm UTC

@smarzban
smarzban merged commit c8e8e8e into main Oct 22, 2026
8 checks passed
@smarzban
smarzban deleted the tsk/t332-backups-keep-url-credentials branch October 22, 2026 20:35

This branch was successfully deployed

1 active deployment
Preview — 92733f54 Deployed Oct 22, 2026 by vercel[bot]
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