All lessons › Function B — Securing AI Architectures › Securing the Architecture — Runtime and the Gateway
B3.7

The agent gateway — one choke point when you scale

B3
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/B3.7.mp4
Risk

Per-agent controls diverge as the fleet grows, and legacy downstreams force a static credential back into agent code.

Control

A single enforcement point holding identity, policy, egress, budget and audit — with the credential for legacy systems held there rather than by the agent.

The lab

Route every call through one gateway and show the same policy holding for agents that never implemented it.

◉Use case relevance

At one agent the controls live in the agent. At fifty, each team implements them slightly differently, none of them is audited, and the only honest answer to "is default-deny on?" is "in some of them".

At CyberTravels. cybertravels/gateway.py. Policy, budget, egress and return validation each live where it was convenient to write them, which is four places a second agent's author can skip. They move behind one entry point, and coverage() is the number that finds the agent that still has a direct route — every control passes its own tests either way.

◴What this lesson is — Day 0, Day 1, Day 2

Moving controls that each live where they were convenient to write behind a single entry point, and the number that finds the agent still holding a direct route. Plus the cost, stated rather than hidden: one choke point is a single point of failure and a queue.

Day 0 — why

Per-agent controls diverge as the fleet grows, and legacy downstreams push a static credential back into agent code.

Day 1 — how

One enforcement point holding identity, policy, egress, budget and audit together.

Day 2 — measure

Share of agent traffic that transits the gateway. Whatever does not is unenforced, whatever the policy says.

Measure this

what to measuredenominator or target
agent traffic that transits the gatewayshare; whatever does not is unenforced whatever the policy says

▦The framework, and how it works

   one agent                     fifty agents
   +--------------+              +-----+ +-----+ +-----+ +-----+
   | controls in  |              | a1  | | a2  | | ... | | a50 |
   | the agent    |              +--+--+ +--+--+ +--+--+ +--+--+
   +--------------+                 |       |       |       |
                                    +-------+---+---+-------+
                                                v
                                        +--------------+
                                        |   gateway    | identity, policy,
                                        +------+-------+ budget, egress, log
                                               v
                                            tools

   one place to enforce, one place to audit, one place to turn off

Mitigates: every threat in this chapter, at one enforcement point.

Everything in Chapters B2 and B3 works. The problem is where it lives.

At one agent, the controls sit in the agent, and that is fine. At fifty, it stops being fine for reasons that have nothing to do with security engineering:

  • Each team implements provenance, budgets and egress slightly differently.
  • Nobody can answer "is this control on, everywhere" without reading fifty repositories.
  • A new agent starts at zero and re-earns every control by hand.
  • Fixing a control means fifty pull requests and a migration.

The gateway is the same controls, moved to a point every call must pass through. It holds identity (B2.1–B2.3), policy (B3.1), egress (B3.3), budgets (B3.4) and audit (B2.7). An agent that implements none of them still gets all of them, because the enforcement is no longer the agent's responsibility.

It also solves a problem nothing else does: downstream systems that cannot consume a delegated identity. A legacy database or a vendor API that only understands a static credential forces that credential back into agent code — undoing B2.3 completely. The gateway holds it instead, authorises the user before the call, and presents the static credential onward. The agent never sees it.

The honest cost: the gateway is now a single point of failure and a very attractive target. It has to be operated accordingly.

What this control closes. Not a new control. The same controls, at a point every call passes through — and the only answer to a downstream that cannot consume delegated identity.

2 · The control

3 · Proving every call goes through it, as a skill

A gateway is only a choke point if nothing routes around it, and "nothing routes around it" is not a fact you can read off a route table. The procedure tests reachability from inside the deployment, covers the paths people forget — tool-initiated calls, background jobs, retries — and records each guardrail's action, because a filter set to observe is a filter that is switched on and stopping nothing. This is the file in this repository:

▶Real time execution as skill

The skill — skills/attestation/llm-gateway-guardrail-verifier/SKILL.md

name: llm-gateway-guardrail-verifier
description: >-
  Prove all model traffic leaves through the sanctioned gateway, including
  tool-initiated and background calls, and that guardrail policies are
  attached and enforced. Use to attest gateway routing, to check whether a
  provider endpoint is directly reachable, or to evidence which guardrail
  policies are switched on.
allowed-tools: Bash, Read

Llm Gateway Guardrail Verifier

Controls: Control 4 — gateway routing and guardrails

Confidence: HIGH only if egress is enforced below the application

An application-layer gateway configuration is a routing preference. An agent that can open a socket can bypass it by calling the provider directly. If the allowlist is not enforced at the network layer, downgrade this control to PARTIAL and say why.

When to use this

When a deployment routes model traffic through a gateway and the attestation wants to say so. Check first whether egress is enforced below the application: if it is not, an agent opens a socket and the gateway is advisory, and this control's confidence drops with it.

Procedure

  1. Test reachability, do not read configuration. From the deployment's network position, attempt to reach provider endpoints directly. Anything reachable that is not the gateway is a finding, regardless of what the configuration says.
  1. Cover the paths people forget. Tool-initiated calls, background jobs, scheduled tasks, retry paths and sub-agents. A gateway that fronts the main request path and not the batch job is a gateway with a hole in it.
  1. Confirm guardrail attachment and version. Record the guardrail ID and version actually attached to the route, not the one in the template.
  1. Enumerate enabled policies. Content filters, prompt-attack filtering, denied topics, sensitive-information filters, contextual grounding. Record whether each is applied on input, output, or both — input-only filtering is a common and quiet gap.
  1. Confirm the action. A filter set to observe rather than block is telemetry, not a control. Record the configured action per policy.

Example

Input — the fixture committed at the top of scripts/llm_gateway_guardrail_verifier.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.

Output — the opening lines of a real run:

   the intended call           ALLOWED
   unregistered agent          denied at identity
   verb not permitted          denied at policy
   exfiltration destination    denied at egress
   over the per-target ceiling denied at budget

audit entries written: 1
credential held by the agent: never - attached at the gateway

The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.

Output contract

{
  "deployment_id": "str",
  "gateway_enforced": true,
  "enforcement_layer": "network|application",
  "reachable_provider_findings": [{"endpoint": "str", "path": "direct|tool|background"}],
  "guardrail": {"id": "str", "version": "str",
                "policies": [{"name": "str", "applied_to": "input|output|both",
                              "action": "block|anonymize|observe"}]},
  "verdict": "PASS|PARTIAL|FAIL"
}

Failure modes

  • Reading the route table instead of testing reachability.
  • Missing the background path. Scheduled and tool-initiated calls are the ones that bypass the gateway in practice.
  • Recording a guardrail as enforced when its action is observe.

Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.

1In your AI agent — you are taught

The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.

Install it, once. From the folder you cloned. --all does every agent it finds on your machine:

python3 scripts/install_skills.py --all --lessons B3.7

Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.

Then pick b3-7-the-agent-gateway:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons B3.7type /b3-7-the-agent-gateway
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons B3.7type /b3-7-the-agent-gateway in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons B3.7type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons B3.7type $b3-7-the-agent-gateway, or run /skills

2As one Python command — you just run it

No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.

python3 scripts/lesson.py B3.7

3Read the code, and the difference — nothing runs

Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.

python3 scripts/checkpoint.py --at B3.7 --out work/cybertravels
python3 scripts/checkpoint.py --at B3.7 --diff

On Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.

Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.

Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.

If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.

✓What you just proved

Five calls hit one gateway. The intended call is allowed and audited with the human principal attached; the unregistered agent, the unpermitted verb, the exfiltration destination and the over-budget call are each denied at the first check that catches them — and the legacy credential is attached at the gateway, never held by the agent. The skill also reports its shape. Its confidence is HIGH only where egress is enforced below the application — the gateway is a choke point because the network makes it one, not because the SDK was configured to point at it, and an application-level base URL is a default, not a control.

✎Your turn

Count your agents. If it is more than five, work out how you would currently answer 'is egress control on for all of them' — and how long that would take.

Tools used

agentgatewayOPAKeycloak

Skill source: skills/attestation/llm-gateway-guardrail-verifier

← PreviousB3.6 · Human approval that survives volume