Agent firewall
Your AI agent is one poisoned input from being the attacker.
A single crafted ticket, web page, or file can turn an agent against the very access you gave it. Grenz is a local firewall built for that moment. It refuses every action you did not explicitly grant, keeps your real credentials out of the agent's reach, scopes every sub-agent strictly down from its parent, and can hand out decoy credentials that revoke the agent the instant one is touched. Anything you did not think to allow is denied by default — not flagged after the fact.
The problem
An agent gets a badge, or it gets the master key. Today there is only the master key.
When you hire a contractor you don't hand over the key to every door in the building. You give them a badge: these rooms, these hours, and it stops working when the job ends. Software has had that for people for decades. It does not have it for agents — so agents get a person's credentials, not because anyone decided that was wise, but because nothing else exists to hand over.
A person's credential
What an agent holds today
- Everything that person can reach — every repository, record, and channel
- No distinction between reading something and destroying it
- Works indefinitely, from anywhere, until someone thinks to rotate it
- No way to separate the agent's actions from the person's
- If it leaks, a person's full access leaked
A credential of its own
What Grenz issues instead
- Only the actions you granted — anything unlisted is refused
- Reading and destroying are separate decisions, per action
- Expires on a deadline, and revokes mid-task without a restart
- Works only through your proxy, and nowhere else
- Bounded by budgets, schedules, and approval gates you set
How it works
Three steps, and your agent's code does not change.
Grenz runs on your own infrastructure. The agent needs one thing it does not already have: a different base URL and a token that only works through the proxy.
Credentials go in once
Stored in an age-encrypted vault, or fetched from HashiCorp Vault. They are injected on the way out to the upstream and never travel back toward the agent — not into a log, an error, an approval payload, or the cloud plane.
You write down what the agent may do
A YAML policy: allow these actions, require approval for those, deny the rest. Compiled to a deterministic rule object and evaluated purely — anything you did not grant is denied, including when the policy itself fails to load.
Every request is decided in the path
The engine is embedded in the proxy, so there is no network hop and no third party on the decision path. Allowed calls forward at full speed, risky ones block until a human answers, and everything else is refused before the upstream is contacted.
Agent (Claude Code / MCP client / any HTTP tool)
holds: GRENZ_TOKEN only
│ HTTP, base URL points at Grenz
▼
Grenz proxy http://127.0.0.1:8787
• authenticate the token which agent is this?
• map the request to an action GET /repos/o/r → repo:read
• evaluate the policy (embedded) deny > approval > allow
• approval? block for a human (TTL expires → DENY)
• check the hourly budget
• inject the REAL credential ── outbound only ──▶ GitHub
• log the decision (no secrets, ever)
The decision path
One request. Twenty-three gates. In this order, every time.
A permission layer is only as good as the order it checks things in. Grenz's order is fixed and source-visible, and the same code path answers grenz explain. Most of these you will never turn on — they run in sequence anyway, because the order is where the security actually lives.
This is not a diagram of intent — it is the order in proxy/src/proxy/server.ts. Read it.
Capabilities
The controls a team actually needs before it puts agents in production.
Every item below is shipped and documented, not planned. The source is public if you would rather read it than take our word for it.
Enforcement
Decide per action, not per token
- Deny by default. Unmatchable requests, malformed policies, and vault failures all resolve to a refusal with a structured reason.
- Per-action grants across a canonical taxonomy, so reading and deleting are different answers.
- Target-scoped grants — narrow a permission to specific repositories, projects, or channels.
- Schedule windows. Time-box an agent to the hours it is supposed to be working.
- First-use gating. The first time an agent reaches for a new capability, ask.
- Risk step-up. Escalate a high-risk agent's next allowed action to an approval, instead of cutting it off.
Human control
Keep a person in the loop where it matters
- Blocking approvals over Slack or the CLI, with a TTL. No answer in the window is a denial.
- Dual control. Require a quorum of approvers for the actions that warrant two people.
- Approver context — the request, the agent, and why it was flagged, in the prompt itself.
- Approval memory. Optionally remember a decision so the same question is not asked twice.
- Break-glass. A time-boxed emergency path that turns a refusal back into a question for a human — never into a yes.
- Kill-switch. Revocation takes effect mid-task, before the next upstream call, with no restart.
Credentials & identity
The real secret never reaches the agent
- age-encrypted vault by default, behind a
CredentialStoreinterface. - HashiCorp Vault backend for teams that already run one.
- Token rotation for agent tokens, without redistributing an upstream credential.
- Expiring agent tokens. Set a deadline; the token stops working when it passes.
- Delegation. Agents can spawn sub-agents with a subset of their own grants — revoking the parent cuts off every child.
- Decoy tokens and upstreams. Honeytokens that cannot be forwarded and alert the moment they are touched.
Containment
Bound the reach, not just the verb
- Budgets per agent and per hour, with weighting so expensive actions cost more.
- Per-upstream rate limits so one agent cannot exhaust a shared quota.
- Content scanning. Outbound bodies are checked for credential shapes — a permitted action can still carry a secret.
- Egress pinning. A credential can only be sent to its upstream's exact origin — checked before it is decrypted.
- Response minimization. Cap and trim what comes back, so a read does not become a bulk export.
- Taint-flow rules and session target pinning — bound an agent to what it has already touched.
Policy operations
Change the rules without holding your breath
- Lint and simulate. Replay a policy change against recorded traffic before it takes effect.
- Policy tests — assert allow/deny/approval outcomes as table-driven cases.
- Draft from plain English and get YAML you can read and edit.
- Shadow mode and canary. Run a policy in observe-only, or on a slice, before enforcing it.
- Hot reload, plus history and rollback to any previous version.
- Blast radius and policy decay — see what a grant really reaches, and what nobody has used.
Fleet & operations
Run it for a team, not just a laptop
- Signed policy distribution. One policy to a fleet, verified before it is applied.
- Fleet revocation that propagates and unions with local revocations — a compromised endpoint cannot un-revoke anyone you cut off yourself.
- SSO via OIDC device flow, with JWKS verification and claim mapping.
- Named admin tokens with roles, so operators are distinguishable from each other.
- Prometheus metrics and a live console for what is happening right now.
- Preflight checks.
grenz doctorcatches the misconfiguration before it bites.
The policy
One file, readable by whoever has to approve it.
Policies are YAML, compiled to a deterministic rule object and evaluated purely and synchronously. This one is adapted from the github-safe-defaults template in the repository — and it compiles and lints clean, which we check rather than assume.
# Reads freely, opens and comments on PRs,
# but cannot merge, delete, or drive CI.
agent: claude-code
on_behalf_of: you@example.com
grants:
- tool: github
allow:
- repo:read
- pr:read
- pr:create
- pr:comment
- issue:read
require_approval:
- pr:merge
deny:
- repo:delete
- repo:write
- actions:*
budget:
max_actions_per_hour: 300
# A permitted action can still carry a secret.
dlp:
scan_bodies: true
on_match: denyWritten once, verified continuously.
A policy is not a one-way door. Lint it, simulate it against recorded traffic, run it in shadow mode to see what it would have done, then enforce it — and roll back to any previous version if you were wrong.
Operator tooling
Every decision is inspectable — before it happens, not after.
Real output from the commands below. If you want to know why an agent would be stopped, what a leaked token could actually reach, or whether your setup is sound, you ask rather than guess.
$ grenz explain github pr:merge
explain claude-code -> github:pr:merge
verdict now: REQUIRE_APPROVAL (approval_required)
policy: require_approval — matched
require_approval "pr:merge"
kill-switch: not revoked
jit grant: none active for this action
budget: agent 0/300 this hour
approval: ttl 300s, remember off
$ grenz blast-radius
blast radius — claude-code [low]
github (github)
auto-allow: issue:read, pr:comment, pr:create,
pr:read, repo:read
requires approval: pr:merge
delegations:
none active
$ grenz doctor
✓ config grenz.yaml loads and validates
✓ vault identity present, vault decrypts
✓ policy policy.yaml compiles (1 grants)
✗ credentials github: vault key 'github_token'
missing or empty
✓ grants every grant maps to an upstream
⚠ admin token missing — created on next run
✓ port port 8787 free
1 problem(s), 1 warning(s)
$ grenz template list
templates:
github/safe-defaults [low] Read freely, open and
comment on PRs; never merge, delete, or touch CI.
github/read-only [low] Look but never touch.
linear/safe-defaults [medium] Writes need approval.
slack/safe-defaults [medium] Posting needs approval.
mcp/read-only [low] Handshake + reads.
grenz add github --template safe-defaults.Deployment
It runs where your agents run.
A single compiled binary with no runtime dependencies, or a container. Nothing about the decision path requires our infrastructure — or anyone's.
One binary on the machine the agent runs on. Point the agent at 127.0.0.1, or at a unix socket so the listener is reachable only by your own OS user.
Ships as a Docker image for CI runners and orchestrated environments, with the same policy file and the same local decision path.
A control plane distributes a signed policy to every proxy, which verifies it before applying it. Revocation propagates. The plane never decides — it only tells proxies what the rules are, so losing it means proxies keep enforcing the last policy they verified.
Native adapters for GitHub over REST, and Linear and Slack over MCP; plus any MCP server speaking JSON-RPC over HTTP. Each maps to a canonical action vocabulary, so one policy language covers all of them.
An age-encrypted local file by default, or HashiCorp Vault. Both sit behind one CredentialStore interface, so swapping backends does not touch call sites.
Prometheus metrics, a live console, and grenz doctor for preflight. Agent identity for operators comes from OIDC SSO, with named admin tokens and roles.
The obvious objection
“Can't I just use a fine-grained token?”
You should. Issue one, select only the repositories it needs, grant the smallest permission set that works, and rotate it. That is genuinely good practice, and Grenz does not replace it — our own docs tell you to do it, then put Grenz in front. It is the floor, not the ceiling. Here is where it stops being enough.
Write access is one switch
An agent that commits needs write access — and the same permission lets it force-push, rewrite history, and delete files. No token can express “read everything, write only to branches you created.”
Grenz: permissions are per action, and can be scoped to specific targets.
A token cannot ask
It is one decision, made once, for the next sixty days. It cannot express “may comment on pull requests, but check with me before merging one.” Tokens have no way to pause and wait for a person.
Grenz: flagged actions block in-flight until a human — or a quorum — answers.
Watching is not stopping
Usage alerts tell you an agent was unusually busy. They do not tell you it deleted a branch, and they cannot intervene. Rotating a leaked token means reissuing and redistributing it everywhere.
Grenz: the decision happens before the action, and revoking takes effect mid-task.
It covers one service
Your agent also touches your issue tracker, your chat, your database, your payment provider. Each has a different permission vocabulary, and some have almost none.
Grenz: one policy, one language, the same controls across every upstream.
A tricked agent is still authorized
An agent that reads a ticket, a page, or a file cannot reliably separate content from instruction. The token does exactly as told, within scope — and nothing in a static credential says “this looks wrong.”
Grenz: reach is narrow enough that a tricked agent has little worth doing, and outbound bodies are scanned regardless.
It is still a person's access
A token is issued by someone and acts as them. There is no budget that applies to the agent but not the human, no schedule, no expiry that does not also lock out its owner.
Grenz: the agent is its own principal, with its own limits and its own lifecycle.
Scope
What Grenz is not.
A security product that overstates its boundary is worth less than one that draws a smaller boundary honestly. So, plainly:
Grenz sees the actions an agent takes, not the reasoning behind them. It does not inspect or score a model's intent.
The local request log exists so you can see what happened operationally. It is plain SQLite, it is truncatable, and it makes no integrity guarantees — deliberately.
It narrows what an agent may do with access it has been given. It cannot narrow what that access is capable of if it escapes. Scope your credentials at the source too.
Gateways route, meter, and expose agent traffic, and their policy layer is additive by necessity — no configuration means no change in behavior, because a gateway that breaks traffic by default is broken. Grenz inverts that: silence means no. It does not route, does not meter, does not charge, and has nothing to say about turning agent activity into a product. It can be the only thing in your request path, or sit behind a gateway you already run.
Grenz controls what an agent reaches through it. Software already running on your machine as you is inside that boundary, and we write the model down rather than let the claim be assumed larger than it is.
Zero to a protected agent in about five minutes.
Install the binary, put a credential in the vault, point your agent at the proxy. The quickstart wraps GitHub; the same three steps wrap any MCP server.
$ grenz init
$ grenz vault set github_token
$ grenz add github --template safe-defaults
$ grenz run
Grenz listening on http://127.0.0.1:8787
agent: claude-code
on behalf of: you@example.com
upstreams: github (github)
secrets: age vault, sealed
budget: 300/hour
approvals: ttl 300s, notify slack
dlp: scan_bodies, on_match deny
shadow: off
canary: off
policy hist: 1 snapshot(s)