<?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>Whose response is this? The MCP server SDK that matched replies by id alone</title>
      <dc:creator>Actionable Vectors</dc:creator>
      <pubDate>Wed, 21 Oct 2026 11:11:06 +0000</pubDate>
      <link>https://dev.to/actionable-vectors/whose-response-is-this-the-mcp-server-sdk-that-matched-replies-by-id-alone-50li</link>
      <guid>https://dev.to/actionable-vectors/whose-response-is-this-the-mcp-server-sdk-that-matched-replies-by-id-alone-50li</guid>
      <description>&lt;h1&gt;
  
  
  Whose response is this? The MCP server SDK that matched replies by id alone
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A vector breakdown of CVE-2026-111770 / GHSA-jm63-cq47-j5jp (&lt;code&gt;@modelcontextprotocol/server&lt;/code&gt; 2.0.0 through 2.2.0, fixed in 2.3.0)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;No injection, no stolen token. Two people use the same MCP server over HTTP at the same time, and one of them gets the other's answer. That's the whole bug, and it came straight from the pattern the official framework examples showed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the advisory actually says
&lt;/h2&gt;

&lt;p&gt;From GHSA-jm63-cq47-j5jp, published by the MCP TypeScript SDK maintainers and added to the GitHub Advisory Database on October 20, 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Package:&lt;/strong&gt; &lt;code&gt;@modelcontextprotocol/server&lt;/code&gt; (npm). &lt;strong&gt;Affected:&lt;/strong&gt; 2.0.0 through 2.2.0. &lt;strong&gt;Fixed:&lt;/strong&gt; 2.3.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not affected:&lt;/strong&gt; stdio servers, &lt;code&gt;createMcpHandler()&lt;/code&gt; with a factory that builds a new server on every call, and &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; 1.26.0 and later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weakness:&lt;/strong&gt; CWE-362, a race on a shared resource. &lt;strong&gt;Severity:&lt;/strong&gt; High, CVSS 3.1 7.7 (&lt;code&gt;AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:H&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bug:&lt;/strong&gt; "an MCP server reached over HTTP could deliver one client's responses and notifications to another client. This happened when clients shared one stateless transport, or one &lt;code&gt;McpServer&lt;/code&gt; or &lt;code&gt;Server&lt;/code&gt; instance."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why ids collide:&lt;/strong&gt; "Messages were matched to a connection by JSON-RPC &lt;code&gt;id&lt;/code&gt; alone. SDK clients number requests from 0, so ids collide in normal use."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit:&lt;/strong&gt; DavidCarliez and legitu1337.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's one of five MCP TypeScript SDK advisories published the same afternoon. The others cover session-unbound tasks, a validation memory blowup, a missing token audience check, and headers resent across redirects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vector: two clients, one connection table
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A developer builds an HTTP MCP server the way the README examples for the &lt;code&gt;hono&lt;/code&gt;, &lt;code&gt;express&lt;/code&gt;, &lt;code&gt;fastify&lt;/code&gt; and &lt;code&gt;node&lt;/code&gt; adapters showed: create the server and transport once at startup, then reuse them for every request.&lt;/li&gt;
&lt;li&gt;Client A sends a request with JSON-RPC id 0. Client B, an unrelated user, also sends id 0, because every SDK client starts counting at 0.&lt;/li&gt;
&lt;li&gt;The shared instance has one slot for "the response to id 0." Whichever response is ready gets routed to whichever connection currently holds that id.&lt;/li&gt;
&lt;li&gt;Client B receives client A's tool result, or A's notifications, which may hold whatever data A's tool returned.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The attack complexity is rated high because it depends on timing and overlap. But this isn't something an attacker has to engineer. It happens on its own whenever traffic overlaps, so ordinary users can see each other's data without anyone trying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this class of bug exists: three root causes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A protocol-level id used as a security identity
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; JSON-RPC ids exist so one client can match its own replies. They're only unique within one connection. A server that uses them across connections is using a counter as if it were an identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory: messages were "matched to a connection by JSON-RPC &lt;code&gt;id&lt;/code&gt; alone," and because clients "number requests from 0, … ids collide in normal use."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Route by connection or session first, then by id. Any lookup keyed on something the client chooses needs a server-assigned scope around it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "Stateless" read as "safe to share"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; A stateless HTTP transport sounds like it holds nothing, so creating one at startup and sharing it looks like a harmless optimization. But it still tracks in-flight requests, and that tracking is state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; Affected setups are ones where "your code creates the transport or the server once at startup, instead of once per request or per session." The fix makes the SDK refuse sharing outright: "a second &lt;code&gt;connect()&lt;/code&gt; rejects with &lt;code&gt;ALREADY_CONNECTED&lt;/code&gt;, and a stateless transport rejects its second request."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Treat per-request objects as per-request. If an object tracks anything in flight, it belongs to one request or one session. Making misuse fail loudly, as 2.3.0 does, is the right default.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The insecure pattern was the documented one
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; Most people build servers by copying the quickstart. If the quickstart shares one instance, most servers will too, and the bug spreads by copy-paste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory: "Until this release, the README examples of &lt;code&gt;@modelcontextprotocol/hono&lt;/code&gt;, &lt;code&gt;express&lt;/code&gt;, &lt;code&gt;fastify&lt;/code&gt; and &lt;code&gt;node&lt;/code&gt; showed that pattern."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Example code is security-relevant code. Review quickstarts with the same care as the library, and design APIs so the copy-pasted shape is the safe one, for example a factory that builds a fresh server per call.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this connects to the rest of the batch
&lt;/h2&gt;

&lt;p&gt;Several of this week's MCP SDK advisories share a theme: something that should have been scoped to one client wasn't. Tasks weren't bound to sessions, tokens weren't bound to an audience, and here responses weren't bound to a connection. &lt;strong&gt;In a multi-user agent server, everything keyed by a client-chosen value needs a server-owned scope around it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Defensive takeaways you can check this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check your version and your wiring.&lt;/strong&gt; If you use &lt;code&gt;@modelcontextprotocol/server&lt;/code&gt; 2.0.0 through 2.2.0 over HTTP, look for a server or transport created once at startup and reused across requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade and change the code.&lt;/strong&gt; Move to 2.3.0 or later, then switch to one of the patterns the advisory lists: &lt;code&gt;createMcpHandler()&lt;/code&gt; with a factory, a new server and transport per request, or one of each per session with &lt;code&gt;sessionIdGenerator&lt;/code&gt; set. Upgrading alone will break a shared setup, on purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can't upgrade yet?&lt;/strong&gt; The advisory says the same code change fixes affected versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick up the rest of the batch.&lt;/strong&gt; Moving to &lt;code&gt;@modelcontextprotocol/server&lt;/code&gt; / &lt;code&gt;client&lt;/code&gt; 2.3.0 or &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; 1.32.0 also covers the sibling advisories. Note that the validation-memory fix needs its new limit option configured; upgrading alone doesn't enable it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assess exposure.&lt;/strong&gt; If several users shared an affected HTTP server, consider whether tool results could have held sensitive data, and handle it the way you'd handle any cross-tenant leak.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test it.&lt;/strong&gt; Run two clients concurrently against your server with overlapping request ids and assert each receives only its own responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-jm63-cq47-j5jp" rel="noopener noreferrer"&gt;MCP TypeScript SDK advisory GHSA-jm63-cq47-j5jp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/advisories/GHSA-jm63-cq47-j5jp" rel="noopener noreferrer"&gt;GitHub Advisory Database entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-22jm-h49p-29qw" rel="noopener noreferrer"&gt;Sibling advisory GHSA-22jm-h49p-29qw (task store not bound to session)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-46jr-4wx8-v453" rel="noopener noreferrer"&gt;Sibling advisory GHSA-46jr-4wx8-v453 (validation memory exhaustion)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-rvq5-wwqv-78pq" rel="noopener noreferrer"&gt;Sibling advisory GHSA-rvq5-wwqv-78pq (no token audience check)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you're building agent backends and want a checklist for this kind of trust boundary, the &lt;a href="https://actionable0.gumroad.com/l/mtsdk" rel="noopener noreferrer"&gt;Trust Boundary pack&lt;/a&gt; covers it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance and edited by a human. All facts come from the advisories linked above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>mcp</category>
      <category>appsec</category>
    </item>
    <item>
      <title>Summarize this document, then run it: Vibe-Trading's prompt-injection-to-root shell</title>
      <dc:creator>Actionable Vectors</dc:creator>
      <pubDate>Wed, 21 Oct 2026 11:10:59 +0000</pubDate>
      <link>https://dev.to/actionable-vectors/summarize-this-document-then-run-it-vibe-tradings-prompt-injection-to-root-shell-5bk6</link>
      <guid>https://dev.to/actionable-vectors/summarize-this-document-then-run-it-vibe-tradings-prompt-injection-to-root-shell-5bk6</guid>
      <description>&lt;h1&gt;
  
  
  Summarize this document, then run it: Vibe-Trading's prompt-injection-to-root shell
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A vector breakdown of CVE-2026-114107 (HKUDS Vibe-Trading 0.1.0 before 0.1.7, fixed in 0.1.7)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most agent shell bugs need an attacker who can talk to the agent. This one only needs an attacker who can get a document or a web page in front of it. Vibe-Trading hands its model a shell tool that runs whatever command the model emits, as root, with no allow-list and no approval step. So the person who wrote the PDF the agent is summarizing has the same reach as the operator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the advisory actually says
&lt;/h2&gt;

&lt;p&gt;From the CVE record (published to NVD October 21, 2026, 01:17 UTC, by VulnCheck), the VulnCheck advisory, and the project's GitHub advisory GHSA-jqmf-mx4f-hfr6:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; HKUDS Vibe-Trading, an AI trading-research agent. &lt;strong&gt;Affected:&lt;/strong&gt; 0.1.0 up to, but not including, 0.1.7. &lt;strong&gt;Fixed:&lt;/strong&gt; 0.1.7.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weakness:&lt;/strong&gt; CWE-1427, Improper Neutralization of Input Used for LLM Prompting. The GitHub advisory also lists CWE-77, CWE-78, CWE-94, CWE-116 and CWE-918.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Severity:&lt;/strong&gt; VulnCheck scores it High, CVSS 4.0 8.7 (&lt;code&gt;AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H&lt;/code&gt;). Network-reachable, no privileges, and "passive" user interaction: someone just has to ask the agent to read the attacker's content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bug, in the CVE's words:&lt;/strong&gt; "remote attackers [can] execute shell commands as root by planting instructions in documents or web pages the agent processes. BashTool and BackgroundRunTool pass model-emitted commands to subprocess.run with shell=True and no allow-list or approval step, and read_url fetches arbitrary URLs, enabling command execution and server-side request forgery."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related:&lt;/strong&gt; CVE-2026-114106 covers the same release's file-read tools, which could be steered by path traversal into files like &lt;code&gt;/etc/shadow&lt;/code&gt; and &lt;code&gt;/proc/self/environ&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GitHub advisory groups five findings under one root cause: "LLM-callable tool primitive ships with zero input validation between the LLM and the sink."&lt;/p&gt;

&lt;h2&gt;
  
  
  The vector: attacker text in, root shell out
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The attacker writes instructions into content the agent will read later: an uploaded document, a web page, a trade journal.&lt;/li&gt;
&lt;li&gt;A legitimate user asks the agent to summarize or research that content. The user's own prompt is clean.&lt;/li&gt;
&lt;li&gt;The model reads the planted instructions and can't reliably separate "text I'm analyzing" from "instructions I should follow," so it emits a call to the shell tool.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BashTool&lt;/code&gt; (or its async sibling &lt;code&gt;BackgroundRunTool&lt;/code&gt;) passes that command straight to &lt;code&gt;subprocess.run(..., shell=True)&lt;/code&gt;. No parser, no allow-list, no confirmation.&lt;/li&gt;
&lt;li&gt;The container has no &lt;code&gt;USER&lt;/code&gt; directive, so the command runs as root, with whatever keys and network the agent process holds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The advisory notes a second path to the same tools: in affected versions the session API itself had no working authentication (a separate advisory), so anyone who could reach the port could drive them directly. But it's explicit that fixing authentication alone "leaves prompt-injection RCE on the table."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this class of bug exists: four root causes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A general-purpose shell is registered by default
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; Agent frameworks discover tools automatically and hand the model the whole registry. If one of those tools is "run any shell command," every task the agent does, even a summary, carries the ability to run code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory says all five tools "are members of the auto-discovered tool registry the LLM agent gets at startup," and "the tool registration is unconditional in default config — no operator opt-in flag gates them." It also points out that there are two shell entry points, so "fixing only BashTool leaves this path intact."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Make dangerous tools opt-in, not discovered. A summarizing turn has no business holding a shell at all. If a workflow truly needs one, the operator should switch it on explicitly, for that workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The model's judgment is the only gate
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; When the code between the model and &lt;code&gt;subprocess&lt;/code&gt; does nothing, the model's own discretion is the security boundary. Prompt injection exists precisely to beat that discretion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory's wording: "the LLM's own judgement is the only barrier, and that barrier collapses under prompt injection." The command argument is read "directly … (LLM-emitted) with no allowlist, no escaping, no chroot, no seccomp, and no length cap."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Put a deterministic check between the model and the sink: an allow-list of command prefixes, argument parsing with &lt;code&gt;shell=False&lt;/code&gt;, and human approval for anything outside the list. The check has to be code, because text the attacker controls can't change code.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Untrusted content and privileged tools share one turn
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; The same model turn that ingests outside content can call tools with real authority. That gives the author of the content the operator's authority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory describes "an authenticated, non-malicious caller using a clean prompt" being "turned into an RCE vector by feeding the agent attacker-controlled content (a malicious PDF, web page, or trade journal)." The same applies to &lt;code&gt;read_url&lt;/code&gt;, which forwarded any model-supplied URL with "no schema check, no hostname allowlist, no RFC1918 filter," so planted text could also point the agent at internal services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Split reading from acting. Turns that process outside content get read-only tools; acting turns don't see raw outside content, or their tool calls need approval the content can't grant. Validate outbound URLs (scheme, host, private address ranges) in code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The blast radius is root in the container
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; Even a well-gated agent will eventually run something it shouldn't. What it can reach then decides whether that's a nuisance or a breach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; "The container has no &lt;code&gt;USER&lt;/code&gt; directive, so successful execution runs as &lt;code&gt;uid=0(root)&lt;/code&gt;." The related CVE-2026-114106 shows the same release could read &lt;code&gt;/proc/self/environ&lt;/code&gt;, which is where API keys in environment variables live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Run as a non-root user, keep secrets out of the agent's environment, and limit outbound network access to what the workflow needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this connects to other agent tool bugs
&lt;/h2&gt;

&lt;p&gt;Recent approval-gate bugs had a gate that checked too little, or a gate that went silent on headless surfaces. This one never had a gate. Same lesson, simplest form: &lt;strong&gt;if the only thing between untrusted text and a shell is the model's opinion, the attacker is the one with the shell.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Defensive takeaways you can check this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade.&lt;/strong&gt; If you run Vibe-Trading 0.1.0 through 0.1.6, move to 0.1.7, per the &lt;a href="https://github.com/HKUDS/Vibe-Trading/security/advisories/GHSA-jqmf-mx4f-hfr6" rel="noopener noreferrer"&gt;GitHub advisory&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate if exposed.&lt;/strong&gt; If an affected version read outside documents or URLs, or its port was reachable by others, rotate the LLM and data-provider keys in its environment and review its shell history and outbound traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory your own agents' shell tools.&lt;/strong&gt; Search for &lt;code&gt;shell=True&lt;/code&gt; and equivalents reachable from a model tool call. For each, ask whether it's registered by default and what code, not prompt, stands in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate outbound fetches.&lt;/strong&gt; Any "read this URL" tool should reject non-HTTP schemes and private, loopback and link-local addresses before it fetches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop root.&lt;/strong&gt; Add a non-root &lt;code&gt;USER&lt;/code&gt; to agent containers and keep long-lived secrets out of their environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the injection path.&lt;/strong&gt; Add a test that feeds the agent a document containing an instruction to call its shell tool and asserts the call is refused, not just that the model "usually ignores it."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-114107" rel="noopener noreferrer"&gt;NVD entry for CVE-2026-114107&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.vulncheck.com/advisories/hkuds-vibe-trading-0.1.0-before-0.1.7-indirect-prompt-injection-rce-via-bashtool-and-backgroundruntool" rel="noopener noreferrer"&gt;VulnCheck advisory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/HKUDS/Vibe-Trading/security/advisories/GHSA-jqmf-mx4f-hfr6" rel="noopener noreferrer"&gt;Vibe-Trading GitHub advisory GHSA-jqmf-mx4f-hfr6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-114106" rel="noopener noreferrer"&gt;NVD entry for CVE-2026-114106&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you're building agent backends and want a checklist for this kind of trust boundary, the &lt;a href="https://actionable0.gumroad.com/l/mtsdk" rel="noopener noreferrer"&gt;Trust Boundary pack&lt;/a&gt; covers it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance and edited by a human. All facts come from the advisories and CVE record linked above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>llm</category>
      <category>appsec</category>
    </item>
    <item>
      <title>No prompt injection required: Langchain-Chatchat's /tools/call is a remote shell</title>
      <dc:creator>Actionable Vectors</dc:creator>
      <pubDate>Wed, 21 Oct 2026 11:10:17 +0000</pubDate>
      <link>https://dev.to/actionable-vectors/no-prompt-injection-required-langchain-chatchats-toolscall-is-a-remote-shell-8c5</link>
      <guid>https://dev.to/actionable-vectors/no-prompt-injection-required-langchain-chatchats-toolscall-is-a-remote-shell-8c5</guid>
      <description>&lt;h1&gt;
  
  
  No prompt injection required: Langchain-Chatchat's &lt;code&gt;/tools/call&lt;/code&gt; is a remote shell
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A vector breakdown of CVE-2026-75128 / GHSA-5rx2-xp53-9x5r (Langchain-Chatchat through 0.3.1.3)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most agent bugs I write about need the model to be talked into something. This one skips the model entirely. Langchain-Chatchat has an HTTP endpoint that runs any registered tool by name, with no authentication, and one of the tools that's always registered is a shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the advisory actually says
&lt;/h2&gt;

&lt;p&gt;From GHSA-5rx2-xp53-9x5r and the VulnCheck advisory, published October 20, 2026 at 03:32 UTC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; Langchain-Chatchat, an open-source RAG and agent app built on LangChain. &lt;strong&gt;Affected:&lt;/strong&gt; through 0.3.1.3. No fixed release is listed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weakness:&lt;/strong&gt; CWE-306, Missing Authentication for Critical Function. &lt;strong&gt;Severity:&lt;/strong&gt; Critical, CVSS 3.1 score 9.8 (&lt;code&gt;AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bug:&lt;/strong&gt; the project "exposes a POST /tools/call API endpoint that invokes any registered tool by name without authentication, leaving the always-registered shell tool callable."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; "Remote attackers can send a single request naming the shell tool to execute arbitrary OS commands as the server user (root in the shipped Docker image)."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit:&lt;/strong&gt; VulnCheck credits Noy Pearl, Michael Sromin and Ophir Dror.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The vector: the agent's toolbox, published as an API
&lt;/h2&gt;

&lt;p&gt;The advisory points at &lt;code&gt;tool_routes.py&lt;/code&gt;. Reading it at the referenced commit, the shape is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;/tools&lt;/code&gt; router has two routes. &lt;code&gt;GET /tools&lt;/code&gt; lists every registered tool with its name, description and argument schema. &lt;code&gt;POST /tools/call&lt;/code&gt; takes a tool name and an input dictionary and runs that tool.&lt;/li&gt;
&lt;li&gt;Neither route has an authentication dependency.&lt;/li&gt;
&lt;li&gt;Tools register themselves when their module is imported. The tools package imports a &lt;code&gt;shell&lt;/code&gt; tool that wraps LangChain's &lt;code&gt;ShellTool&lt;/code&gt;, described as "Use Shell to execute system shell commands."&lt;/li&gt;
&lt;li&gt;So anyone who can reach the API server can list the tools, see &lt;code&gt;shell&lt;/code&gt;, and call it directly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No model is involved, so there's no system prompt to get around and no injection to craft. The tool layer that was meant to sit behind the agent is reachable straight from the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this class of bug exists: three root causes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Agent tools exposed without the agent's controls
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; In an agent app, tools are meant to be called by the model inside a turn, with whatever checks the app places around that turn. A direct "call any tool" endpoint is handy for debugging and for front ends. But it removes every control the agent path had and makes the HTTP layer the only policy. Here the HTTP layer has none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory: the endpoint "invokes any registered tool by name without authentication." In the source, &lt;code&gt;call_tool&lt;/code&gt; looks up the tool by the caller-supplied name and calls &lt;code&gt;ainvoke&lt;/code&gt; on the caller-supplied input. Nothing checks who's asking or which tool they named.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Don't expose a generic tool-invocation route in production. If a front end needs one, put authentication on it and keep an explicit allow-list of tools callable that way, with dangerous tools never on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A dangerous tool on by default
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; Registering every tool when the package is imported is convenient, because nobody has to wire them up. It also means the riskiest tool in the box ships turned on for everyone, including people who never asked for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory calls the shell tool "always-registered." In the source, the tools package imports &lt;code&gt;shell&lt;/code&gt; next to &lt;code&gt;calculate&lt;/code&gt;, &lt;code&gt;weather_check&lt;/code&gt; and the rest, and the &lt;code&gt;@regist_tool&lt;/code&gt; decorator registers it on import.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Tools that execute code, run shell commands or touch the filesystem should be opt-in. Require an explicit config flag to enable them, and log loudly at startup when they are on.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deployment defaults that turn a local bug into a remote one
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; A missing auth check on a service bound to localhost is a local problem. The same check on a service listening on every interface, running as root, is remote code execution with full control of the container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; Outside Docker, the project's settings bind the API server to &lt;code&gt;127.0.0.1&lt;/code&gt; by default, on port 7861. The shipped Dockerfile changes that with &lt;code&gt;chatchat-config server --default_bind_host=0.0.0.0&lt;/code&gt; and never sets a non-root &lt;code&gt;USER&lt;/code&gt;, which matches the advisory's "root in the shipped Docker image." The example &lt;code&gt;docker-compose.yaml&lt;/code&gt; runs the container with &lt;code&gt;network_mode: "host"&lt;/code&gt;, so that listener sits directly on the host's network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Images should run as an unprivileged user, and anything that listens on all interfaces should require authentication. If a container has to bind &lt;code&gt;0.0.0.0&lt;/code&gt; to be reachable, publish the port only to localhost or a private network and put an authenticating proxy in front.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this connects to the approval-gate bugs
&lt;/h2&gt;

&lt;p&gt;A lot of this week's agent advisories are about approval gates that get skipped: by sub-agents, by headless modes, by injected instructions. This one shows what happens when the gate isn't skipped but missing, because the tool can be called without going through the agent at all. Before asking "can the model be tricked into calling the shell?", ask the simpler question: &lt;strong&gt;can anyone call the shell without the model?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Defensive takeaways you can check this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find out if you run it.&lt;/strong&gt; Langchain-Chatchat through 0.3.1.3 is affected and no fixed release is listed, so watch the &lt;a href="https://github.com/advisories/GHSA-5rx2-xp53-9x5r" rel="noopener noreferrer"&gt;advisory&lt;/a&gt; for one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check exposure first.&lt;/strong&gt; Find out whether port 7861 (the API server) is reachable from anywhere beyond the machine itself. If you used the shipped Docker image or compose file, assume it listens on all interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block &lt;code&gt;/tools&lt;/code&gt; at the edge.&lt;/strong&gt; Put the API server behind a reverse proxy that requires authentication, and deny &lt;code&gt;/tools&lt;/code&gt; and &lt;code&gt;/tools/call&lt;/code&gt; outright unless you actually use them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn off the shell tool.&lt;/strong&gt; If your deployment doesn't need shell commands, remove or disable that tool rather than relying on network controls alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't run as root.&lt;/strong&gt; Rebuild the image with a non-root user and a read-only filesystem where you can, so one bad request doesn't own the container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look at your logs.&lt;/strong&gt; Search the API server or proxy logs for &lt;code&gt;POST /tools/call&lt;/code&gt; from addresses you don't recognize, and if you find any, treat the host and any keys on it as compromised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit your own agent apps.&lt;/strong&gt; Search for routes that take a tool name from the request and invoke it. Every one needs authentication and an allow-list, whatever framework you use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/advisories/GHSA-5rx2-xp53-9x5r" rel="noopener noreferrer"&gt;GitHub advisory GHSA-5rx2-xp53-9x5r&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-75128" rel="noopener noreferrer"&gt;NVD entry for CVE-2026-75128&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.vulncheck.com/advisories/langchain-chatchat-through-0.3.1.3-unauthenticated-rce-via-tools-call-endpoint" rel="noopener noreferrer"&gt;VulnCheck advisory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.lasso.security/vulnerability-database/cve-2026-75128" rel="noopener noreferrer"&gt;Lasso Security vulnerability database entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chatchat-space/Langchain-Chatchat/blob/a6a9193b5002061719f0eee62c6310bd5b1b7f63/libs/chatchat-server/chatchat/server/api_server/tool_routes.py#L33" rel="noopener noreferrer"&gt;tool_routes.py at the advisory's referenced commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chatchat-space/Langchain-Chatchat/blob/a6a9193b5002061719f0eee62c6310bd5b1b7f63/docker/Dockerfile" rel="noopener noreferrer"&gt;Shipped Dockerfile at the same commit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you're building agent backends and want a checklist for this kind of trust boundary, the &lt;a href="https://actionable0.gumroad.com/l/mtsdk" rel="noopener noreferrer"&gt;Trust Boundary pack&lt;/a&gt; covers it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance and edited by a human. All facts come from the advisory, its CVE record and the source files it references.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>python</category>
      <category>appsec</category>
    </item>
    <item>
      <title>The approval gate that always said yes: OpenSRE's headless shell bypass</title>
      <dc:creator>Actionable Vectors</dc:creator>
      <pubDate>Wed, 21 Oct 2026 11:10:08 +0000</pubDate>
      <link>https://dev.to/actionable-vectors/the-approval-gate-that-always-said-yes-opensres-headless-shell-bypass-1686</link>
      <guid>https://dev.to/actionable-vectors/the-approval-gate-that-always-said-yes-opensres-headless-shell-bypass-1686</guid>
      <description>&lt;h1&gt;
  
  
  The approval gate that always said yes: OpenSRE's headless shell bypass
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A vector breakdown of CVE-2026-109444 / GHSA-fqh3-76w5-5vvf (OpenSRE through 0.1.2026.10.19)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I write a lot about approval gates that check the wrong thing. This one doesn't check anything. In OpenSRE's headless mode the function that decides whether the agent may run a shell command ignores its inputs and returns &lt;code&gt;True&lt;/code&gt;. Put that next to an agent whose job is reading alerts and Slack messages, and anyone who can get text in front of it can run commands on the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the advisory actually says
&lt;/h2&gt;

&lt;p&gt;From GHSA-fqh3-76w5-5vvf and the VulnCheck advisory, published October 20, 2026 at 03:32 UTC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; OpenSRE (Tracer-Cloud), an AI site-reliability agent. &lt;strong&gt;Affected:&lt;/strong&gt; through 0.1.2026.10.19. No fixed release is listed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weakness:&lt;/strong&gt; CWE-862, Missing Authorization. &lt;strong&gt;Severity:&lt;/strong&gt; High, CVSS 3.1 score 8.8 (&lt;code&gt;AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H&lt;/code&gt;). It's network-reachable, needs no privileges, and needs one "user interaction": the agent has to triage the attacker's content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bug:&lt;/strong&gt; "an approval gate bypass vulnerability that allows attackers to execute shell_run commands without confirmation because the headless presenter's execution_allowed() always returns True."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; "Attackers can inject instructions into alerts or Slack messages triaged by the agent to run commands on the host and exfiltrate LLM, Slack and cloud credentials."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit:&lt;/strong&gt; VulnCheck credits Noy Pearl, Michael Sromin and Ophir Dror.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advisory links two lines in the source, and both are worth reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vector: untrusted text in, shell commands out
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;An attacker gets text in front of the agent. The advisory names two routes: an alert the agent is asked to triage, or a Slack message.&lt;/li&gt;
&lt;li&gt;The text contains instructions. The model can't reliably tell "data I'm analyzing" from "instructions I should follow," so it decides to call &lt;code&gt;shell_run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Before running a command, OpenSRE asks the active presenter whether execution is allowed. In the interactive terminal that's where a human confirms.&lt;/li&gt;
&lt;li&gt;In headless surfaces (the Slack gateway, scheduled runs, embedded use) the presenter is &lt;code&gt;HeadlessSubprocessPresenter&lt;/code&gt;. Its &lt;code&gt;execution_allowed()&lt;/code&gt; takes the policy result and an action summary, discards both, and returns &lt;code&gt;True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The command runs on the host with whatever the process can reach, and the advisory says that includes LLM, Slack and cloud credentials.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No authentication step is skipped. The attacker never logs in. They just write something the agent will read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this class of bug exists: three root causes
&lt;/h2&gt;

&lt;p&gt;The advisory covers what's wrong, not the fix. Here's the class split into causes you can look for in any agent framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Approval treated as a UI feature instead of a security control
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; The decision to run a risky action is handed to the presentation layer, the part that draws prompts and buttons. That works when there's a terminal and a human. When the same agent runs headless, the presentation layer has nobody to ask, so someone writes a stub. A stub for a print method is harmless. A stub for the permission check is the vulnerability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The module's own docstring describes it as the presenter "for non-TTY agent surfaces (gateway, scheduled, embedded)." Its &lt;code&gt;execution_allowed()&lt;/code&gt; ignores the &lt;code&gt;ExecutionPolicyResult&lt;/code&gt; it's handed and returns &lt;code&gt;True&lt;/code&gt;. The project's commit history shows the presenter was registered in an August 2026 change (#4845) so that "the default agent can execute" &lt;code&gt;shell_run&lt;/code&gt;. That was a reasonable fix for "the agent can't run commands," made through a component that never enforced policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; The allow/deny decision belongs in the execution path, not the presenter. The presenter can show a prompt, but the policy result itself should decide whether the command runs. If a surface can't ask a human, the policy has to answer, and the safe default answer for an unreviewed shell command is no.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "No human available" fails open
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; Approval prompts are annoying in chat. They stall, time out and clutter threads. The tempting fix is to drop them on that surface. Unless something else takes over the gate, that turns "needs approval" into "approved."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The second line the advisory links is in the Slack dispatcher, where a comment says: "No approval prompts in Slack for now. Button waits stalled the turn until the timeout and posted extra replies in the thread." The turn is then set up with &lt;code&gt;tool_hooks=None&lt;/code&gt;. That's an honest usability tradeoff. It also means that in Slack the shell tool runs on the model's say-so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; When an approval can't be collected, fail closed: refuse the action, or queue it for a human somewhere else and tell the user it's waiting. Faster is fine for read-only tools. Anything that writes, executes or touches credentials should be denied by default on surfaces with no approver.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The agent reads attacker text with operator authority
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; An SRE agent exists to read noisy, untrusted input such as alerts, logs and messages from anyone in a channel. If the same model turn that reads that input can also call a shell, the attacker's text has the same pull on the model as the operator's instructions. And if the shell runs in a process holding long-lived credentials, every successful injection becomes a credential leak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; The advisory's attack path is exactly this: instructions "inject[ed] into alerts or Slack messages triaged by the agent," leading to command execution and exfiltration of "LLM, Slack and cloud credentials." The CVSS vector requires no privileges, only that the agent processes the content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixes it.&lt;/strong&gt; Treat everything the agent triages as untrusted data. Split reading from acting: a turn that ingests external content shouldn't hold the shell tool, or its tool calls should need approval that untrusted content can't grant. Run the executor without the agent's own API keys in its environment, and give it scoped, short-lived cloud credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this connects to the other approval-gate bugs
&lt;/h2&gt;

&lt;p&gt;Usually the gate exists but checks too little, such as the verb without the object or the parent agent without its sub-agents. Here the gate exists in the interactive path and is a no-op in the headless one. It's the same lesson again: &lt;strong&gt;an approval control is only as strong as its weakest execution surface.&lt;/strong&gt; If you can't name who approves a shell command in Slack, nobody does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defensive takeaways you can check this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find out if you run it.&lt;/strong&gt; If you have OpenSRE at or below 0.1.2026.10.19, assume headless surfaces (Slack gateway, scheduled runs, embedded use) execute &lt;code&gt;shell_run&lt;/code&gt; with no confirmation. No fixed release is listed yet, so watch the &lt;a href="https://github.com/advisories/GHSA-fqh3-76w5-5vvf" rel="noopener noreferrer"&gt;advisory&lt;/a&gt; for one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop exposing the headless path to untrusted input.&lt;/strong&gt; Until there's a fix, don't connect the Slack gateway to channels where people outside your on-call team can post, and don't point scheduled triage at alert sources that carry text an outsider controls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take the credentials away from the shell.&lt;/strong&gt; Run the agent in a container or VM whose environment doesn't hold your LLM keys, your Slack bot token or broad cloud credentials. Use scoped, short-lived roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate if you were exposed.&lt;/strong&gt; If an affected version triaged outside content in headless mode, rotate the LLM, Slack and cloud credentials it could read, and review its command history and outbound network logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit your own agents for the same pattern.&lt;/strong&gt; Search for approval or &lt;code&gt;*_allowed&lt;/code&gt; methods that return a constant, and for surfaces that pass &lt;code&gt;None&lt;/code&gt; for hooks or approvers. For each one, ask what happens when nobody is there to click.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the headless path.&lt;/strong&gt; Add a test that puts a prompt-injection string in an alert or chat message and asserts the shell tool is refused, not just that a prompt was shown.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/advisories/GHSA-fqh3-76w5-5vvf" rel="noopener noreferrer"&gt;GitHub advisory GHSA-fqh3-76w5-5vvf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-109444" rel="noopener noreferrer"&gt;NVD entry for CVE-2026-109444&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.vulncheck.com/advisories/opensre-through-0.1.2026.10.19-approval-gate-bypass-via-shell-run-prompt-injection" rel="noopener noreferrer"&gt;VulnCheck advisory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.lasso.security/vulnerability-database/cve-2026-109444" rel="noopener noreferrer"&gt;Lasso Security vulnerability database entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Tracer-Cloud/opensre/blob/8eba8c9301fa954d9eecb1693b7e2021215ec2ec/tools/interactive_shell/subprocess_presenter.py#L41" rel="noopener noreferrer"&gt;subprocess_presenter.py at the advisory's referenced commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Tracer-Cloud/opensre/blob/8eba8c9301fa954d9eecb1693b7e2021215ec2ec/gateway/transports/slack/processing/dispatcher.py#L276" rel="noopener noreferrer"&gt;Slack dispatcher at the advisory's referenced commit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you're building agent backends and want a checklist for this kind of trust boundary, the &lt;a href="https://actionable0.gumroad.com/l/mtsdk" rel="noopener noreferrer"&gt;Trust Boundary pack&lt;/a&gt; covers it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance and edited by a human. All facts come from the advisory, its CVE record and the source lines it references.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>llm</category>
      <category>appsec</category>
    </item>
    <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>
