wirenboard-agent-vm/images
Evgeny Boger 396011beb5 feat(phase6): gh/git credential injection + per-launch repo allow-list
In-VM agents need to push commits and create PRs to be useful for
actual development. Original Bash agent-vm did this with the host's
gh auth and a credential proxy. The rewrite has been silent on it.
This wires it end-to-end with a per-launch GitHub repo allow-list
enforced at the proxy hook.

**Credential injection.**
- `secrets::refresh_gh` shells out to `gh auth token` and stashes the
  result in the host-only `<state>.secrets/gh` (mode 0600). Returns
  None gracefully if `gh` isn't installed or the user isn't logged in
  — no warning, just no gh substitution.
- New constant `GH_TOKEN_PLACEHOLDER` ("MSB_PLACEHOLDER_GH_TOKEN_v1")
  is what the guest's gh / git tools see; the proxy substitutes it
  for the real bearer on outbound traffic.
- `secrets::write_guest_gh_config` writes `<state>/gitconfig` (with a
  credential helper that echoes the placeholder so `git push` sends
  it as Basic auth) and `<state>/gh-config/hosts.yml` (for the gh
  CLI). The launcher's patch builder symlinks both into the standard
  guest paths (`/root/.gitconfig`, `/root/.config/gh`).

**Allow-list.**
- `run::detect_github_repos_in_cwd` parses `git remote -v` in the
  project dir and pulls out `owner/repo` slugs from any
  github.com remote (https, ssh, ssh:// — `parse_github_slug`
  covers each form).
- New CLI flags on `claude/codex/opencode/shell`: `--no-git` (opt
  out entirely) and `--repo OWNER/NAME` (repeatable, widens the
  list beyond the cwd remote).
- The launcher gates `use_github` on having at least one allowed
  repo + `--no-git` not set; passes that into
  `secrets::refresh(state_dir, project_guest_path, use_github)`
  (now takes a 3rd arg).

**Proxy enforcement (the new bit).**
- New secret entry in `run.rs` for the gh token, with
  `inject_basic_auth(true)` so git's Basic-auth wire format
  (base64(x-access-token:placeholder)) is substituted correctly.
  Allowed hosts: api.github.com, github.com, codeload.github.com,
  raw.githubusercontent.com, objects.githubusercontent.com.
- New intercept rules for api.github.com on every method gh CLI
  uses (GET/POST/PATCH/PUT/DELETE), all with path "/" (prefix
  match — catches everything). Only added when a gh token was
  captured; otherwise the hook has nothing to forward with.
- New hook path in `intercept_hook::forward_github_api` (hook is
  now `async fn`): parses the buffered HTTP request, calls
  `github_path_allowed(path, allowed_repos)`, and either
  synthesizes a 403 (denied) or forwards via `reqwest` after
  replacing `GH_TOKEN_PLACEHOLDER` in Authorization with the real
  bearer from `<state>.secrets/gh`. Response is rebuilt as
  HTTP/1.1 bytes for the proxy to encrypt back to the guest.
- `github_path_allowed` covers `/repos/<owner>/<repo>/...` and
  `/user[/...]` (gh CLI needs the latter for auth-status and
  `gh repo list`; doesn't expose specific-repo state).

**Image (`images/Dockerfile`).**
- Adds gh from the official apt repo (gh.io packages signed
  by /etc/apt/keyrings/githubcli-archive-keyring.gpg).

**Verified end-to-end on this host:**
- `agent-vm shell` from a project with `origin =
  github.com/wirenboard/agent-vm`:
  - `gh api repos/wirenboard/agent-vm` reaches GitHub (Bad
    credentials — outer-bridge nested limit; placeholder is
    actually reaching GitHub via our forwarder).
  - `gh api repos/octocat/Hello-World` returns our clean 403 with
    the allow-list named in the message.
  - `gh api user` allowed (gh auth-status etc. work).
- With `--repo octocat/Hello-World`, the allow-list grows; the
  octocat path now reaches GitHub, anthropics/claude-code still
  returns 403 listing both allowed repos.
- No host token in the guest mount (`grep -ras "<real prefix>"
  /agent-vm-state /root` empty).

**Out of scope (per user direction):** GitHub App device flow,
per-repo scoped tokens, Copilot subcommand. `github.com` smart-
protocol git push is NOT path-filtered — token is substituted but
agents could in principle push to other repos via the git wire
protocol. Filtering that requires stream-aware intercept hooks
upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 05:46:15 +03:00
..
build.sh fix(setup): robust registry-state detection on Docker 29.x 2026-05-21 21:33:32 +03:00
Dockerfile feat(phase6): gh/git credential injection + per-launch repo allow-list 2026-05-24 05:46:15 +03:00