fix(runner): git credential helper for proper repo authentication#1025
fix(runner): git credential helper for proper repo authentication#1025Gkrumbach07 merged 2 commits intomainfrom
Conversation
…ding tokens in URLs Previously, git tokens were embedded directly in remote URLs during clone (https://x-access-token:TOKEN@github.com/...), which meant expired tokens persisted in .git/config and broke subsequent git operations. The init container also had no auth at all, failing on private repos. This introduces a git credential helper that reads GITHUB_TOKEN/GITLAB_TOKEN from environment variables at git-operation time. Tokens are refreshed each turn by populate_runtime_credentials(), and the credential helper picks them up automatically without touching remote URLs. Changes: - Add git credential helper script + install_git_credential_helper() in auth.py - Add ensure_git_auth() to consolidate token-setting + helper install pattern - Remove token-in-URL injection from repos.py, workflow.py, content.py - Add _sanitize_remote_url() to strip tokens from repos cloned by old code - Update hydrate.sh to install credential helper and fetch tokens from backend API - Pass BACKEND_API_URL and PROJECT_NAME to init-hydrate container in operator - Use existing redact_secrets() for error message redaction - Guard install_git_credential_helper() to run once per process Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WalkthroughThis pull request introduces a centralized Git authentication mechanism that replaces embedding credentials directly in clone URLs. It adds a Git credential helper that dynamically fetches credentials from environment variables, backed by backend API integration to retrieve tokens when needed, while refactoring all Git operations across endpoints to use the new Changes
Sequence Diagram(s)sequenceDiagram
participant Endpoint as Git Endpoint<br/>(content/repos/workflow)
participant Auth as ensure_git_auth()
participant Helper as Git Credential Helper
participant Git as Git Command
participant Backend as Backend API
Endpoint->>Auth: ensure_git_auth(github_token, gitlab_token)
Auth->>Auth: Set GITHUB_TOKEN/GITLAB_TOKEN in os.environ
Auth->>Helper: install_git_credential_helper()
Helper->>Helper: Write script to /tmp/git-credential-ambient
Helper->>Helper: chmod +x and git config credential.helper
Endpoint->>Git: git clone <remote_url> (no embedded creds)
Git->>Helper: Request credentials via get flow
Helper->>Helper: Parse host from URL
alt Host matches github*
Helper->>Helper: Emit password from GITHUB_TOKEN
else Host matches gitlab*
Helper->>Helper: Emit password from GITLAB_TOKEN
else Token not in env
Helper->>Backend: Fetch token via BACKEND_API_URL
Backend-->>Helper: Return token
Helper->>Helper: Emit password from fetched token
end
Helper-->>Git: Return protocol/host/username/password
Git-->>Endpoint: Clone succeeds with dynamic credentials
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/runners/ambient-runner/ambient_runner/endpoints/repos.py`:
- Around line 329-337: The fresh-clone path currently skips cleaning
credentialized remote URLs, so if git_url contains embedded credentials they get
written into origin; update the fresh-clone branch where clone_url/git_url is
used (same area that calls ensure_git_auth and sets clone_url) to call the
existing _sanitize_remote_url(repo_final) after cloning (or before setting
origin) so the repository config is sanitized just like the repo-exists branch;
apply the same fix pattern in the comparable handlers that use clone logic in
content.py and workflow.py to call _sanitize_remote_url for repo_final after
clone.
In `@components/runners/ambient-runner/ambient_runner/platform/auth.py`:
- Around line 482-493: The case block that matches hosts using substring
patterns "*github*" and "*gitlab*" (inside the case "$HOST" ... esac and the
printf branches that use GITHUB_TOKEN/GITLAB_TOKEN) should be replaced with an
allowlist/exact-host check: introduce a trusted hosts list (e.g., TRUSTED_HOSTS)
or read the exact GitLab host from credential metadata and only return tokens
when HOST exactly equals (or matches an anchored regex for) an entry in that
allowlist; update both the case "$HOST" block in ambient_runner/platform/auth.py
and the duplicated logic in components/runners/state-sync/hydrate.sh so you no
longer rely on substring matching but on explicit trusted host comparison before
emitting the credential printf lines.
- Around line 510-523: The code currently sets _credential_helper_installed =
True regardless of git config success; change the subprocess.run call that
configures git (the call that uses ["git", "config", "--global",
"credential.helper", _GIT_CREDENTIAL_HELPER_PATH]) to capture the result (e.g.,
assign to result) and check result.returncode (or use check=True) before setting
_credential_helper_installed and logging success; if the returncode is non-zero,
log a detailed error via logger.warning or logger.error including
result.stdout/stderr and do not set _credential_helper_installed so the install
can be retried (references: _GIT_CREDENTIAL_HELPER_PATH,
_GIT_CREDENTIAL_HELPER_SCRIPT, _credential_helper_installed, logger,
subprocess.run).
- Around line 526-539: ensure_git_auth currently mutates process-global
os.environ causing cross-request token leakage; change it to avoid persistent
env mutation by temporarily providing tokens only to the
credential-install/action that needs them: in ensure_git_auth (and any call
sites) do not write GITHUB_TOKEN/GITLAB_TOKEN into os.environ permanently—either
build a copy of os.environ with the provided tokens and pass that environment to
install_git_credential_helper (or to the subprocess that runs git) or use a
short-lived context that sets the keys and restores previous values immediately
after install_git_credential_helper returns; ensure that when github_token or
gitlab_token is None you do not leave prior values in the process env and that
any previous values are restored.
In `@components/runners/state-sync/hydrate.sh`:
- Around line 215-238: The current block only executes when both GITHUB_TOKEN
and GITLAB_TOKEN are empty; change it to fetch each provider independently by
checking each token separately (use GITHUB_TOKEN and GITLAB_TOKEN checks) and
only require BACKEND_API_URL and BOT_TOKEN for each fetch; keep using CRED_BASE,
the curl calls (GH_RESP/GL_RESP) and parsing with jq to set
GH_TOKEN/GITHUB_TOKEN and GL_TOKEN/GITLAB_TOKEN respectively, so if one token is
present the other will still be retrieved from the backend.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 682f80b1-26fe-495c-bb12-2c245a86ffdf
📒 Files selected for processing (7)
components/operator/internal/handlers/sessions.gocomponents/runners/ambient-runner/ambient_runner/endpoints/content.pycomponents/runners/ambient-runner/ambient_runner/endpoints/repos.pycomponents/runners/ambient-runner/ambient_runner/endpoints/workflow.pycomponents/runners/ambient-runner/ambient_runner/platform/auth.pycomponents/runners/ambient-runner/tests/test_git_identity.pycomponents/runners/state-sync/hydrate.sh
- Sanitize remote URL after fresh clone too (not just existing repos) - Check git config return code before setting install guard flag - Fetch GitHub/GitLab tokens independently in hydrate.sh (don't skip one provider just because the other is already set) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…bient-code#1025) ## Summary - **Problem**: Git operations in sessions fail because tokens are embedded in remote URLs (expire/go stale) and the init container has no auth at all for private repos - **Solution**: Git credential helper that reads `GITHUB_TOKEN`/`GITLAB_TOKEN` from env vars at operation time — tokens refresh automatically, remote URLs stay clean - **Init container**: Now fetches tokens from backend API before cloning, using the same credential endpoint the runner uses at runtime ## Changes | Component | Change | |-----------|--------| | `auth.py` | New `install_git_credential_helper()` (guarded, once per process) + `ensure_git_auth()` wrapper | | `repos.py` | Stop embedding tokens in clone URLs; add `_sanitize_remote_url()` for legacy repos | | `workflow.py` | Stop embedding tokens in workflow clone URLs | | `content.py` | Stop embedding tokens when configuring remotes | | `hydrate.sh` | Install credential helper + fetch tokens from backend API before cloning | | `operator/sessions.go` | Pass `BACKEND_API_URL` and `PROJECT_NAME` to init-hydrate container | | `test_git_identity.py` | New tests for credential helper install, guard flag, error handling | ## How it works ``` git clone https://github.com/org/private-repo ← clean URL, no token ↓ git invokes credential helper (/tmp/git-credential-ambient) ↓ helper reads $GITHUB_TOKEN from env (refreshed each turn by populate_runtime_credentials) ↓ returns credentials in git protocol format → auth succeeds ``` ## Test plan - [x] All 17 git identity tests pass (including 3 new ones) - [x] Full runner test suite: 488 passed, 0 failures - [x] Operator Go code compiles cleanly (`go vet ./...`) - [ ] Manual: create session with private GitHub repo → verify clone succeeds - [ ] Manual: verify git push/pull work after token refresh - [ ] Manual: verify session resume with previously-embedded-token repo gets sanitized 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Ambient Code Bot <bot@ambient-code.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
GITHUB_TOKEN/GITLAB_TOKENfrom env vars at operation time — tokens refresh automatically, remote URLs stay cleanChanges
auth.pyinstall_git_credential_helper()(guarded, once per process) +ensure_git_auth()wrapperrepos.py_sanitize_remote_url()for legacy reposworkflow.pycontent.pyhydrate.shoperator/sessions.goBACKEND_API_URLandPROJECT_NAMEto init-hydrate containertest_git_identity.pyHow it works
Test plan
go vet ./...)🤖 Generated with Claude Code