Start

Behind Grenz: an OpenClaw agent

OpenClaw runs on your own box, around the clock — it watches your repos, reads your files, runs commands, and acts on a schedule. To do that it authenticates to GitHub as you, and your real token sits in plaintext in ~/.openclaw/openclaw.json. That is exactly the shape Grenz exists for, and the same binary closes both sides of it.

Two problems, one wrap

An always-on agent is always acting. A prompt-injected task or a bad plan can merge, delete, or kick off CI while you are asleep — Grenz gates each action before it happens, so reads flow and irreversible calls stop for your approval.

And the credential is just sitting in a config file. An infostealer or a trojanized MCP server does not need to beat the model; it reads openclaw.json and walks off with your token. After Grenz, that file holds a GRENZ_TOKEN that does nothing off your own loopback — the real token never leaves the proxy.

What changes in OpenClaw

OpenClaw reaches GitHub through the remote GitHub MCP server. Before Grenz, that entry points straight at GitHub and carries your real token:

~/.openclaw/openclaw.json — before
{ "mcp": { "servers": {
  "github": {
    "url": "https://api.githubcopilot.com/mcp/",
    "transport": "streamable-http",
    "headers": { "Authorization": "Bearer ghp_yourRealPAT" }
  }}}}

After Grenz, the same entry points at your local proxy and carries a GRENZ_TOKEN. Two lines change; nothing else about OpenClaw does:

~/.openclaw/openclaw.json — after
    "url": "http://127.0.0.1:8787/u/github-mcp",
    "headers": { "Authorization": "Bearer grenz_…" }  # the GRENZ_TOKEN, not your real one

Everything below sets up the proxy that lives behind that URL.

1. Register the GitHub MCP upstream

Initialize a home outside your repos (export GRENZ_HOME="$HOME/.grenz" && grenz init — it prints your GRENZ_TOKEN once), then add the GitHub MCP server as an upstream:

~/.grenz/grenz.yaml
upstreams:
  github-mcp:
    type: mcp
    base_url: https://api.githubcopilot.com/mcp/
    credential: github_pat  # vault key
shell
printf %s "$GITHUB_PAT" | grenz vault set github_pat

From here the token lives only in the age-encrypted vault, decrypted inside the proxy and injected onto the outbound request to GitHub. OpenClaw never sees it again.

2. Grant the calls it may make

The GitHub MCP server exposes its actions as call:<tool> — so you gate by tool name. Reads flow, writes wait for a human, destructive calls are refused:

~/.grenz/policy.yaml
grants:
  - tool: github-mcp
    allow: [session:*, notify:*, tools:list, resources:list,
            call:get_*, call:list_*, call:search_*,
            call:add_issue_comment, call:create_issue]
    require_approval: [call:create_pull_request, call:merge_*,
                       call:update_*, call:create_or_update_file]
    deny: [call:delete_*, call:*_workflow]  # no deletes, no CI dispatch

Not sure what the server exposes? Run in --shadow for a while — every call the agent attempts is logged with the action Grenz derived, and grenz policy shrinkwrap then tightens the allow list to what was actually used.

3. Route approvals to your phone (Relay)

This is the step that makes approvals work on a headless box. When a call blocks for a human, there is no one at that machine's keyboard — so the proxy sends the blocked action out to the relay, you approve it from Slack, and the verdict comes back before the approval's TTL expires.

~/.grenz/grenz.yaml
relay:
  url: https://relay.grenz.dev/api  # hosted relay; the URL ends in /api
shell
printf %s "$RELAY_TOKEN" | grenz vault set relay_token

Only the action's metadata — agent, tool, action, target, the same fields a human approver sees — leaves the box; the token never does. The relay holds no credentials and makes no decisions. If it is unreachable, the approval simply expires and the action is denied. A plain Slack webhook notifies you but cannot carry the verdict back, which is why a headless agent needs the relay — when both are set, the relay wins. relay.grenz.dev is the hosted relay; you can also self-host your own and point url at it (it must end in /api).

4. Run it, point OpenClaw at it, prove it

Start the proxy with grenz run — the banner reads notify: relay (…) when approvals will route to your phone. Make the two-line edit to openclaw.json from the top of this page and restart OpenClaw. Then check the file you just changed:

shell
grenz scan ~/.openclaw/openclaw.json

Before the swap, scan flags the plaintext ghp_… an infostealer would harvest and exits non-zero. After it, there is nothing left to take. Now ask OpenClaw to merge a pull request: the call blocks, Slack lights up your phone, and the merge happens only if you tap Approve. Ask it to delete a file and it is refused before the request ever leaves your machine.

Local (stdio) MCP servers. Grenz fronts a network endpoint, not a subprocess — it cannot sit in front of a command-based stdio server. Use the server's HTTP/remote variant (as with the GitHub MCP server here) so there is an endpoint to point at the proxy. OpenClaw's Slack, Linear, or any other streamable-HTTP tool wraps the same way — see wrapping an MCP server.