Playbook: Inline credential in a git remote URL — leak prevention & remediation
Trigger: A git remote URL on any checkout contains an inline credential —
https://x-access-token:<PAT>@github.com/...orhttps://user:pass@host/.... Any git command that prints the remote (git remote -v,git config --get remote.origin.url,git config --list, verbose git) then leaks the token verbatim into chat transcripts,agent_tasks.output, and queue result columns. Originating incident: #1994 (bms-4/opt/p24-infraorigin, observed during worker jobdev_r_worker_queue:387).
Related: docs/playbooks/static-api-key-incident-rotation.md (the authoritative
rotation procedure — start there for the PAT rotation itself).
Why this is dangerous
A repo-write PAT embedded in the remote URL is recoverable from any captured log
that ran a remote-printing git command. Worker agents print their command output to
agent_tasks.output (Supabase) and to chat transcripts, both visible to more people
than a local shell. One git remote -v is enough to expose the token.
Never reference the token VALUE in any output — key NAME / type only.
Layer 0 — Provisioning prevention (worker clones)
scripts/spawn-worker.sh clones each worker checkout from a credential-less URL
(https://github.com/<owner>/<repo>.git) and authenticates via the gh credential
helper rather than inlining the PAT in the clone URL. After cloning it pins the helper
into the new repo’s local config:
git -C "$WORKDIR" config --local --replace-all credential.helper ''
git -C "$WORKDIR" config --local --add credential.helper '!gh auth git-credential'So the worker’s own git fetch/git push resolve the token through
gh auth git-credential (gh’s stored login) and origin never carries a token — no
git error in a worker session can echo one. This is why the worker /tmp clones are
“credential-helper based” in Layer 1 below. Recurrence fixed in #2295 (original
provisioning inlined https://${GH_TOKEN_VAL}@github.com/...). Do NOT re-introduce an
inline-token clone URL in worker bootstrap.
Layer 1 — Prevention (guard hook)
.claude/hooks/pre-bash-safety.sh blocks remote-printing git commands when, and
only when, a configured remote actually carries an inline credential:
- Blocked patterns:
git remote -v|--verbose|show|get-url,git config --get remote.*.url,git config --get-regexp …remote,git config --list|-l, baregit ls-remote. - Detection is boolean (
grep -q) — the guard never prints the URL. - Clean credential-helper checkouts (e.g. the worker
/tmpclones) are unaffected.
To inspect remotes safely while a token is still inline:
git config --get-regexp '^remote\..*\.url$' | sed -E 's#://[^/@]+@#://<redacted>@#'Fleet-wide audit (find every leaking checkout, never printing a token)
Boolean scan of every .git/config on the host — reports paths only, never the URL.
Used in #2399 to find checkouts beyond /opt/p24-infra (five stale worker clones
under /home/claude-runner/p24-infra-* were still carrying inline tokens — one a
classic ghp_ PAT):
while IFS= read -r cfg; do
repo=$(dirname "$(dirname "$cfg")")
if git -C "$repo" config --get-regexp '^remote\..*\.url$' 2>/dev/null \
| grep -qE '://[^/@]+@[^/]'; then echo "INLINE-CRED: $repo"; fi
done < <(find /opt /tmp /home -maxdepth 4 -type f -name config -path '*/.git/config' 2>/dev/null)Layer 2 — Remediation (de-inline the remote)
Strip the inline token from the remote and rely on a git credential helper so the token is never stored in the URL or printed:
bash scripts/git-deinline-remote.sh [REMOTE_NAME] [REPO_DIR]
# defaults: REMOTE_NAME=origin, REPO_DIR=current directoryThe script is idempotent (a clean remote is left untouched), prints only the redacted URL, and verifies access still works through the credential helper. If the fetch test fails afterwards, configure a helper:
git config --global credential.helper store # token in ~/.git-credentials (chmod 600)
# or: gh auth setup-gitPreferred: de-inline AND wire the credential helper in one step (#2399)
git-deinline-remote.sh only STRIPS the token — on a checkout that authenticated
purely via the inline token (like /opt/p24-infra), stripping it breaks
git fetch/git push unless a helper is already configured. Use the combined
remediator instead — it de-inlines and pins the canonical
!gh auth git-credential helper (the same pattern spawn-worker.sh uses since
#2295), then verifies reachability. Idempotent, never prints a token:
bash scripts/setup-git-credential-helper.sh [REPO_DIR] [REMOTE_NAME]
# defaults: REPO_DIR=current directory, REMOTE_NAME=originThe token is resolved at git-time by gh auth git-credential from gh’s stored
login (or GH_TOKEN in the environment — which may itself be minted by
scripts/gh-app-token, the GitHub App token that replaced GH_PAT_ADMIN on
2026-06-30). Run it as the user that owns the checkout’s .git/config (for
/opt/p24-infra on bms-4 that is root).
On bms-4 /opt/p24-infra (the originating checkout)
/opt/p24-infra/.git/config is root-owned, so this must run as root (an
unprivileged worker cannot modify it). Server-operation playbook rules apply —
annotate the SSH command with # PLAYBOOK: git-remote-inline-token-leak.md:
ssh root@54.36.123.110 'cd /opt/p24-infra && bash scripts/setup-git-credential-helper.sh /opt/p24-infra origin' # PLAYBOOK: git-remote-inline-token-leak.mdStale worker clones under /home/claude-runner/p24-infra-* (#2399)
The deprecated pre-#2295 worker layout left abandoned per-job clones at
/home/claude-runner/p24-infra-<jobnum>, several still carrying inline tokens.
Run the fleet-wide audit above, de-inline any that remain
(git -C <dir> remote set-url origin https://github.com/radieu/p24-infra.git), and
consider removing the abandoned clones entirely once confirmed stale (no
index.lock, old .git mtime, no process cwd inside them).
Layer 3 — Rotation (HUMAN / credential-admin action)
De-inlining stops the leak going forward, but a token that was already exposed in
logs must be rotated. Follow docs/playbooks/static-api-key-incident-rotation.md:
- Rotate / regenerate the
radieu/p24-infrarepo-write PAT. - Distribute the new value everywhere it lives — SOPS
secrets/n8n-bms4.env.sops(GH_TOKEN), GitHub Actions secrets, live envs, containers, n8n credentials. - Re-point the bms-4
/opt/p24-infraremote at the rotated token via the credential helper (NOT inline) — see Layer 2. - Record the rotation in
docs/secrets-rotation-log.md. - Audit existing
agent_tasks.output/ queue result rows for captured token values where feasible.
Verification checklist
- Fleet-wide audit (above) reports zero
INLINE-CRED:checkouts -
git config --get-regexp '^remote\..*\.url$'(redacted) shows nouser:pass@ -
bash scripts/setup-git-credential-helper.shreports the remote token-free, helper pinned, and reachable - The guard blocks
git remote -von a checkout that still has an inline token - PAT rotated and distributed per Layer 3; rotation logged