Skip to content

fix(runner): git credential helper for proper repo authentication#1025

Merged
Gkrumbach07 merged 2 commits intomainfrom
worktree-better-git-auth
Mar 25, 2026
Merged

fix(runner): git credential helper for proper repo authentication#1025
Gkrumbach07 merged 2 commits intomainfrom
worktree-better-git-auth

Conversation

@Gkrumbach07
Copy link
Copy Markdown
Contributor

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

  • All 17 git identity tests pass (including 3 new ones)
  • Full runner test suite: 488 passed, 0 failures
  • 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

…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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Walkthrough

This 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 ensure_git_auth() function.

Changes

Cohort / File(s) Summary
Configuration & Operator
components/operator/internal/handlers/sessions.go
Added BACKEND_API_URL and PROJECT_NAME environment variables to the init container configuration to support backend integration.
Credential Helper & Platform Auth
components/runners/ambient-runner/ambient_runner/platform/auth.py
Introduced install_git_credential_helper() and ensure_git_auth() functions to manage Git credentials via environment variables rather than URL embedding. Helper script writes to /tmp/git-credential-ambient and configures git config credential.helper to parse tokens from env vars for GitHub/GitLab hosts.
Endpoint Refactoring
components/runners/ambient-runner/ambient_runner/endpoints/content.py, repos.py, workflow.py
Refactored Git operations to delegate credential setup to ensure_git_auth() instead of manually constructing URLs with embedded tokens. Updated logging to use redact_secrets() for sanitizing sensitive output. Added _sanitize_remote_url() to strip embedded credentials from existing remotes.
Shell Script Integration
components/runners/state-sync/hydrate.sh
Implemented Git credential helper setup and automated token fetching from backend API using BACKEND_API_URL, BOT_TOKEN, PROJECT_NAME, and SESSION_NAME when GITHUB_TOKEN/GITLAB_TOKEN env vars are not pre-set.
Test Coverage
components/runners/ambient-runner/tests/test_git_identity.py
Added comprehensive test suite TestInstallGitCredentialHelper validating credential helper script generation, chmod execution, git config invocation, and guard-based skipping. Enhanced existing tests with patches to verify helper installation during runtime credential population.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(runner): git credential helper for proper repo authentication' accurately summarizes the main change—implementing a git credential helper mechanism for authentication instead of embedding tokens in URLs.
Description check ✅ Passed The description clearly explains the problem (embedded tokens expire, init container lacks auth), solution (credential helper reading env vars), and implementation across multiple components, all directly related to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-better-git-auth

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 05c33d3 and 4d33425.

📒 Files selected for processing (7)
  • components/operator/internal/handlers/sessions.go
  • components/runners/ambient-runner/ambient_runner/endpoints/content.py
  • components/runners/ambient-runner/ambient_runner/endpoints/repos.py
  • components/runners/ambient-runner/ambient_runner/endpoints/workflow.py
  • components/runners/ambient-runner/ambient_runner/platform/auth.py
  • components/runners/ambient-runner/tests/test_git_identity.py
  • components/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>
@Gkrumbach07 Gkrumbach07 merged commit 797d7bf into main Mar 25, 2026
36 of 37 checks passed
@Gkrumbach07 Gkrumbach07 deleted the worktree-better-git-auth branch March 25, 2026 16:51
Gkrumbach07 pushed a commit that referenced this pull request Mar 25, 2026
…1025)

Replace custom _git_auth_env() in bridge.py with the platform's
ensure_git_auth() from auth.py (merged in #1025). Marketplace cloning
now uses the same credential helper as all other git operations.
jeremyeder pushed a commit to jeremyeder/platform that referenced this pull request Mar 26, 2026
…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>
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