Skip to content

Scope the Engine's own git and gh credentials, so a wrong-repo write fails instead of being reported afterwards #679

Description

@serge-ivo

Follow-up to #676, which shipped the detection half in 7d246958. That change halts a run after a wrong-repo write and names the repository. It does not stop the write. This issue is the half that does.

The problem, measured

The Engine is a claude --dangerously-skip-permissions child process on the owner's machine, spawned at packages/browser-runner/src/coding/headless.ts:420 with env: mergeEnv(process.env, this.config.env) — the machine env inherited wholesale. It therefore reaches GitHub with the account's credentials:

  • git push is over SSH. Both remotes are git@github-personal:… (ProAgentStore/platform and proappstore-online/platform). Git ignores userinfo on an ssh URL, so no token of any kind can gate these pushes.
  • gh is authed from the keyring, account serge-ivo, scopes admin:org, delete_repo, gist, repo, workflow — every org the account can see.
  • Nothing injects a GitHub credential today. grep -rn "GH_TOKEN\|GITHUB_TOKEN" packages/browser-runner/src workers/api/src returns only redaction logic. grep -rni "credential\.helper\|GIT_CONFIG_COUNT\|ASKPASS\|insteadOf\|GIT_SSH_COMMAND" returns 0.
  • The Engine steers its own credential. Observed on two runs: export GH_CONFIG_DIR=~/.config/gh-personal; gh pr merge 111 --merge --repo proappstore-online/platform. Anything env-based is therefore a speed bump, not a boundary, unless the engine is also denied the ability to re-point it.

The tension that has to be designed around, not through

#676 item 2 wants writes scoped; item 3 wants reads broad, and gives a real example. A process holds exactly one gh token, so:

  • scoped GH_TOKENgh pr create -R other/repo fails at the credential (good) and gh pr view 138 --repo proappstore-online/platform fails too (breaks the protected pattern);
  • broad read-only GH_TOKEN → reads fine, but the engine cannot open a PR on its own registered repo.

Verified this is not hypothetical: the cross-repo read is the Engine running gh, not a platform tool. Instance a185b1db ("PAGS Coder"), session csess_42bdfe20, 2026-08-16T01:38:04Z, the Pilot's own instruction: Please run: `gh pr view 138 --repo proappstore-online/platform --json tit…` . The platform's github_read_pull was never called on 138 from that instance.

So the answer is a per-invocation credential chooser, not a single injected token.

Sketch

A runner-owned bin directory prepended to the Engine's PATH, holding a gh shim that classifies argv and routes the credential:

Plus, for git: GIT_CONFIG_* env injecting a credential helper, and url.<https>.insteadOf rules to force github SSH remotes through it.

Risks this carries, stated up front

  1. Forcing SSH → https means taking over the machine's git configuration. The owner uses a custom github-personal host alias; rewriting it affects their own reads, not just the agent's. This is the single biggest design risk and may be a reason to scope this issue to gh only and accept that git push stays ungated.
  2. gh's argv surface is large. Misclassifying a write as a read is a hole; default-denying unknown subcommands breaks reads. The classification needs to be a closed, reviewed vocabulary — the same shape as packages/browser-runner/src/coding/repo-write.ts.
  3. A shim on PATH is bypassable by absolute path (/opt/homebrew/bin/gh) or by the engine re-exporting its own env, as it already does. This raises the cost of a wrong-repo write; it does not make one impossible. Anything claiming otherwise in the UI would be worse than nothing.
  4. Engine-agnostic? Only Claude Code reports acts, but a PATH shim applies to every engine — so unlike A coding instance can push to and open PRs on any repo the user can reach, not just its registered one #676's gate, this one would work on Codex/Grok too. That is a point in its favour.

Acceptance

  • A write to an unregistered repo through gh fails at the credential layer, with the refused repo in stderr, and the run reports that rather than a generic failure.
  • A cross-repo gh pr view still succeeds — pinned by a test, because it is the property most likely to be lost.
  • Whatever remains bypassable is stated in coding_diagnostics alongside githubApp.writeScope, replacing the enforcement: "acts-observed-halt" value A coding instance can push to and open PRs on any repo the user can reach, not just its registered one #676 shipped.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1: blocks external usersMust be true before someone who is not the owner can run an agent (#68)backendBackend / Worker / API workcoderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, OverseerenhancementNew feature or requestrunnerpackages/browser-runner + packages/cli — ships only via an npm publishsecuritySecurity hardening / audit finding

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions