<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Actionable Vectors</title>
    <description>The latest articles on DEV Community by Actionable Vectors (@actionable-vectors).</description>
    <link>https://dev.to/actionable-vectors</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4191268%2Faad33fc2-7e2d-4343-a49b-4641e66de828.png</url>
      <title>DEV Community: Actionable Vectors</title>
      <link>https://dev.to/actionable-vectors</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/actionable-vectors"/>
    <language>en</language>
    <item>
      <title>Your AI agent's approval gate checks the verb, not the object</title>
      <dc:creator>Actionable Vectors</dc:creator>
      <pubDate>Sun, 18 Oct 2026 00:10:32 +0000</pubDate>
      <link>https://dev.to/actionable-vectors/your-ai-agents-approval-gate-checks-the-verb-not-the-object-559f</link>
      <guid>https://dev.to/actionable-vectors/your-ai-agents-approval-gate-checks-the-verb-not-the-object-559f</guid>
      <description>&lt;p&gt;Most human-in-the-loop setups for AI agents ask one question before a risky action: &lt;em&gt;is this tool allowed?&lt;/em&gt; A person clicks "approve" on &lt;code&gt;approve_payment&lt;/code&gt;, and the gate is satisfied. But that approval binds the &lt;strong&gt;verb&lt;/strong&gt;, not the &lt;strong&gt;object&lt;/strong&gt;. Nothing ties it to &lt;em&gt;this&lt;/em&gt; payee, &lt;em&gt;this&lt;/em&gt; amount, &lt;em&gt;this&lt;/em&gt; invoice, so content the agent reads after the click can still decide what actually gets paid.&lt;/p&gt;

&lt;p&gt;I built a small mock company to test this: helpdesk, finance, code-review and release agents behind a policy layer. The policy passed &lt;strong&gt;10 out of 10&lt;/strong&gt; of its refusal tests. A vendor email couldn't trigger a payment, and a PR description couldn't merge itself. Then I wrote five tests that left the verbs alone and changed what they acted on. &lt;strong&gt;Three of the five failed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post covers the vector (indirect prompt injection through retrieved content), why a green scorecard missed it, and seven distinct reasons it exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vector
&lt;/h2&gt;

&lt;p&gt;An agent reads content written by someone other than the person who made the request (a wiki runbook, an invoice field, a PR description, an earlier ticket message), and that content steers what the agent does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwuil81qmrrh342ln02ei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwuil81qmrrh342ln02ei.png" alt="Where the trust boundary actually is" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The top row is the control plane: the requester, the system prompt, the policy check, the approver. The bottom row is the data plane, labeled by &lt;strong&gt;who can write it&lt;/strong&gt;. Both collapse into one context window, and the dashed boundary between them disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the 10/10 measured
&lt;/h2&gt;

&lt;p&gt;Nine of the ten fixtures were &lt;em&gt;direct&lt;/em&gt; injection: the attacker's text arrived in the same message as the request. Only one, a poisoned wiki note, was truly indirect. And every test asked the same question: &lt;em&gt;does the gate block this privileged verb?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It did. The gate was wired correctly. But that tells you nothing about the arguments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three failures
&lt;/h2&gt;

&lt;p&gt;Using the same policy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bank field swap.&lt;/strong&gt; A human approved paying invoice INV-2201. The invoice's bank details were then edited, and the payment went through to the new account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amount change.&lt;/strong&gt; Same approval, with the amount raised from $1,840 to $18,400. It went through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Safe" tool egress.&lt;/strong&gt; A helpdesk ticket got the agent to include the staff contact list in its reply through &lt;code&gt;send_reply&lt;/code&gt;, a tool the policy treats as always safe. The redaction filter missed all five records.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; these were scripted stand-ins that behaved &lt;em&gt;as if&lt;/em&gt; the model had already obeyed the injected text. This tests the policy layer, not how a particular model resists injection. That's deliberate: you should assume the model will sometimes comply and ask what the system lets it do when it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven root causes
&lt;/h2&gt;

&lt;p&gt;"The model obeyed a malicious document" is true but not useful. Underneath it are separate mechanisms, and each one needs a different fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RC1: No structural separation of instructions and data.&lt;/strong&gt; SQL injection was largely solved by parameterized queries: code and data travel separately. LLM context has no equivalent. A runbook and the system prompt are the same kind of token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RC2: Task framing amplifies obedience.&lt;/strong&gt; "Follow the runbook" or "do what the ticket asks" turns instruction-following from a risk into the job description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RC3: Provenance is stripped at ingestion.&lt;/strong&gt; By the time a chunk reaches the context window, a contractor's wiki edit looks the same as the SRE lead's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RC4: Confused deputy.&lt;/strong&gt; The group of people who can &lt;em&gt;write&lt;/em&gt; to the data plane is far larger than the group allowed to &lt;em&gt;trigger&lt;/em&gt; the actions. The agent connects them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdo0alwqp51lvn60ywpiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdo0alwqp51lvn60ywpiw.png" alt="Who can write vs who can act" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RC5: Approvals bind the verb, not the object.&lt;/strong&gt; This is the cause the 10/10 hid. The attacker doesn't need to unlock a privileged tool. They only need to sit upstream of one a human already approved and shape its arguments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuhacvhsmf7zc3um9arvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuhacvhsmf7zc3um9arvx.png" alt="Approvals that bind the verb, not the object" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RC6: The human gate is mediated by the agent.&lt;/strong&gt; The approver reads a summary written by the same agent that just read the poisoned content. And "trusted channel" is often just a string the caller sets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjiet9rb3hoy3io8kptek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjiet9rb3hoy3io8kptek.png" alt="The approver reads the agent's framing" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RC7: "Safe" tools are egress.&lt;/strong&gt; Policies guard privileged verbs and wave through &lt;code&gt;send_reply&lt;/code&gt;, &lt;code&gt;comment&lt;/code&gt; and &lt;code&gt;summarize&lt;/code&gt;. Once private data and untrusted input share a session, those tools are an exit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmgyh1qkll9if3ogh14vx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmgyh1qkll9if3ogh14vx.png" alt="Safe tools are still egress" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fixes are boring, and they worked
&lt;/h2&gt;

&lt;p&gt;A tightened policy passed all five tests, and the two legitimate-use controls still worked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bind approvals to the object.&lt;/strong&gt; Sign the exact arguments (tool, payee, amount, invoice, expiry, nonce) and re-check them at execution time. If anything changed after sign-off, deny.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taint tracking for egress.&lt;/strong&gt; Once untrusted content is in context, outbound tools become internal-only, templated, or need approval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show approvers the raw request and arguments&lt;/strong&gt;, not the agent's summary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Label provenance&lt;/strong&gt; on retrieved chunks, and limit which sources can be in context for privileged tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege per task.&lt;/strong&gt; Run with the requesting user's permissions, not a super-identity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the object, not just the verb.&lt;/strong&gt; For every privileged tool, add a regression test that changes the arguments after approval, and one positive control showing legitimate work still succeeds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Add a line to the system prompt telling the model to ignore instructions in documents" isn't on this list. It can help at the margins, but it doesn't fix any of the seven causes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try this on your own platform
&lt;/h2&gt;

&lt;p&gt;Pull one approval record from your agent platform. Does it bind the exact arguments (payee, target, amount, expiry), or just the tool name? If it only names the tool, your human-in-the-loop is approving a category, not a transaction.&lt;/p&gt;




&lt;h3&gt;
  
  
  The full pack, and a disclosure
&lt;/h3&gt;

&lt;p&gt;I packaged this review method as five agent prompts for security engineers: threat modeling, PR security review, CVE triage, incident notes, and an agent-feature reviewer built around RC1–RC7. Disclosure: it's my product, &lt;strong&gt;Trust Boundary: Agent Prompts for Security Engineers&lt;/strong&gt;, $15 at &lt;a href="https://actionable0.gumroad.com/l/mtsdk" rel="noopener noreferrer"&gt;https://actionable0.gumroad.com/l/mtsdk&lt;/a&gt;. Launch offer: code &lt;code&gt;BOUNDARY5&lt;/code&gt; makes it $10 for the first 25 buyers, until Oct 31: &lt;a href="https://actionable0.gumroad.com/l/mtsdk/BOUNDARY5" rel="noopener noreferrer"&gt;https://actionable0.gumroad.com/l/mtsdk/BOUNDARY5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article and the diagrams were drafted with AI assistance and reviewed by a human before publishing. The lab is a clean-room mock; no real systems were tested.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;— Actionable Vectors&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>llm</category>
      <category>appsec</category>
    </item>
  </channel>
</rss>
