Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gh-bridge

Run gh commands from inside a sandboxed environment that blocks api.github.com — by routing them through GitHub Actions over the one host that is allowed: github.com.

Built originally to work around the Cowork VM block documented in claude-code#37970, but it works anywhere you have git, curl, jq, a token, and a host that permits plain git push to GitHub.

How it works

  Sandboxed VM                 github.com                 Actions runner
  (api.github.com BLOCKED)     (ALLOWED)                  (full net access)
  ─────────────────────        ─────────                  ─────────────────
  gh-bridge api /user   ──►  git push req/<uuid>    ──►  on: push → run real gh
                                                         → capture stdout/stderr/exit
                                                         → push resp/<uuid>
  poll git ls-remote    ◄──                         ◄──
  shallow-clone resp/<uuid>
  print stdout, exit $code
  • Transport: git-over-HTTPS to github.com.
  • Compute: a private bridge repo running the bridge.yml workflow on the free Actions runner.
  • Wire format: two JSON blobs, schema/request.json and schema/response.json.

Typical round-trip: ~15–35 s per call (dominated by Actions queue + cold start). Not fast enough for interactive use; fine for CI/agent jobs that already think in seconds.

Quickstart

You need one private bridge repo per user/team.

1. Fork this repo (or gh repo create your-org/gh-bridge --private --clone and copy the files in).

2. On your VM, install the client:

git clone --depth 1 \
  "https://x-access-token:${TOKEN}@github.com/your-org/gh-bridge.git" /tmp/ghb
install -m 755 /tmp/ghb/bin/gh-bridge ~/.local/bin/

3. Set env vars and call it like gh:

export GH_BRIDGE_REPO=your-org/gh-bridge
export GH_BRIDGE_TOKEN=<fine-grained PAT: Contents read+write on gh-bridge>
export PATH="$HOME/.local/bin:$PATH"

gh-bridge api /repos/your-org/some-repo --jq .full_name
# → "your-org/some-repo"   (exit 0)

Exit code, stdout, and stderr match what gh would have produced.

What works

v0 ships with a single-path client and a single bridge.yml that runs whatever gh command you hand it, under the repo's auto-minted GITHUB_TOKEN.

  • ✅ Any gh api … call against the bridge repo itself
  • ✅ Any gh subcommand that's happy with GITHUB_TOKEN's default scope (contents, issues, pull-requests: write on the bridge repo)
  • ❌ Cross-repo / cross-org calls — GITHUB_TOKEN can't see them
  • ❌ User-scope calls like gh api /user

To widen the surface, add a user/org PAT as a repo secret and teach bridge.yml to prefer it over the default GITHUB_TOKEN. Planned for v0.1.

Environment variables

Var Required Default Notes
GH_BRIDGE_REPO yes owner/repo of the bridge repo
GH_BRIDGE_TOKEN yes PAT with Contents RW on the bridge repo
GH_BRIDGE_HOST no github.com For GHES deployments
GH_BRIDGE_POLL no 3 Seconds between ls-remote polls
GH_BRIDGE_TIMEOUT no 180 Max seconds to wait for a response
GH_BRIDGE_KEEP no 0 If 1, don't delete resp/<uuid>

Repo layout

bin/gh-bridge             POSIX bash client. Needs git, curl, jq, od, mktemp.
.github/workflows/
  bridge.yml              Actions runner. Triggered by req/** branch pushes.
schema/
  request.json            JSON Schema for the request wire format.
  response.json           JSON Schema for the response wire format.

Security notes

Honest about the threat model:

  • Token scope: use a fine-grained PAT limited to the single bridge repo with Contents read+write. Don't reuse your personal token.
  • Runner isolation: every request runs on a fresh Actions runner. Default GITHUB_TOKEN can only touch the bridge repo.
  • Replay protection: each request has a random 128-bit UUID and the runner refuses to process a branch whose name doesn't match the file's UUID. No signatures — there's nothing to forge inside the closed repo, but don't point a public bridge repo at untrusted branch pushes.
  • Response retention: req/* branches are deleted by the workflow on success. resp/* branches are not auto-pruned yet; set a repo Actions schedule to sweep them if you care.

Non-goals

  • Not a drop-in for every gh subcommand — some interactive flows (gh auth login, gh codespace ssh) don't round-trip through JSON.
  • Not low-latency.
  • Not a multi-tenant service. One user, one private bridge repo.

Contributing

v0 is deliberately minimal. PRs welcome for:

  • Broader command coverage + test fixtures
  • PAT-via-secret path for cross-repo / user-scope calls
  • Pruner for stale resp/* branches
  • GHES / Enterprise Cloud support hardening

License

MIT — see LICENSE.

Origin

Built at MSApps while wiring a Claude Code Cowork agent into GitHub and hitting the sandbox block. Shared publicly because someone else will hit the same wall.

About

VM↔API bridge: git-over-HTTPS transport + Actions runner for gh commands when api.github.com is blocked

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages