<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[justrunme]]></title><description><![CDATA[justrunme]]></description><link>https://justrunme.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 13 Nov 2026 18:26:58 GMT</lastBuildDate><atom:link href="https://justrunme.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Fail-closed authorization for AI agent tool calls, and the audit record behind it]]></title><description><![CDATA[Platform teams already enforce fail-safe defaults for Kubernetes objects with RBAC and admission controllers such as OPA/Gatekeeper and Kyverno. My…]]></description><link>https://justrunme.hashnode.dev/fail-closed-authorization-for-ai-agent-tool-calls-and-the-audit-record-behind-it</link><guid isPermaLink="true">https://justrunme.hashnode.dev/fail-closed-authorization-for-ai-agent-tool-calls-and-the-audit-record-behind-it</guid><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AI]]></category><category><![CDATA[mcp]]></category><dc:creator><![CDATA[Andrey Lesnikov]]></dc:creator><pubDate>Fri, 13 Nov 2026 10:54:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af6c64bd6a3db38d1023234/66031aaa-e587-4ec2-8b43-2914b200b1c7.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Platform teams already enforce fail-safe defaults for Kubernetes objects with RBAC and admission controllers such as OPA/Gatekeeper and Kyverno. My preprint, <em>Fail-Closed Authorization for Tool-Using AI Agents: A Capability Contract and Case Study</em>, asks what the same kind of admission check should look like for agent tool calls, and what a plain tool-level allowlist leaves uncovered. It describes the Agent Security Contract (ASC), a deny-by-default gate that decides on each agent or MCP tool call before it executes. If the policy does not explicitly allow a call, the call does not run.</p>
<p>It is a preprint and has not been peer-reviewed. The paper itself calls it a systems/engineering contribution rather than a new enforcement mechanism, and closely related enforcers exist.</p>
<h2>What the contract contains</h2>
<p>An ASC policy is a YAML file with:</p>
<ul>
<li>a capability catalog with a pinned <code>capability_digest</code> (a SHA-256 digest of the approved tool catalog)</li>
<li>per-agent allowed tools, each with argument constraints: type, regex <code>pattern</code>, <code>deny_patterns</code>, <code>allow_hosts</code>, <code>max_length</code></li>
<li>a list of high-impact tools that need out-of-band approval</li>
<li>rate limits per trace and per tool</li>
</ul>
<p>The demo policy in the paper declares 8 capabilities, allowlists 7 of them for <code>demo-agent</code> (<code>secrets.read</code> is left out) and marks <code>filesystem.delete</code> and <code>deploy.production</code> as high impact.</p>
<h2>How a decision is made</h2>
<p>Every decision is one of <code>ALLOW</code>, <code>BLOCK</code> or <code>REQUIRE_APPROVAL</code>. Only <code>ALLOW</code> reaches the tool. <code>BLOCK</code> and <code>REQUIRE_APPROVAL</code> go back to the agent.</p>
<p>Evaluation is ordered and short-circuits. The first three checks in the paper:</p>
<ol>
<li>Unknown <code>agent_id</code>: block.</li>
<li>Catalog digest mismatch: block.</li>
<li>Rate limit exceeded: block.</li>
</ol>
<p>The catalog digest is SHA-256 over the list of <code>{name, description, input_schema}</code> objects, sorted by name and serialized with <code>sort_keys=True</code>, separators <code>(",":")</code> and <code>ensure_ascii=False</code>, UTF-8 encoded. No Unicode normalization is applied, so two descriptions that look the same but are encoded differently give different digests and the call is blocked. Other catalog fields, such as annotations, are not covered.</p>
<p>The fail-closed part goes beyond the default verdict. Every error path the paper enumerates and tests resolves to <code>BLOCK</code>, so an error inside the gate does not turn into an allowed call.</p>
<p>ASC runs in two modes. At runtime, the agent runtime calls the gate (as a library or a loopback HTTP sidecar) once per proposed call, passing the calls already admitted in that episode plus the new one. In CI/admission mode, a recorded trace is evaluated as a whole, as a regression or admission check. All results in the paper use the second mode.</p>
<h2>Evidence for every verdict</h2>
<p>Each decision is written as a JSON evidence envelope. According to the repo's schema it carries <code>schema_version</code>, <code>policy_version</code>, <code>policy_sha256</code>, <code>scenario_id</code>, <code>verdict</code>, <code>reason</code>, <code>trace_id</code>, <code>commit_sha</code>, <code>timestamp</code> and <code>envelope_sha256</code>, a SHA-256 over the canonical JSON of the other fields. <code>asc verify</code> rejects envelopes with missing fields, inconsistent verdicts or edits that no longer match the digest, and with <code>--policy</code> it also rejects a <code>policy_sha256</code> that differs from the policy file.</p>
<p>The digest is unkeyed. It catches accidental or naive edits, but anyone who can rewrite the file can recompute it, so it is an integrity check and not a signature. Envelopes are trustworthy only when they come from a CI run or a pipeline you control.</p>
<h2>From verdict to audit record</h2>
<p>An envelope records the decision. It doesn't say which workload ran the call, on whose behalf, or what the model step cost. I wrote a separate guide on that part, <a href="https://justrunme.com/guides/ai-agent-audit-trail/">AI agent audit trail: what to log for every tool call</a>. It is a design for an audit trail on Kubernetes, not a product.</p>
<p>The starting point is that the logs you already have each miss something. Kubernetes audit logs only see requests to the API server, so an agent calling <code>send_email</code> through an MCP server never shows up there. Application logs usually record the tool call but not which policy allowed it. LLM traces often drop arguments and results for privacy. Gateway logs have tokens and cost per request but not the tool call the request led to.</p>
<p>The guide proposes one record per tool call, blocked and approval-pending calls included, with field groups for who (<code>agent_id</code>, <code>workload_identity</code>, <code>on_behalf_of</code>), what (<code>tool_name</code>, <code>tool_catalog_digest</code>, <code>arguments_digest</code>), the decision (<code>verdict</code>, <code>reason</code>, <code>policy_version</code>, <code>policy_sha256</code>, approval fields), outcome, cost, correlation IDs (<code>trace_id</code>, <code>tool_call_id</code>, <code>mcp_session_id</code>), provenance and a <code>record_digest</code>. A missing approval on a call that did not need one is information. A missing policy digest on any call is a gap.</p>
<p>Some rules from the guide apply directly to the gate.</p>
<ul>
<li>Use the OpenTelemetry GenAI names where they exist, such as the <code>execute_tool</code> span and <code>gen_ai.tool.name</code>. These conventions are at Development stability, so pin the semconv version you follow.</li>
<li>Leave <code>gen_ai.tool.call.arguments</code> and <code>gen_ai.tool.call.result</code> off by default (they are Opt-In) and store a digest plus a redacted copy instead.</li>
<li>Bind approvals to the argument digest. An approval for <code>delete /workspace/tmp/report.json</code> must not cover <code>delete /workspace</code>.</li>
<li>Write records to append-only storage the agent's identity cannot modify, chain the digests so removed records are detectable, and add signatures if third parties rely on the records. An unkeyed hash is not enough there.</li>
</ul>
<p>The guide's own test is short: if you cannot reconstruct identity, policy version, verdict and cost for a single tool call, you have logs and not an audit trail.</p>
<h2>The case study</h2>
<p>The open-source implementation was evaluated on static, recorded tool-call traces only. No live agent was run. All numbers below are copied from the abstract, each with its scope.</p>
<ul>
<li>Adversarial probe, 60 cases. Bypasses of the v0.1.0 demo policy found in review were fixed in v0.2.0. Allowed attacks fall from 32 to 1 (a policy gap), at the cost of 3 new benign over-blocks.</li>
<li>Author corpus, 66 scenarios (v0.2.1). ASC returns the expected verdict in 66/66 cases. I built this corpus around these checks.</li>
<li>Tool-name RBAC baseline (Python and OPA/Rego). It admits 30 of its 33 attacks, by construction.</li>
<li>Mutation testing. Only 8 of 62 single-constraint mutants are killed, which means the corpus exercises few policy constraints.</li>
<li>Held-out AgentDojo replay. 132 AgentDojo v1.2 ground-truth traces, replayed under policies frozen beforehand. With tool-level approval policies, 25 of 26 non-empty attack traces are held for approval, and so are 60 of 97 benign tasks. v0.2.0's strict-argument rule makes these unadapted policies block 94 of 97 benign tasks.</li>
<li>Policies derived from AgentDojo's tool signatures. They first hit v0.2.0's lack of optional arguments (31 benign blocks). With v0.2.1 they reproduce the tool-level verdicts on all 132 traces, with no blocks and no newly admitted attack. Type-only declarations add no protection here.</li>
<li>Latency. Median in-process latency is about 38 µs per trace.</li>
</ul>
<h2>Limits</h2>
<p>These are taken from the paper's threat model.</p>
<ul>
<li>The <code>approved</code> flag is a boolean field of the call inside the trace in all released versions (v0.1.0 to v0.2.1). ASC does not authenticate it. A runtime that lets the model fill it in voids approval gating. Binding approvals to an authenticated principal is listed as future work.</li>
<li>Catalog pinning only detects a changed catalog, including a change after approval, if the runtime passes the catalog actually shown to the model on every call. A stale or missing catalog defeats the check.</li>
<li>Out of scope: compromise of the evaluator host, CI or approval channel; envelope forgery by someone with write access; attacks that fit entirely inside allowed calls and arguments, such as exfiltration through an allowlisted host; semantic misuse no syntactic constraint captures; OS-level isolation.</li>
<li>ASC is not a prompt-injection detector, a sandbox or a signer. It is meant to sit next to red-teaming, regression harnesses, runtime guardians and model-level defences.</li>
</ul>
<p>The security goal is stated narrowly: no proposed call outside the declared contract executes, and any high-impact call without approval is held. A permissive constraint admits whatever it permits.</p>
<h2>Links</h2>
<ul>
<li>Research page: <a href="https://justrunme.com/research/fail-closed-authorization-ai-agents/">https://justrunme.com/research/fail-closed-authorization-ai-agents/</a></li>
<li>Zenodo: <a href="https://doi.org/10.5281/zenodo.24075886">https://doi.org/10.5281/zenodo.24075886</a></li>
<li>Research Square: <a href="https://doi.org/10.21203/rs.3.rs-11676427/v1">https://doi.org/10.21203/rs.3.rs-11676427/v1</a></li>
<li>Code: <a href="https://github.com/justrunme/agent-security-contract">https://github.com/justrunme/agent-security-contract</a></li>
<li>Related guide: <a href="https://justrunme.com/guides/ai-agent-audit-trail/">https://justrunme.com/guides/ai-agent-audit-trail/</a></li>
</ul>
<h2>About the author</h2>
<p>Andrey Lesnikov is a Senior DevOps &amp; AI Platform Engineer and an independent researcher. He writes about access control and audit trails for AI agent tool calls.</p>
<p>Links: <a href="https://justrunme.com">justrunme.com</a> · <a href="https://github.com/justrunme">GitHub</a> · <a href="https://orcid.org/0009-0004-7583-4314">ORCID</a> · <a href="https://independent.academia.edu/justrunme">Academia.edu</a> · <a href="https://sessionize.com/justrunme">Sessionize</a> · <a href="https://hashnode.com/@justrunme">Hashnode</a> · <a href="https://bsky.app/profile/justrunme.com">Bluesky</a> · <a href="https://www.amazon.com/author/justrunme">Amazon author page</a></p>
<p><em>Originally published on</em> <a href="http://dev.to"><em>dev.to</em></a><em>:</em> <a href="https://dev.to/justrunme/fail-closed-authorization-for-ai-agent-tool-calls-and-the-audit-record-behind-it-5f0g"><em>https://dev.to/justrunme/fail-closed-authorization-for-ai-agent-tool-calls-and-the-audit-record-behind-it-5f0g</em></a></p>
]]></content:encoded></item></channel></rss>