Approvals
Put a human between an agent and anything irreversible.
The approval loop
A push matching an approval rule returns HTTP 403:
{
"code": "REQUIRE_APPROVAL",
"requestId": "7f3a9c21-4b0e-4c55-9d1a-2e6f0b8c7a13",
"requiredApprovals": 2,
"message": "Review 7f3a9c21-… with the host approval CLI, then retry the same ref updates"
}Git clients may only show the status code. Inspect and decide on the trusted host:
npm run approvals
npm run approve -- <request-id-or-8-char-prefix>
npm run deny -- <request-id-or-8-char-prefix>Then retry the exact push from the agent.
What a grant covers
A grant applies to the exact ref updates (old and new object IDs), runtime context, and configuration digest. It:
- expires 15 minutes after the request,
- is consumed durably before forwarding — including when upstream fails,
- can't be shared by concurrent retries.
A new commit or a configuration change needs a new approval. Denial rejects that request only; it doesn't create a permanent rule. Pending approvals are capped (maxPending, default 50) — beyond that, 429 TOO_MANY_PENDING.
Reviewer identity
Every review carries a reviewer and a source decided by the broker, never the client:
| Source | Comes from | Verified? |
|---|---|---|
local:<name> | Host CLI (AGENTGATE_REVIEWER or $(whoami)) | No — anyone with admin socket access can type any name |
oidc:<email> | Approval web UI via your IdP | Yes — HMAC-signed by approval-web, ±60 s |
Require verified reviewers on sensitive rules:
{ "id": "review-main", "action": "git.push", "repository": "acme/payments",
"ref": "refs/heads/main", "effect": "approval", "approvals": 2, "reviewerSources": ["oidc"] }Security
Without reviewerSources: ["oidc"], anyone with socket access can supply both approvals of a two-person rule as two made-up local: names.
Self-approval and duplicates
- A review from the push's delegating
runtime.humanis rejected asSELF_APPROVAL. Identities are compared without scheme, case-insensitively, ignoring+tag;local:developercounts as[email protected]. - A second review from the same identity is rejected as
DUPLICATE_REVIEWER. - Any single deny closes the request as
denied.
Approval web UI
approval-web lists pending approvals and lets reviewers decide from a browser. It has no login of its own and must be reached only through oauth2-proxy.
- Trusts
X-Forwarded-Emailonly with a matching proxy secret (constant-time compare). AGENTGATE_APPROVAL_EMAIL_DOMAINSrestricts reviewer domains — set it and oauth2-proxy'semail_domains.- CSRF tokens are bound to reviewer, request, and action; POSTs require an exact
Origin. Content-Security-Policy: default-src 'none',X-Frame-Options: DENY, and HTML-escaped agent text.- Mounts broker state read-only; it only talks to
admin.sock.
Warning
Anyone holding the approval-web admin secret can approve as any oidc: reviewer. Treat approval-web and its host as part of the approval trust boundary.
Slack notifications
Set AGENTGATE_APPROVAL_WEBHOOK_URL_FILE (preferred) to post a Slack-compatible message once per new request. It includes the request ID, repository, action, identity, refs with short object IDs, count, expiry, and — with AGENTGATE_APPROVAL_PUBLIC_URL — a review link.
It never includes PR bodies, and it escapes &, <, > in agent-controlled fields so a branch name can't forge @channel. Notifier failures are logged and never delay the push.