ROTATE: a git.ltms.dev token for user ltms was exposed in an agent session, and any credential-resolving command on the Mac will do it again #182

Open
opened 2026-08-28 01:04:25 +02:00 by ltms · 1 comment
Owner

Action needed: rotate the git.ltms.dev credential stored in the Mac login keychain for user ltms. The value itself is deliberately not in this ticket.

What happened

While verifying the credential helper proposed in #177, the lead ran git credential fill against that helper on the Mac. The intent was to check the helper returned a synthetic test token. It did not. Git fell through to the next helper in the list — credential.helper = osxkeychain, set in global git config — which answered with the operator's real stored credential. The value was printed into the agent session's output.

Exposure — checked, not assumed

  • Not in the repo, any provisioned worktree, or any scratch file. Verified by content search across claude-bridge, .bridged-worktrees, and the session scratchpad: zero matches.
  • Not in any Gitea issue, pull request, or comment. The three written in that session (#175, #176, and the review comment on #177) do not contain it.
  • Not written to disk by the command itself — git credential fill only reads. It never called approve/store, so no new copy was created.
  • It is in one local session transcript on the operator's Mac.
  • It was transmitted as part of that agent conversation, so it left the machine. Treat it as disclosed, not merely as at risk.

Why it will happen again

This is a host property, not a one-off mistake. On this Mac:

  • global git config sets credential.helper = osxkeychain;
  • git treats credential helpers as an ordered list, not a single value;
  • so any command that resolves a credential — git credential fill, an HTTPS git push/fetch/ls-remote against a host with a stored credential — can surface a real secret, including inside an agent turn that believed it was using a synthetic one.

Nothing about such a command looks like it touches secrets. That is the same trap as #157, where git remote -v and git config --list print a token, and the same trap as the earlier host survey that printed one from a remote URL.

The related defect (fix is already in flight)

The helper in #177 emitted only username= and no password=. A partial answer is what makes git continue down the helper list. For a member that is worse than the leak #157 set out to fix: the origin looks clean, git remote -v shows nothing, the push succeeds — and it succeeds because the member silently used the operator's keychain credential instead of the repo-scoped WORKER_GITEA_TOKEN. Every test passes. Same shape as #175: the operation appeared to succeed, and nobody checked it succeeded for the right reason.

Being fixed on #177: emit both fields, reset the inherited helper list for the worktree so nothing can answer but ours, and drop the GITEA_TOKEN fallback (that name is not on the member allow list, so it is always blank).

Suggested guard, beyond rotating

Any test or agent step that resolves git credentials must isolate itself from the operator's configuration first:

GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null GIT_TERMINAL_PROMPT=0 git credential fill ...

Worth making that isolation a required part of the #177 tests, so they cannot pass or fail because of a real stored credential. That guard is in the brief already.

Related

  • #157 — the token in git remote URLs; this was found while fixing it.
  • #175 — asking a backend for something and never checking what you got.
  • #144 — the credential-exposure line this belongs to.
**Action needed: rotate the `git.ltms.dev` credential stored in the Mac login keychain for user `ltms`.** The value itself is deliberately not in this ticket. ## What happened While verifying the credential helper proposed in #177, the lead ran `git credential fill` against that helper on the Mac. The intent was to check the helper returned a synthetic test token. It did not. Git fell through to the next helper in the list — `credential.helper = osxkeychain`, set in **global** git config — which answered with the operator's real stored credential. The value was printed into the agent session's output. ## Exposure — checked, not assumed - **Not** in the repo, any provisioned worktree, or any scratch file. Verified by content search across `claude-bridge`, `.bridged-worktrees`, and the session scratchpad: zero matches. - **Not** in any Gitea issue, pull request, or comment. The three written in that session (#175, #176, and the review comment on #177) do not contain it. - **Not** written to disk by the command itself — `git credential fill` only reads. It never called `approve`/`store`, so no new copy was created. - **It is** in one local session transcript on the operator's Mac. - **It was** transmitted as part of that agent conversation, so it left the machine. Treat it as disclosed, not merely as at risk. ## Why it will happen again This is a host property, not a one-off mistake. On this Mac: - global git config sets `credential.helper = osxkeychain`; - git treats credential helpers as an ordered **list**, not a single value; - so **any** command that resolves a credential — `git credential fill`, an HTTPS `git push`/`fetch`/`ls-remote` against a host with a stored credential — can surface a real secret, including inside an agent turn that believed it was using a synthetic one. Nothing about such a command looks like it touches secrets. That is the same trap as #157, where `git remote -v` and `git config --list` print a token, and the same trap as the earlier host survey that printed one from a remote URL. ## The related defect (fix is already in flight) The helper in #177 emitted only `username=` and no `password=`. A partial answer is what makes git continue down the helper list. For a member that is worse than the leak #157 set out to fix: the origin looks clean, `git remote -v` shows nothing, the push succeeds — and it succeeds because the member silently used the operator's **keychain** credential instead of the repo-scoped `WORKER_GITEA_TOKEN`. Every test passes. Same shape as #175: the operation appeared to succeed, and nobody checked it succeeded for the right reason. Being fixed on #177: emit both fields, reset the inherited helper list for the worktree so nothing can answer but ours, and drop the `GITEA_TOKEN` fallback (that name is not on the member allow list, so it is always blank). ## Suggested guard, beyond rotating Any test or agent step that resolves git credentials must isolate itself from the operator's configuration first: ``` GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null GIT_TERMINAL_PROMPT=0 git credential fill ... ``` Worth making that isolation a required part of the #177 tests, so they cannot pass or fail because of a real stored credential. That guard is in the brief already. ## Related - #157 — the token in git remote URLs; this was found while fixing it. - #175 — asking a backend for something and never checking what you got. - #144 — the credential-exposure line this belongs to.
Author
Owner

Related finding from the same session, filed as #184: SSH_AUTH_SOCK blocking does not stop a member using the operator's forge ssh key, because that key is a readable, passphrase-free file on disk that ssh_config Includes point at.

Same shape as this ticket in one respect worth noting here: both are credentials reachable through a path the environment scrub never looks at. Rotation of the git.ltms.dev credential leaked here is still owed and unaffected by that.

Related finding from the same session, filed as #184: `SSH_AUTH_SOCK` blocking does not stop a member using the operator's forge ssh key, because that key is a readable, passphrase-free **file** on disk that `ssh_config` `Include`s point at. Same shape as this ticket in one respect worth noting here: both are credentials reachable through a path the environment scrub never looks at. Rotation of the `git.ltms.dev` credential leaked here is still owed and unaffected by that.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#182