{"id":557,"date":"2026-08-07T13:01:36","date_gmt":"2026-08-07T13:01:36","guid":{"rendered":"https:\/\/abrarqasim.com\/blog\/ai-agent-security-tool-schema-problem\/"},"modified":"2026-08-07T13:01:36","modified_gmt":"2026-08-07T13:01:36","slug":"ai-agent-security-tool-schema-problem","status":"publish","type":"post","link":"https:\/\/abrarqasim.com\/blog\/ai-agent-security-tool-schema-problem\/","title":{"rendered":"AI Agent Security: The Tool Schema Problem Nobody Audits"},"content":{"rendered":"<p>I was wiring up an agent for a client project last month. A dozen tools: email, calendar, a Postgres database, the usual suspects. Somewhere around tool number eight I had a thought that ruined my evening. I&rsquo;d spent hours reviewing the system prompt for safety and exactly zero minutes reviewing the tool definitions.<\/p>\n<p>Turns out I had it backwards.<\/p>\n<p>A paper that landed on arXiv last week put hard numbers on something I&rsquo;d only half-suspected: the same model that refuses a sketchy request in plain chat will often execute it without complaint once it&rsquo;s running as an agent. The model didn&rsquo;t change. The tool schemas changed how it reads the request. If you&rsquo;re building agents in 2026, this is the AI agent security problem hiding in the one part of your codebase nobody audits.<\/p>\n<h2 id=\"your-model-gets-less-safe-the-moment-you-hand-it-tools\">Your model gets less safe the moment you hand it tools<\/h2>\n<p>The paper is <a href=\"https:\/\/arxiv.org\/abs\/2607.29254\" rel=\"nofollow noopener\" target=\"_blank\">Tool Specifications Matter: Uncovering and Mitigating Safety Risks in AI Agents<\/a>. The authors started from a known but badly explained fact: LLMs get substantially less safe when deployed as agents. Ask a model to do something harmful in a chat window and it refuses. Wrap the same model in an agent loop with tools and the refusal rate craters. In their tests, the average refusal rate for harmful requests sat at 23.8 percent. Not 90. Not 70. Twenty-three.<\/p>\n<p>What&rsquo;s new here is the why. Using white-box analysis of the models&rsquo; internal representations, they traced the degradation to the schema-formatted tool specifications themselves. The JSON blobs describing your tools weaken the model&rsquo;s internal refusal signals. The structured format nudges the model into execution mode before it has decided whether it should act at all.<\/p>\n<p>I&rsquo;ll admit that surprised me. My working theory was that agent safety problems came from long contexts, or from the feedback loop of observations pouring back in. Nope. The spec format alone does damage.<\/p>\n<p>If that sounds abstract, make it concrete. An agent holding a shell tool, an email tool, and a database connection is one bad decision away from being an insider threat with API access. The gap between &ldquo;refuses in chat&rdquo; and &ldquo;executes as a tool call&rdquo; stops being academic the day the tool call drops a table or mails your customer list to a stranger.<\/p>\n<h2 id=\"the-tool-schema-is-part-of-your-prompt-whether-you-meant-it-or-not\">The tool schema is part of your prompt, whether you meant it or not<\/h2>\n<p>Here&rsquo;s a tool spec in the shape most LLM tool calling APIs expect, more or less identical to ones I&rsquo;ve shipped:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;name&quot;: &quot;send_email&quot;,\n  &quot;description&quot;: &quot;Send an email on behalf of the user&quot;,\n  &quot;input_schema&quot;: {\n    &quot;type&quot;: &quot;object&quot;,\n    &quot;properties&quot;: {\n      &quot;to&quot;: { &quot;type&quot;: &quot;string&quot; },\n      &quot;subject&quot;: { &quot;type&quot;: &quot;string&quot; },\n      &quot;body&quot;: { &quot;type&quot;: &quot;string&quot; }\n    },\n    &quot;required&quot;: [&quot;to&quot;, &quot;subject&quot;, &quot;body&quot;]\n  }\n}\n<\/code><\/pre>\n<p>Nothing wrong with it. But remember where it goes: straight into the model&rsquo;s context, on every single turn. Both <a href=\"https:\/\/docs.anthropic.com\/en\/docs\/build-with-claude\/tool-use\" rel=\"nofollow noopener\" target=\"_blank\">Anthropic&rsquo;s tool use docs<\/a> and <a href=\"https:\/\/platform.openai.com\/docs\/guides\/function-calling\" rel=\"nofollow noopener\" target=\"_blank\">OpenAI&rsquo;s function calling guide<\/a> are upfront about the fact that tool definitions are injected into the prompt. We treat schemas as configuration. The model treats them as instructions. That gap is where the trouble lives.<\/p>\n<p>And it cuts both ways. If schemas can suppress refusal behavior by accident, they can do it on purpose. A tool description is an injection point, the same class of problem as <a href=\"https:\/\/genai.owasp.org\/llmrisk\/llm01-prompt-injection\/\" rel=\"nofollow noopener\" target=\"_blank\">OWASP&rsquo;s LLM01 prompt injection<\/a> entry. I wrote about a related trick, where a poisoned GitHub issue steered a coding agent into doing an attacker&rsquo;s errands, in <a href=\"https:\/\/abrarqasim.com\/blog\/ai-coding-agents-poisoned-issue-prompt-injection\" rel=\"noopener\">my post on poisoned issues and coding agents<\/a>. The schema version is nastier, because nobody reads schemas with a skeptical eye. We read prompts. We skim specs.<\/p>\n<h2 id=\"the-fix-is-almost-annoyingly-simple\">The fix is almost annoyingly simple<\/h2>\n<p>The paper&rsquo;s mitigation, called SafeKeep, is a two-pass trick. Pass one: ask the model whether the request is safe, but describe the tools in flat plain text instead of JSON schemas. Pass two: if the request passes, execute with the original schemas, which your API needs for actual tool calls anyway.<\/p>\n<p>The flattening step is the kind of thing you can write before your coffee cools:<\/p>\n<pre><code class=\"language-python\">def flatten_tool_spec(tool):\n    schema = tool[&quot;input_schema&quot;]\n    required = set(schema.get(&quot;required&quot;, []))\n    lines = [f&quot;Tool: {tool['name']}. {tool['description']}.&quot;]\n    for arg, prop in schema[&quot;properties&quot;].items():\n        status = &quot;required&quot; if arg in required else &quot;optional&quot;\n        lines.append(f&quot;- takes {arg} ({prop['type']}, {status})&quot;)\n    return &quot;\\n&quot;.join(lines)\n<\/code><\/pre>\n<p>The safety pass then sees this instead of JSON:<\/p>\n<pre><code>Tool: send_email. Send an email on behalf of the user.\n- takes to (string, required)\n- takes subject (string, required)\n- takes body (string, required)\n<\/code><\/pre>\n<p>Same information. Different format. In the paper&rsquo;s experiments across four models and two benchmarks, that change lifted the average refusal rate for harmful requests from 23.8 to 70.6 percent, and dropped the attack success rate for observation-level prompt injection from 25.6 to 2.5 percent. If you care about prompt injection protection, that second number is the headline. A one-file change cut attack success by an order of magnitude.<\/p>\n<p>Is it bulletproof? No. A 70.6 percent refusal rate still means unsafe requests get through, and I wouldn&rsquo;t ship it as my only defense. But as a cheap extra layer, it embarrasses a lot of what currently gets sold as agent security tooling.<\/p>\n<h2 id=\"the-ai-agent-security-risks-i-actually-check-for-now\">The AI agent security risks I actually check for now<\/h2>\n<p>My review process changed after reading this paper. Descriptions come first: I read every tool description as if an attacker wrote it, because in a world of third-party plugins and MCP servers, sometimes one did. Schemas come second: an agent that only needs to read a table has no business holding a tool that can write to it. Most AI agent security vulnerabilities I&rsquo;ve met in the wild were ordinary permission problems wearing a fancy hat.<\/p>\n<p>Then outputs. Tool results flow back into the context on the next turn, so anything a tool returns is another injection path. Scraped web pages, file contents, even error messages. And I validate every argument server-side before execution, because the model producing well-formed JSON says nothing about the JSON being safe. I covered that gap in <a href=\"https:\/\/abrarqasim.com\/blog\/openai-structured-outputs-json-schema-what-strict-mode-wont-do\" rel=\"noopener\">my structured outputs post<\/a>: strict mode guarantees shape, not sense.<\/p>\n<p>None of this is exotic. It&rsquo;s the least-privilege thinking backend developers have preached for decades, pointed at a new kind of caller. A decent chunk of <a href=\"https:\/\/abrarqasim.com\" rel=\"noopener\">my client work<\/a> lately is exactly this: not building new agents, but going back over agents that got built fast and asking who&rsquo;s allowed to do what.<\/p>\n<p>One more thing on cost, since somebody will ask. A second model call per tool-using turn isn&rsquo;t free. In practice I run the safety pass with a smaller, cheaper model than the one doing the real work, and only on turns that touch tools with side effects. Reading a calendar doesn&rsquo;t need a gatekeeper. Sending email does. That keeps the added latency small and the added spend at rounding-error level, which is a price I&rsquo;ll happily pay to not star in someone&rsquo;s incident report.<\/p>\n<p>There&rsquo;s also a boring organizational fix hiding in all this: version your tool specs and review changes to them like code, because they are code. A one-line edit to a description can shift your agent&rsquo;s behavior as much as a system prompt rewrite, and right now most teams would merge that edit without a second look.<\/p>\n<h2 id=\"what-to-do-this-week\">What to do this week<\/h2>\n<p>Pick one agent you have running, in production or in a side project, and dump every tool spec into a single file. Read it end to end, descriptions included. I mean actually read it, the way you&rsquo;d read a pull request from someone you don&rsquo;t trust yet.<\/p>\n<p>Then add a pre-execution safety pass using flattened specs like the snippet above. Log every refusal for a week and look at the log. Mine flagged a test harness doing something I never asked it to do within two days. Cheap experiment, uncomfortable results.<\/p>\n<p>Your system prompt was never the whole prompt. The schemas were in there the whole time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tool schemas quietly weaken your model&#8217;s refusal behavior. Here is what a new agent-safety paper found and the checks I now run before shipping any agent.<\/p>\n","protected":false},"author":2,"featured_media":556,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"Tool schemas quietly weaken your model's refusal behavior. Here is what a new agent-safety paper found and the checks I now run before shipping any agent.","rank_math_focus_keyword":"ai agent security","rank_math_canonical_url":"","rank_math_robots":"","footnotes":""},"categories":[4],"tags":[621,363,5,573,405],"class_list":["post-557","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","tag-ai-agent-security","tag-ai-agents","tag-llm","tag-prompt-injection-2","tag-tool-calling-2"],"_links":{"self":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/557","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/comments?post=557"}],"version-history":[{"count":0,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/557\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media\/556"}],"wp:attachment":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media?parent=557"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/categories?post=557"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/tags?post=557"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}