I run autonomous AI coding agents. Agents push code, open pull requests, and create releases. To do any of that, they need access to GitHub. The question I kept running into was: how do you give an agent access to GitHub without also giving it your SSH key and your personal access token?
To solve this problem, I implemented ghbrk. It’s a credentials broker for both git and gh. It’s written in Rust and optimized for Linux environments.
The problem with direct credential access#
Coding agents are powerful because they have shell access. They can read files, run almost every command their user can run, and inspect environment variables. On Unix, credentials usually live in exactly those places: files and environment variables. GitHub is no exception. Your SSH keys sit in ~/.ssh, and your GitHub token is commonly passed through the environment.
Here is how those credentials leak to the LLM provider:
- An agent reads
~/.ssh/configor~/.ssh/id_rsato figure out how to push, and your private key ends up in the context window. - Or it runs
echo $GITHUB_TOKENto debug a failingghcall, and your token lands in the session transcript. - Or it uses a personal access token scoped to an organization, and that one token can push, open pull requests, and close issues across every repository it reaches.
None of these require a mistake. They are ordinary agent behavior: read what you need, inspect what’s broken, use the credential you’ve been given. The exposure is a structural property of how credentials are wired, not a failure mode you can prompt your way around.
Enter ghbrk: a broker that keeps credentials out of reach#
Privilege separation is the right model here: the agent must be able to run every operation it needs without being able to read the credentials behind them. That is the pattern behind ghbrk. A broker sits between the agent and GitHub, holds the credential, checks a policy, and returns the output. The agent never sees a key or a token.
The following flowchart illustrates how it works:
Agent
│ ghbrk git push / ghbrk gh pr create
▼
ghbrk daemon (holds your SSH key and token)
│
[check] (policy allows operation?)
│
├────────── allow → runs git / gh with credentials injected
│
└────────── deny → returns error, logs the decision
- Agents call
ghbrkexplicitly for remote operations. Local commands likegit statusandgit commitrun as usual, without going through the broker. - The daemon checks policy (a YAML file owned by root, not readable by the agent). The agent cannot see or modify what it is allowed to do.
- Credentials are injected at execution time. For SSH, the daemon starts a per-operation
ssh-agent, loads the key with a thirty-second TTL, and exposes a proxy socket. The child process gets cryptographic signatures back, never the key bytes. For HTTPS, a temporary askpass script prints the token and is removed when the operation completes. - Privilege is dropped before the child runs. The sequence is
setgroups, thensetresgid, thensetresuid, thenchdir. The ordering matters: you have to drop the group before the UID, becauseCAP_SETGIDis gone once the UID is dropped. - Every decision is logged to an append-only audit log in JSON Lines format, whether the operation was allowed or denied.
The caller’s identity is verified via SO_PEERCRED. The kernel reports the UID of the process on the other end of the Unix socket. The agent cannot forge this.
Fine-grained control from one policy file#
The usual way to limit what a credential can do is to issue many narrowly-scoped, short-lived tokens: one per repository or scope, each rotated before it expires. That gives you fine-grained control, but it also means issuing, distributing, and expiring a token for every agent and every task.
ghbrk gives you the same fine-grained control from one place. The daemon holds a single credential, and a YAML policy decides what that credential may do, per user, per repository, per branch, per operation.
rules:
- user: ferris
org: marconae
repo: ghbrk
operations: [push, fetch, pull, issue_create]
branches: ["feature/*"]
effect: allow
This rule lets the ferris agent push, fetch, pull, and open issues on marconae/ghbrk, but only on feature/* branches. Rules are evaluated first-match-wins, and if none matches, the request is denied. Built-in roles (read-only, write, admin) expand to their operation sets, and branch patterns support globs.
The file belongs to root. ghbrk allow appends a rule and hot-reloads the daemon without a restart, but it also requires root, so the agent has no path to widen its own access. To check a rule before it goes live, ghbrk explain git push origin main prints the decision without running the command.
Installation#
ghbrk ships with a one-line installation script. It installs the binary, the systemd daemon, a default-deny policy, and the agent configuration:
curl -fsSL https://raw.githubusercontent.com/marconae/ghbrk/main/install.sh | sudo bash
# Verify setup
ghbrk doctor
ghbrk doctor checks that the daemon is reachable, credentials exist with correct permissions, and the policy parses cleanly. Run it after any change to the policy or credential store.
The installer configures your agent automatically#
ghbrk ships with a small rules file (ghbrk.md). The installer links it from your user-level CLAUDE.md (for Claude Code) or AGENTS.md (for Codex and similar agents), so the agent is configured automatically. From these rules, the agent learns which operations need the ghbrk prefix:
# ghbrk — git/gh credential broker
- Every remote git/gh operation must be prefixed with `ghbrk`; local operations use plain `git`
- Remote (use `ghbrk`): `ghbrk git push`, `ghbrk git fetch`, `ghbrk gh pr create`, `ghbrk gh release create`, ...
- Local (plain git): `git status`, `git add`, `git commit`, `git diff`, ...
- Never call `git push`, `git fetch`, `git pull`, `git clone`, or any `gh` subcommand directlyGetting started#
👉 Get started in 2 minutes: marconae/ghbrk. Free, open-source, and MIT licensed.
ghbrk is one piece of the autonomous agent setup I’ve been building. For how I run agents unattended on a dedicated machine, see How to unlock coding agents that work autonomously. For the spec-driven workflow that keeps their changes structured and reviewable, see speq-skill.