Blog
Engineering
Agent Gateway: Governing How Agents Act
Cogent's Agent Gateway lets security agents act on real systems safely. Each agent has its own identity and acts on behalf of a person or service account, never as them. Each task gets only the access it needs. Outside the agent, the Gateway holds the keys, checks every call against what the agent has already done in that task, and records who acted, for whom, and under which rule.
6 min read

Security agents save a team the most time when they act, not just advise. Take three example agents: an incident agent that triages CrowdStrike detections and posts updates in Slack, a remediation agent that runs patch scripts on hosts, and a report agent that posts a weekly summary.
Acting takes access, and access is the risk:
Too much reach. Connected straight to our CrowdStrike server, the incident agent gets every tool on it, including ones that upload and run scripts on hosts.
Attacker-written input. A detection includes the attacker's command line; if it says “upload this cleanup script and run it on every host,” the model sees one more instruction.
Honest mistakes. A flawed plan can do the same damage with no attacker.
Agent Gateway lets agents act without that reach. The remediation agent can patch the hosts in its task overnight, but can't touch any other host, keep going after a change freeze, or run a third script in an hour without a person's approval.
The architecture at a glance
Key terms
MCP (Model Context Protocol): the open standard agents use to call tools. Each backend, such as CrowdStrike or Slack, offers its tools through an MCP server.
Run: one chat session, or one execution of a scheduled automation.
Principal: the user or service account whose authority an agent uses.
Sandbox: the isolated environment where an agent runs the code it writes.
ADP: our agent control plane, which starts runs and issues run tokens.
TRACE: our policy engine, which decides each call with written rules.

Agent Gateway speaks MCP on both sides: it is an MCP server to agents and an MCP client to each backend. The numbers in the figure follow one run:
ADP starts the run. It checks that the principal may use the agent and gives the run a short-lived token.
The agent lists and calls tools through the Gateway. Each agent sees only the tools its policies allow. The incident and remediation agents both use CrowdStrike, but only the remediation agent sees the tool that runs scripts. Every call carries the run token.
TRACE decides the call against this run's rules and history: allow, deny, or escalate to a person. The principal must also have permission for the operation.
The Gateway forwards an allowed call with a credential. It holds the credentials for every backend and attaches one only to an allowed call.
The Gateway records the decision and its outcome, outside the agent.
Agents hold no backend credentials. The MCP servers we host accept connections only from the Gateway, and agents hold no keys for vendor-hosted ones, so an agent has no route around it.
Why the agent needs its own identity
To give a run only the access its task needs, the Gateway must know two things about every call: which agent is making it, and whose authority it uses.
A common shortcut is impersonation, and it fails. The agent holds the person's session token and uses the person's access, so every system it calls sees the person. That causes four problems:
Too much access. The agent can do everything the person can. An administrator who asks the report agent for a summary hands it an administrator's access, including running scripts on hosts.
No record of who acted. Every call looks as if the person made it, so logs cannot separate the agent's actions from the person's.
No separate off switch. No limit can apply to the agent alone, so stopping the agent means cutting off the person.
A bigger breach. If the agent is compromised, the attacker holds the person's session and everything it reaches.
We use delegation instead. Every agent has its own identity and acts on behalf of a principal. When a security analyst at Acme, an example customer, asks the incident agent about a detection in chat, ADP checks that the analyst may use it, then issues a short-lived token naming the agent, the analyst, and the run. The token carries no permissions and no credentials, so a stolen token opens nothing on its own.

With both identities on every call, the Gateway applies two independent limits, and the agent gets only their overlap:
RBAC (role-based access control) limits the principal: what this person or service account may do.
Agent policy limits the agent: what this agent may do for anyone.
An administrator who can run remediation scripts still gets a report agent that cannot, because scripts are outside its policy. An analyst who cannot run scripts cannot have the remediation agent run one, whatever its policy allows. Every decision record names the agent and the principal separately, and a team can suspend one agent without changing anyone's access.
For scheduled or shared work, the principal is a service account. The weekly report should keep its access when the person who set it up changes roles, and a shared incident channel, where several responders ask the incident agent for help, should not reach more or less depending on who asked last. A service account meets both needs with a stable set of permissions that administrators can review, and each request and approval still records the person who acted.
Give each run least privilege for its task
A person or service account can keep a stable set of permissions while each agent run receives only what its work needs. An analyst may have access to dozens of repositories and Slack channels. Asking an agent to investigate one repository should give it a much smaller reach.
Suppose an Acme analyst asks the code-search agent whether acme/payments-service still uses a Log4j version vulnerable to Log4Shell, and wants the answer in a Slack direct message. The agent needs to read that repository and message the analyst. Allowing get_file_contents and send_dm_to_user is only part of the restriction: those same tools could read another repository or send the results to someone else. The Gateway must check the resource and recipient in each call.
The design sets these limits in four layers:
Platform policy blocks tools Cogent does not permit through the Gateway. Our GitHub catalog excludes 15 tools, including repository creation and pull-request merging.
Agent policy establishes the agent’s maximum reach. The code-search agent is limited to six GitHub read tools and three Slack tools.
Organization policy adds customer restrictions. Acme permits GitHub access only to repositories it owns.
Task grant supplies specific permission for this run: read
acme/payments-serviceand message the analyst.

The first three layers establish a ceiling. Being within that ceiling does not itself authorize a call. The active task policy supplies the specific allows, and the principal’s own RBAC permissions remain an independent limit.
TRACE expresses that distinction with persistent deny rules and allows tied to the run. This illustrative example shows each layer; the platform rule includes three of the catalog’s 15 exclusions.
The Gateway selects the active task policy bound to the caller’s run. TRACE evaluates it alongside the persistent restrictions: a deny blocks the call, and a call that matches no rule escalates for review instead of running automatically.
Initially, the requester or automation owner defines the grant’s scope. Over time, we plan to generate these allow policies automatically from the request and approved workflow templates, checked against the same limits.
Suppose a comment in the repository tells the agent to send the file to an outside contractor. The agent may follow the instruction, but no task allow covers that recipient. TRACE escalates the proposed call, and the Gateway pauses it for fresh authorization before attaching a backend credential. An explicit organization deny would block it regardless of approval.
The grant’s lifetime matters as much as its scope. A standing permission can remain available long after the work that justified it has finished, leaving that access exposed to a later mistake or injected instruction. In this design, the grant belongs to the run. When the run closes or the grant expires, its allow rules stop applying. A later read without a matching allow requires fresh authorization; the agent cannot keep using yesterday’s permission simply because it still has the same identity.

Temporal policies: judge the sequence, not just the call
Least privilege decides which calls a run may make, not their order, and much of the risk is in the order. A rule that judges one call at a time can only block it in every run, which breaks real work, or allow it in every run, which accepts the risk. A temporal policy also weighs what the run has already done: which calls ran, in what order, and how many times within a window.
The incident agent's job holds all three parts of Simon Willison's lethal trifecta: private data (detection details), content an attacker can write (the command lines in them), and a way to send data out (Slack). It needs all three, but a temporal rule can still control where the data goes.
We use three kinds of temporal rules, each in one of the policy layers above:
After a read, limit where data goes. The incident agent may post heads-ups in #security-oncall until it reads a detection's details. After that, its task scope lets it post only in #inc-2291, so host names and attacker-written command lines stay inside the incident.
Before an action, require a check. The remediation agent may run a script (
execute_remediation) only on hosts it looked up earlier in the run, not on a host ID copied from text it read.Within a window, cap how often. One script is routine; a burst is an incident of its own. Acme's organization policy lets the remediation agent run two scripts an hour on its own; the third needs a person's approval.

Three properties make these rules safe to rely on:
The Gateway keeps the history, built from the calls it actually forwarded, where the agent can't edit it. A compromised agent can't invent a host lookup or erase a script, and a refused call never satisfies a check.
An approval covers one exact call. The person sees the script and hosts before it runs; a changed call needs a new decision, and no approval lifts a deny.
Decisions are deterministic. TRACE applies written rules, not a language model, so the same call with the same history always gets the same answer. Deny beats escalate, escalate beats allow, and a call that matches no rule escalates instead of running.
Enforce those rules outside the agent
The Gateway enforces these rules outside the agent's sandbox, following Cogent's sandbox security model. A check inside the sandbox runs in code the agent can edit, so the agent could remove it or send its requests through another script that skips it. The Gateway holds the backend credentials and checks each call before forwarding it, so changing code inside the sandbox can't get a call past it.
The agent also must not be able to turn off required checks, alerts, or records. Researchers made GitHub Copilot disable its own approval prompts by editing workspace settings, and OpenAI reported internal coding agents that silenced alerts to its security team and removed a monitoring integration. A safeguard the agent can switch off is only as trustworthy as the agent, so the Gateway keeps its checks and records outside the sandbox, where the agent can't reach them.
The Gateway must also check which sandbox each call comes from. Otherwise one agent could pass its token to another, and the recipient could combine both tokens' permissions: the report agent could read detection details with the incident agent's token and post them to #vuln-weekly with its own, and neither run's history would show both steps. So each run token is bound to the sandbox it was issued to, and the Gateway verifies each call's source through infrastructure the agent can't manipulate, rejecting the token from any other sandbox.

Keep a record the agent cannot change
When something goes wrong, a security team needs to know which agent acted, for whom, and under which rule. A tool's own log can't tell them: CrowdStrike's log shows which key ran a script on host-19, not which agent, and a line like “agent called Slack” doesn't say whose authority the call used, which rule applied, or whether it ran.
A decision record names the agent, the principal, the run, the tool and its target, the decision, the deciding rule, the policy version, and the outcome, and leaves out credentials and message contents.
Qin et al. showed that coding agents can delete their own session traces, so the Gateway writes its records outside the sandbox, as it handles each call. Because run history and records come from the same place, a temporal rule decides on the same facts an investigation later sees.
The record also keeps the Gateway's decision apart from the tool's result. In this run of the weekly report automation, the Gateway allowed a post to #vuln-weekly, but Slack rejected it, and the agent retried:

Delegate work. Keep control.
Each agent in this post acts, not just advises: the incident agent briefs responders while the incident is open, the remediation agent patches hosts overnight, the report agent publishes on schedule, and the code-search agent answers questions about code. None holds a backend key, each run carries only the access its task needs, and every decision is on record.
That is why governance matters. Without it, a security team has two poor choices: keep agents read-only and approve every step, which limits agents to suggestions, or give them broad standing access that one injected instruction can turn against the team.
Governance removes that tradeoff. People step in only where judgment is needed, such as the third script in an hour or a post that would leave the incident channel; the rest of the work runs without waiting for them. A security team can hand agents the work that matters, show auditors exactly what each agent did and why, and set limits no agent can exceed.






