Claude Code's hooks documentation says: "On most events, when a hook fails or times out, Claude Code still carries out the action. A policy hook with a wrong path or a crashing script then lets everything through."
That is a documented default, and it is not the only one like it. In each of the cases below, the policy text was fine. The place it ran, or what it did when it couldn't get an answer, was the problem.
This piece gives you a vocabulary for that (decision point versus enforcement point) and three properties to check on any placement: what it can see, who can reach it, and what it does when the decision doesn't arrive.
What is AI agent policy enforcement?
AI agent policy enforcement is the act of applying an authorization decision to an AI agent's action at a specific point in its path, such as inside the agent, at a gateway, on the host, or in the OS. Whether the policy holds depends less on its wording than on three properties of that point:
- What it can see: the tool call, the request, the command, or the resolved
execve(2)arguments. - Who can reach it: whether the agent can route around the point or edit it.
- What it does when no decision arrives: whether the action proceeds (fail open) or stops (fail closed).
What is the difference between a policy decision point and a policy enforcement point?
A policy decision point (PDP) evaluates policy and renders a decision. A policy enforcement point (PEP) is where that decision is applied to the request. The OASIS XACML 3.0 specification defines the PDP as "the system entity that evaluates applicable policy and renders an authorization decision," and the PEP as "the system entity that performs access control, by making decision requests and enforcing authorization decisions."
NIST uses the same pair in its zero trust architecture. SP 800-207 says access "is granted through a policy decision point (PDP) and corresponding policy enforcement point (PEP)," and draws them as one combined box. The split is logical. You can run both in one process, as an in-agent hook does.
The split matters because the two halves fail differently. A decision can be perfectly correct and never reach the thing that acts on it. That gap is where the hook defaults and the two CVEs below failed. The two GitHub issues show the other two ways an enforcement point goes wrong.
What can an enforcement point see?
An AI agent policy enforcement point can only apply policy to what it observes at its location in the flow, and everything behind that location is left unchecked. What it can see is the first of the three properties.
NIST puts the constraint plainly: "The PDP/PEP cannot apply additional policies beyond its location in the flow of traffic." Its zero trust principles are about "moving the PDP/PEPs closer to the resource," and it warns that everything between the last enforcement point and the resource is an "implicit trust zone" where "all the entities are trusted to at least the level of the last PDP/PEP gateway."
For an agent, "location in the flow" has a concrete meaning. A hook in the agent sees the tool call the agent decided to make. A gateway sees the requests that cross it. A process on the host sees what the host is about to run. A kernel-level control sees the resolved path and final arguments passed to execve(2), after the shell has expanded everything. Each can only apply policy to what it observes, and each leaves some trust zone behind it. Our earlier piece on command filtering and GTFOBins covers why the string a matcher reads and the thing that executes can differ.
Can the agent reach around or edit the enforcement point?
Reach is whether an AI agent can route around its policy enforcement point or modify it. Check both: a point the agent can bypass or edit constrains nothing.
In-agent enforcement shares a trust domain with the thing it constrains. Claude Code's hooks are the clearest example because they are documented in detail. A PreToolUse hook runs before a tool call executes and can block it. Hooks run with Claude Code's environment, which is the environment the agent works in. A reporter on anthropics/claude-code #11226 said in November 2025 that Edit and Write could modify ~/.claude/hooks/ despite a permissions.deny rule. The issue was closed by an inactivity bot in January 2026 as "not planned," without a maintainer response. Claude Code's permissions docs now list ~/.claude/ as a protected path: a write there prompts instead of being auto-approved, except under bypassPermissions.
Organizations can push managed hooks, and allowManagedHooksOnly blocks hooks from user, project, local, and plugin sources. That narrows who can add a hook. It does not move the hook out of the client.
Two decision sources can also compose wrongly at the enforcement point. In anthropics/claude-code #39344, a PreToolUse hook returning permissionDecision: "ask" silently overrode permissions.deny on version 2.1.84. A Claude Code collaborator wrote that it "was fixed in v2.1.101." The bug is patched. The class is not: the enforcement point is where every decision source meets, so it is where their interaction has to be right.
A gateway has reach limits of its own. It enforces policy on traffic that crosses it. Whether an actor can avoid it depends on whether the network and credentials force traffic through it, not on the gateway's policy.
What does the enforcement point do when the decision doesn't arrive?
Every AI agent policy enforcement point has a default for the case where it cannot load, reach, or evaluate a decision, and that default is part of the policy. Fail open means the action runs. Fail closed means it stops. This third property gets the least attention.
Claude Code's hook documentation spells its default out. On PreToolUse, exit code 2 blocks. Any other non-zero exit with plain text or nothing on stdout is a non-blocking error, and the action goes ahead. A timed-out command, http, or mcp_tool hook "doesn't block the tool call." Setting "onFailure": "block" on a command or http hook (Claude Code v2.1.295 or later) makes a failed hook block. A command or http hook written as a control but left without onFailure: "block" is a suggestion that works when everything is healthy.
Server-side enforcement points have the same property. CVE-2026-16584, in the AWS API MCP Server, is described in the GitHub advisory this way: "the per-request policy check is silently skipped for the lifetime of the process." The affected versions are 0.2.13 up to but not including 1.3.47, and 1.3.47 fixes it. The decision logic was not the issue. The policy data failed to load at startup, and the server kept serving without it.
CVE-2026-59822, a LiteLLM MCP endpoint flaw, is the same shape through a different door. CISA added "BerriAI LiteLLM Improper Authentication Vulnerability" to its Known Exploited Vulnerabilities catalog on 2026-09-02. The mechanism, per BerriAI's advisory, is that a fallback path could replace a failed key validation with an empty auth object instead of rejecting the request. Versions before 1.84.0 are affected.
In both cases the failure path was the weakest part of the control.
How do the four AI agent policy enforcement placements compare?
The four placements, in the agent, at a gateway, on the host, and at the OS or kernel, differ in what they see, who can reach them, and what they do when a decision is missing.
| Placement | Sees | Reach: can the agent get around or edit it? | Default when the decision is missing |
|---|---|---|---|
| In the agent (hook, deny rule, middleware) | The tool call and its text, as the client presents it | Same trust domain as the agent; a hook-file edit gap has been reported | Claude Code hooks: on PreToolUse, only exit 2 or a JSON deny blocks; other failures and timeouts continue through the normal permission flow unless onFailure: "block" is set on a command or http hook |
| Gateway or proxy | Requests that cross it | Only as strong as the network and credential path that forces traffic through it | Depends on the implementation; LiteLLM's MCP endpoint replaced a failed key check with an empty auth object (CVE-2026-59822) |
| Host (a daemon or wrapper on the machine) | What the host is about to run | Depends on what the agent can reach on the host | Depends on the implementation |
| OS or kernel | Resolved path and arguments at execve(2) | Depends on privileges and whether the control sits below the actor | Depends on the implementation |
Every row leaves at least one property open, and in three of the four it is the default. The useful work is knowing which one is open for the control you run.
Placement can also be a deliberate trade. Microsoft's Agent Governance Toolkit describes a stateless policy engine wired in through a framework's native extension points, such as callback handlers and middleware, and, for Azure Kubernetes Service, recommends deploying the engine as a sidecar container alongside the agents. The trade, in our reading: in-framework placement gets you low latency and framework context, and a sidecar adds separation.
What should you ask about your own AI agent policy enforcement?
Take any agent control you run and answer three things about it.
- Where does it run? In the agent's process, on a gateway, on the host, or below the OS? What can it see at that location, and what is left in the trust zone behind it?
- Who can write to it? Can the agent, or a repository the agent is working in, edit the policy or the process that enforces it? Can the agent stop the process?
- What happens when it can't decide? On a timeout, a crash, a failed fetch, an init error, or an unreachable decision service, does the action proceed or stop? Who chose that, and where is it written down?
If you can't answer the third question for a control, that is the finding. Neither a clean policy file nor a passing test with everything healthy tells you.
