---
title: "Governing AI coding agents with Cerbos Synapse"
description: "Learn how to control every tool call with policy, enforce role-based access, and get full audit visibility using Cerbos Synapse"
author: "Alex Olivier"
date: "2026-03-27T14:49:00.000Z"
canonical: "https://www.cerbos.dev/blog/governing-ai-coding-agents-with-cerbos-synapse"
image: "https://stylish-appliance-1c1cc1c30d.media.strapiapp.com/Governing_AI_coding_agents_with_Cerbos_Synapse_1_201c0fc626.png"
tags: ["guide"]
source: "https://www.cerbos.dev/blog/governing-ai-coding-agents-with-cerbos-synapse"
---

# Governing AI coding agents with Cerbos Synapse

[Watch on YouTube](https://www.youtube.com/watch?v=2dVCE3OzEvE)

Your engineering team is probably already shipping faster with Claude Code. Agents are writing tests, refactoring modules, spinning up infrastructure. But here's a question worth asking in the standup: what exactly did those agents do yesterday?

Claude Code has a hooks system that lets you intercept tool calls before and after they happen. It's good. But it's a local config, per-developer, and there's no central way to enforce it. No audit trail. No policy-as-code. If your security or IAM team asks "can you prove no AI agent accessed production secrets last quarter?" you're stuck grepping local logs across 50 laptops.

Current IAM systems like access management, IGA, and PAM were not designed to evaluate decisions at the level of individual tool calls in real time. That layer of control is missing, and AI agents are already operating within that gap. We built a way to fill it.

<p>&nbsp;</p>

## What Synapse does here

[Cerbos Synapse](https://www.cerbos.dev/product-cerbos-synapse?utm_campaign=product_synapse_GTM&utm_source=blog&utm_medium=text&utm_content=&utm_term=) ships with a built-in Claude Code hook handler. Every time Claude Code tries to use a tool (read a file, run a command, write something), the hook fires, hits Synapse, and Synapse runs a Cerbos policy check against it.

<p>&nbsp;</p>

![Diagram (1).png](https://stylish-appliance-1c1cc1c30d.media.strapiapp.com/Diagram_1_3f75f7acc3.png)

<p>&nbsp;</p>


The mapping is clean:

- **Hook event name** (PreToolUse, PostToolUse, etc.) becomes the **resource kind**  
- **Tool name** (Bash, Read, Write, etc.) becomes the **action**  
- **Headers** you set in the hook config become the **principal** (who's doing this and what roles they have)  
- **The full hook payload** lands in **resource attributes**, so your policies can inspect file paths, command arguments, anything

Every decision (allow, deny, or no-match) gets logged. Connect Synapse to Cerbos Hub and those logs stream to a central store where you can search, filter, and build compliance reports.

Policies live in Hub too. Update a policy, it distributes to every Synapse instance automatically. No restarts. No config pushes. No Slack messages asking developers to pull the latest settings.

Here's a 3 minute walkthrough of the whole thing in action and a [detailed product page](https://www.cerbos.dev/ecosystem/claude-code?utm_campaign=product_synapse_GTM&utm_source=blog&utm_medium=text&utm_content=&utm_term=):

[Watch on YouTube](https://www.youtube.com/watch?v=2dVCE3OzEvE)

<p>&nbsp;</p>

## Setting it up

### 1\. Configure Synapse

Add the Claude route extension to your [Synapse ](https://www.cerbos.dev/product-cerbos-synapse?utm_campaign=product_synapse_GTM&utm_source=blog&utm_medium=text&utm_content=&utm_term=) config. If you're already running Synapse as your authorization gateway, this is 6 lines of YAML bolted onto your existing config.

<p>&nbsp;</p>

```
server:
  listenAddress: ":3594"

pdp:
  inProcess:
    hub:
      credentials:
        clientID: "${CERBOS_HUB_CLIENT_ID}"
        clientSecret: "${CERBOS_HUB_CLIENT_SECRET}"
        pdpID: "claude-demo"
    storage:
      driver: hub
      hub:
        remote:
          deploymentID: "${CERBOS_HUB_DEPLOYMENT_ID}"
    audit:
      enabled: true
      accessLogsEnabled: true
      decisionLogsEnabled: true
      backend: hub
      hub:
        storagePath: /tmp/cerbos-audit
    engine:
      defaultPolicyVersion: default

extensions:
  routeExtensions:
    claude:
      extension:
        extensionURL: "system://claude"
      routes:
        "/claude": ["POST"]
```

<p>&nbsp;</p>

The `system://claude` extension URL tells Synapse to use the built-in handler. The route maps `/claude` to POST requests. Synapse serves all route extensions under the `/ext/` prefix, so the full URL becomes `/ext/claude`.

<p>&nbsp;</p>

### 2\. Enforce hooks across every developer with server-managed settings

This is the critical step. Claude Code supports [server-managed settings](https://code.claude.com/docs/en/server-managed-settings): configuration that your organization pushes to every developer's machine via MDM (Jamf, Intune, etc.) or a managed config file. Settings delivered this way **cannot be overridden locally**. Developers can't remove the hook, can't point it somewhere else, can't skip the policy check.

This matters because if you rely on developers opting into the hook config via their local `.claude/settings.json`, someone will forget. Or remove it because it's "slowing them down." Or never set it up in the first place. Server-managed settings close that gap.

Your managed settings file contains the hook configuration:

<p>&nbsp;</p>


```json
{
    "hooks": {
        "PreToolUse": [
            {
                "matcher": "*",
                "hooks": [
                    {
                        "type": "http",
                        "url": "https://synapse.internal:3594/ext/claude",
                        "headers": {
                            "x-claude-user-roles": "marketing",
                            "x-claude-user": "${USER}"
                        }
                    }
                ]
            }
        ],
        "PermissionRequest": [
            {
                "matcher": "*",
                "hooks": [
                    {
                        "type": "http",
                        "url": "https://synapse.internal:3594/ext/claude",
                        "headers": {
                            "x-claude-user-roles": "marketing",
                            "x-claude-user": "${USER}"
                        }
                    }
                ]
            }
        ],
        "PostToolUse": [
            {
                "matcher": "*",
                "hooks": [
                    {
                        "type": "http",
                        "url": "https://synapse.internal:3594/ext/claude",
                        "headers": {
                            "x-claude-user-roles": "marketing",
                            "x-claude-user": "${USER}"
                        }
                    }
                ]
            }
        ]
    }
}
```
<p>&nbsp;</p>

Deploy this via your MDM tool of choice. On macOS with Jamf, it's a managed plist. On Windows with Intune, it's a registry key. The [server-managed settings docs](https://code.claude.com/docs/en/server-managed-settings) cover the specifics for each platform.

The `x-claude-user-roles` header is where it gets interesting. You can set it statically per team ("engineer", "marketing") or pull it dynamically from your identity provider. This becomes the principal role in Cerbos, so your policies can make different decisions for different groups of people. The `x-claude-user` header maps to the principal ID, so audit logs show exactly which developer's agent made each request.

<p>&nbsp;</p>


### 3\. Start in observe mode

Here's the part I think most people will skip and shouldn't.

Don't write any policies yet. Just deploy Synapse with the hook wired up and audit logging flowing to Hub.

With no policies defined, everything gets allowed by default. But every single tool call, every file read, every Bash invocation, all of it shows up in your Cerbos Hub audit logs. You get a complete picture of what Claude Code actually does across your org before you write a single rule.

This is your dry run. Let it cook for a week. Look at the data. You'll probably be surprised by what you find.

<p>&nbsp;</p>

### 4\. Write your first policy: protect sensitive files

After a week of observation, you know what Claude Code does. Now you can make informed decisions about what it should and shouldn't do.

Start with the universal rule: block sensitive file access for everyone, regardless of role.

<p>&nbsp;</p>

```
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  resource: PreToolUse
  version: default
  rules:
    - actions: ["Read", "Write", "Edit"]
      effect: EFFECT_DENY
      roles: ["*"]
      condition:
        match:
          any:
            of:
              - expr: R.attr.tool_input.file_path.startsWith("/etc")
              - expr: |-
                  R.attr.tool_input.file_path.matches(".*\\.env.*")
              - expr: |-
                  R.attr.tool_input.file_path.matches(".*secret.*")
              - expr: |-
                  R.attr.tool_input.file_path.matches(".*credentials.*")
      output:
        when:
          ruleActivated: |-
            {"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "Access to sensitive files is restricted by organization policy."}}

    - actions: ["*"]
      effect: EFFECT_ALLOW
      roles: ["*"]
```

<p>&nbsp;</p>

Push this to Hub. It distributes to your Synapse instances. Done.

Now when any user's agent tries to read `.env.production`:

<p>&nbsp;</p>

```
● Read(.env.production)
  ⎿  PreToolUse:Read hook returned blocking error
  ⎿  Access to sensitive files is restricted by organization policy.
```

The agent gets a clear reason why it was blocked and adapts. The user sees exactly what happened. The audit log captures the attempt, the denial, and the reason. Everything else still works normally.

<p>&nbsp;</p>

### 5\. Add role-based controls

Universal rules are a starting point. Real orgs need nuance.

Your engineers need Bash and write access for legitimate work. Your marketing team just needs to read code and search. You control this through the `x-claude-user-roles` header in the hook config, set per team through MDM or server-managed settings:

<p>&nbsp;</p>


```json
"headers": {
    "x-claude-user-roles": "engineer",
    "x-claude-user": "alex@company.com"
}
```

<p>&nbsp;</p>

Now add role-specific rules above the catch-all. Marketing gets denied Bash, Write, and Edit. Engineers get everything:

<p>&nbsp;</p>

```
    - actions: ["Bash"]
      effect: EFFECT_ALLOW
      roles: ["engineer"]
      output:
        when:
          ruleActivated: |-
            {"hookSpecificOutput": {"permissionDecision": "allow"}}

    - actions: ["Bash", "Write", "Edit"]
      effect: EFFECT_DENY
      roles: ["marketing"]
      output:
        when:
          ruleActivated: |-
            {"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "This tool requires the engineer role. Contact your platform team to request access."}}
```

<p>&nbsp;</p>

Same Cerbos policy language you'd use for any other authorization decision in your stack. If you're already using Cerbos for your application's access control, this is the same mental model applied to AI agents.

<p>&nbsp;</p>

### 6\. Go further with command-level guardrails

Allowing Bash for engineers doesn't mean allowing every command. You can inspect the actual command string and block the destructive ones, even for trusted roles:

<p>&nbsp;</p>

```
    - actions: ["Bash"]
      effect: EFFECT_DENY
      roles: ["engineer"]
      condition:
        match:
          any:
            of:
              - expr: R.attr.tool_input.command.matches(".*rm\\s+-[a-zA-Z]*r.*")
              - expr: R.attr.tool_input.command.matches(".*(curl|wget).*\\|.*(bash|sh).*")
              - expr: R.attr.tool_input.command.matches(".*git\\s+push.*(--force|-f\\s).*")
              - expr: R.attr.tool_input.command.matches(".*git\\s+reset\\s+--hard.*")
      output:
        when:
          ruleActivated: |-
            {"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "This command has been blocked by organization security policy."}}
```

<p>&nbsp;</p>

`git status` runs fine. `git push --force origin main` gets blocked. `rm -rf /` gets blocked. The policy uses regex patterns on `R.attr.tool_input.command` to draw a line between safe and dangerous. Add patterns as you discover new risks from your audit logs.

<p>&nbsp;</p>


## The operational model

Once this is running, the day-to-day looks like this:

* **Policies live in Cerbos Hub as code.** They're versioned, testable (Cerbos has a built-in test framework), and go through the same review process as any other code change. Want to allow a new tool? PR the policy, get it reviewed, merge it, and it's live across the org.

* **Audit logs flow to Hub automatically.** Every allow, every deny, every no-match. Searchable, filterable, exportable. When the compliance auditor asks what your AI agents had access to last quarter, you pull a report instead of running grep across 50 laptops.

* **The feedback loop is tight.** Developers hit a block, the reason is clear, they either use an alternative tool or request a role change. Platform team sees the denied requests in the audit log and can make informed decisions about whether to loosen or tighten policies.

<p>&nbsp;</p>


## Why this matters


This changes how you control agent behavior in practice. The impact shows up immediately in a few areas: security, compliance, different access types, and progressive rollout:

<p>&nbsp;</p>

![Governing AI coding agents with Cerbos Synapse.png](https://stylish-appliance-1c1cc1c30d.media.strapiapp.com/Governing_AI_coding_agents_with_Cerbos_Synapse_c9dd77b0fd.png)

<p>&nbsp;</p>

| Area | Details |
|------|--------|
| Security | AI agents can run arbitrary code and read any file on the machine. A single user action can trigger hundreds of authorization decisions as agents chain tool calls together. Without guardrails, a prompt injection or a confused agent can read your .env files, run destructive commands, or poke around in directories it has no business being in. Synapse puts a policy boundary around every action, with an external policy decision point evaluating each request. |
| Compliance | SOC 2, ISO 27001, and friends all want to know who accessed what and when. "An AI agent" isn't a satisfying answer for auditors. With Synapse, you have a timestamped, searchable log of every action every agent took, tied to the user who invoked it, with the policy decision and reason attached. Gartner expects 25% of enterprise breaches will trace back to AI agent abuse by 2028. This gets you ahead of that. |
| Different roles, different access | Your engineers and your marketing team shouldn't have the same agent permissions. Engineers need Bash and write access. Marketing needs read-only exploration. Synapse lets you express that in policy, not in per-machine config files that someone will inevitably forget to set up. |
| Progressive rollout | You don't have to boil the ocean. Start with observe mode. Look at the data. Write a few targeted policies. Expand from there. The observe-then-enforce pattern means you're making decisions based on real usage data, not guesswork. |
<p>&nbsp;</p>


## What's next

AI coding agents are going to get more capable, not less. They'll manage infrastructure, deploy services, interact with production systems. The user base will keep expanding beyond engineering. The teams that figure out governance early, while the IAM industry is still catching up, will have a head start when that happens.

Synapse gives you that governance layer without slowing anyone down. Same Cerbos policies your team already knows. Central management through Hub. Full audit trail. And it takes about 20 minutes to set up.

If you want to try it, [grab a Synapse licence](https://www.cerbos.dev/product-cerbos-synapse?utm_campaign=product_synapse_GTM&utm_source=blog&utm_medium=text&utm_content=&utm_term=) and follow the setup above. The Claude route extension is built in; you just need to turn it on.

## FAQ

### What is the actual risk with Claude Code agents?

Claude Code agents can read files, write changes, and execute commands on your machine. Without external control, those actions rely on the agent interpreting its own boundaries, which can lead to unintended access or execution.

### Why are Claude Code hooks not enough on their own?

Hooks are configured locally per developer. They are opt-in, can be modified or removed, and do not provide centralized enforcement or a reliable audit trail across an organization.

### How does Cerbos Synapse work with Claude Code?

Claude Code sends hook events for each tool call. Synapse receives those events, maps them to authorization requests, evaluates them against policies, and returns allow or deny decisions in real time.

### What is agentic AI governance and how do to control AI agents in production?

Agentic AI governance is the framework of controls, policies, and oversight used to manage what autonomous AI agents are allowed to do when they act on behalf of an organization.

Unlike traditional AI governance, which focuses on model behavior or outputs, agentic AI governance is about controlling actions at runtime. These systems can plan, decide, and execute tasks independently, so governance must define what they can access, what actions they can take, and how those actions are monitored.

In practice, it includes:

- Defining boundaries for what agents can access and execute
- Enforcing decisions externally, so agents cannot override or reinterpret them
- Monitoring and auditing behavior, with full visibility into what actions were taken and why
- Establishing accountability, so organizations can trace decisions back to users, roles, and policies

The key shift is this that agentic AI governance is not about guiding what the model says, but controlling what the agent does.
