const {P,H2,H3,Callout,Code,Ul,Related,PostHead}=window;
function Article(){
return <>
<PostHead cat="/CONCEPT" date="2026-07-13" title="Four gates: allowlist, schema, sandbox, confirm" lede="If an agent can call tools, a scolding system prompt is not a control. Name the four gates or admit you have a chat box with side effects."/>
<P>We keep seeing the same product: a model, a bag of tools, a long policy paragraph. Jailbreaks that only change the tone are a content problem. Jailbreaks that emit a tool call are an incident. The difference is whether a broker sits in front of the tools.</P>
<P>This is the same thesis as <a href="post-loops.html">prompts → loops</a>. Here are the four gates we look for on every agent audit.</P>
<H2>1. Allowlist</H2>
<P>The model does not get argv. It gets a named set of tools. “Shell” is not a tool you give a customer-facing bot. “SearchCorpus” is. If the list can be extended at runtime by a retrieved document or a user message, you do not have an allowlist. You have an installation vector.</P>
<H2>2. Schema</H2>
<P>Each tool takes a JSON schema the broker validates. Extra fields dropped. Types coerced or rejected. A string that looks like <span style={{fontFamily:'var(--font-mono)',fontSize:13}}>cmd</span> does not become a shell because the model asked. If you parse arguments with “the model usually gets it right,” that is not a schema.</P>
<H2>3. Sandbox</H2>
<P>The tool runs as a principal you can name: a service account, a container, a network policy. It cannot see other tenants. It cannot reach metadata endpoints unless that is the product (it should not be). Filesystem and egress are default-deny. If the tool is “run this SQL,” the database user is not the migration user.</P>
<H2>4. Confirm</H2>
<P>High-impact actions wait. Send email, move money, change IAM, delete. A human or a second control has to say yes. “The model was very sure” is not a confirmation. Logging after the fact is not a confirmation.</P>
<Callout>Vendor refusal screens (Fable downshifts, Daybreak enrollment) are not these gates. They are extra. They do not replace a broker.</Callout>
<H2>How we test, without a circus</H2>
<P>We do not publish a list of magic strings. We try to make the product do something outside the inventory: extra tool, extra field, extra host, skip confirm. We stop at the broker. If the broker holds, the finding is “prompt is messy, tools are contained.” If the broker does not exist, the finding is “this is not an agent product.”</P>
<Code lang="gates.ts">{`if (!ALLOWLIST.has(tool)) return deny("not a tool")
const args = schema.parse(raw)        // throws → deny
await sandbox.run(tool, args)         // no extra env
if (HIGH_IMPACT.has(tool)) await confirm(human)`}</Code>
<H2>RAG is a fifth surface, not a fifth gate</H2>
<P>Retrieved text must not become a new tool name or a new system block. Isolate, ground, strip, refuse. Do not confuse “the chunk said to call shell” with a successful call. If your schema cannot name shell, the chunk is noise.</P>
<Related items={[
['post-loops.html','From prompts to loops'],
['services.html#agents','Agent audits']
]}/>
</>;
}
window.Article=Article;
mountPost();
