Intelligence

The Agent Was Allowed to Update One Record. The Tool Let It Choose the Set.

AshAi patched a broken access control in the identity filter behind its update and destroy tools on 30 August 2026. The tool was correctly authorized to act on one record its caller named. The filter that was supposed to enforce that binding accepted a value shaped as a query instead of a value, and let the caller's own argument choose a different target entirely.

Event analysed: . This analysis was published on 31 August 2026.

When an AshAi tool is authorized to update or destroy the one record its identity argument names, does that authorization also guarantee which record the operation actually reaches once it executes?

Not on its own, and a patched AshAi vulnerability is a precise demonstration of why. GHSA-jg86-xh36-h5xc, published by the ash-project maintainers on 30 August 2026 and assigned CVE-2026-82564, describes a broken access control, classified under CWE-639, in the identity_filter function inside AshAi.Tool.Execution. AshAi is an extension in the Ash Framework ecosystem that lets an Ash application expose its own actions, defined in its own domains and resources, as tools an LLM agent can call, including through a production capable Model Context Protocol server AshAi ships. A tool built on an update or destroy action carries an identity option naming the attribute, or attributes, meant to resolve exactly one record: the one the caller identified. In every AshAi release from 0.6.0 up to but not including 1.0.0, the vulnerable identity_filter took that identity argument's raw value straight from the tool call and handed it, unmodified, to Ash's own query filter. Ash's filter builder accepts a map as a predicate expression rather than only as a literal value, so a caller who supplied a nested comparison object where a scalar identifier belonged could turn what should have been an exact match into an arbitrary selection. The maintainers' own worked example addresses a Todo resource whose identity is keyed on a public_ref attribute: an attacker owning a record with public_ref set to attacker-ref, calling an update tool through AshAi's MCP router with public_ref set to a nested not_eq comparison against attacker-ref rather than a plain string, retargets the update at a victim's own differently valued record instead, and the tool call reports success. The advisory states plainly that the tool's advertised JSON schema was not enforced on this path, because the runtime's own input validation checked only the action's ordinary input arguments, not the identity arguments used to resolve the target. AshAi 1.0.0, released the same day, closes this by casting every identity value to its resource field's declared type before it ever reaches the filter, rejecting anything that is not the scalar the field expects. The GitHub Security Advisory records CVSS 4.0 base score 7.1, High severity, and no evidence, in the advisory or in this record's own search, that the vulnerability was exploited against a real deployment before the patch. Moona Intelligence read the vulnerable and patched source directly, and confirmed a caller supplied argument, not a defect in AshAi's stated identity mechanism itself, is what let one authorized operation reach a record its own configuration was never meant to expose.

AshAi authorized the operation correctly. An update or destroy tool built on an Ash action, with an identity option naming the attribute that should resolve its target, is exactly the kind of narrow, record scoped capability a careful integration wants to hand an agent. The gap this vulnerability opens sits one layer below that decision, in the mechanism that was supposed to turn a caller's identity argument into a filter matching one record, and it is worth reading closely because the operation being correctly authorized is precisely what makes the failure easy to miss.

What AshAi actually is, and what identity is inside it

AshAi is part of the Ash Framework ecosystem, the extension that gives an Ash application structured outputs, vectorization and tool calling for language models built on top of the application's own domains and resources. Rather than writing a separate integration layer, a developer marks an existing Ash action, update, destroy, read or a generic action, as a tool, and AshAi generates the schema an LLM agent sees and the execution path that runs when the agent calls it. AshAi also ships a Model Context Protocol server, AshAi.Mcp.Router, described in its own documentation as production capable rather than a development convenience only, exposing a configured set of tools to any MCP client over a standard JSON RPC transport.

An update or destroy tool needs to know which record it is acting on. AshAi resolves that through an identity option on the tool definition, naming an Ash identity, a set of one or more attributes the underlying resource already treats as uniquely addressing a record, such as tool update_artist, Artist, update, identity: id. This is an ordinary use of a concept Ash Framework already had for entirely unrelated reasons: an Ash identity exists to let a resource's own code look up one record by a stable key. AshAi's contribution is deciding that the same identity mechanism is also the right way to let a caller's tool argument name the one record an update or destroy tool may touch.

Worth being precise about early, because the vulnerability's own name invites confusion. The identity this record is about is record identity, which attribute or attributes address one row of a resource. It is not agent identity, which actor is calling the tool, what credential it holds, or whether it is authenticated. AshAi's identity option never claimed to authenticate a caller. It claimed to resolve a target, and that is the claim this vulnerability breaks.

The failure in identity_filter

Reading the vulnerable and the patched source directly against each other in the ash_ai repository shows exactly what changed. In every release from 0.6.0 through 0.8.2, the last release before the fix, identity_filter built its filter or expression by reading each identity attribute's value straight out of the tool call's raw arguments with Map.get, then passing that value, unexamined, into Ash's own filter construction, either a keyword list handed to Ash.Query.do_filter or an Ash.Expr.expr equality check built from the same unexamined value. Ash's filter builder is not naive about what a map means: it is built to accept a map as a predicate expression, a comparison operator and an operand, precisely because Ash filters are meant to express more than bare equality elsewhere in the framework. Nothing in identity_filter's vulnerable path stopped a caller from handing it exactly that kind of structure in a slot the tool's own contract expected to hold a scalar identifier.

The maintainers' own advisory demonstrates the consequence with a resource identified by a public_ref attribute. An attacker's own record carries public_ref set to attacker-ref. Rather than supplying that scalar value to identify their own record, the attacker supplies a nested comparison object equivalent to public_ref does not equal attacker-ref. Ash's filter builder reads that structure as exactly what it says: match any record whose public_ref is not attacker-ref. Combined with a limit of one and Ash's own bulk update or bulk destroy operations, that filter resolves to some other record, not the caller's own, and the update or destroy the caller was authorized to perform executes against it instead. The advisory states that the tool call reports success, that the attacker's own record is left unchanged, and that a victim's differently valued record is the one that is modified. Nothing about the operation itself was ever unauthorized. The tool was built to let this caller update a Todo, or whichever resource was exposed, identified by public_ref. What failed is narrower: the value meant to pin that update to one specific record could itself be reinterpreted as an instruction to search for a different one.

The advisory states the point that matters most for what an authorization decision can safely assume: the advertised JSON schema was not enforced on this path. AshAi's own input validation checks the ordinary action arguments a tool call supplies, but the vulnerable code did not run that same check against the identity arguments used to resolve the target. A schema telling an agent, and telling anyone reviewing the tool's contract, that an identity field is a string does not, on its own, guarantee the executor treats a supplied value as that string rather than as something the underlying query layer knows how to parse into a different kind of instruction.

Where this was demonstrated, and where the failure actually lives

The maintainers' proof of concept runs the exploit as an ordinary MCP JSON RPC tools call request, reaching the vulnerable tool through AshAi.Mcp.Router. That detail is worth stating precisely, because it is easy to read backward into a claim this record does not make. MCP did not cause this vulnerability. AshAi's MCP router transported an entirely conforming tool call, exactly the shape a legitimate client would send, carrying a value in the identity argument the router had no reason to inspect differently than any other JSON payload. The router's job is to move a tool call from a client to the tool's execution path. It did that correctly. The failure is entirely inside identity_filter's own interpretation of the value once it arrived: whether an update or destroy operation was allowed to act on the record it resolved, which is a question the execution layer answers, not the transport that carried the request there. What the MCP demonstration actually establishes is narrower and, for anyone building agent facing tools on any protocol, more durable: an ordinary, conforming tool call, over a completely standard transport, was enough to reach the failure. No prompt injection, no malicious client behavior and no protocol level exploit was needed.

Operation authority is not target authority

The distinction this vulnerability isolates cleanly is one that a tool's authorization story can otherwise blur without anyone noticing. Deciding that a caller, human or agent, may invoke an update or destroy tool at all is one authorization decision: may this operation run against this resource type. Deciding which record or records that specific invocation actually reaches is a second, separate decision, and AshAi's identity option exists specifically to answer it. Treating the second decision as automatically settled once the first is granted is the exact shape of this vulnerability. The tool's operation authority, that this caller may call update_todo, was never in question and never failed. What failed is the assumption that an identity argument, once supplied, could only ever narrow the operation to the one record it apparently named. A value shaped as a predicate rather than a literal broadens it instead, and nothing downstream of the tool's own authorization decision was positioned to catch that the target had widened.

Put plainly: authorizing the tool or the operation does not complete the authority decision if a target identifying argument can be reinterpreted as a broader selection expression once it reaches the layer that actually resolves a record. An identifier and a query occupy the same JSON shape, a string here, a small object there, and only the code that consumes the value decides which one it is being handed. AshAi's vulnerable path let the caller decide that instead of the tool's own contract.

What the fix actually does

AshAi 1.0.0, released the same day as the advisory, closes the gap by casting, restoring the distinction the vulnerable path erased. The patched identity_filter routes every identity value through a helper that casts it to the exact type the resource's own field declares before any filter or expression is built from it. A value that cannot be cast to that scalar type, a map among them, is rejected rather than silently accepted and handed downstream. This is a narrower fix than validating the argument more strictly while still allowing it to flow into a general purpose filter builder. It removes the ambiguity at its source: by the time a value reaches Ash's query layer, it is already the literal type the resource's identity attribute expects, and nothing shaped like a comparison operator can survive that cast.

Kept separate: five other advisories from the same release

AshAi 1.0.0 patched six vulnerabilities at once, each assigned its own CVE, and it would be a mistake to read this record as covering all six or to describe them as one event. The other five are a prompt template evaluated as executable EEx code, an MCP origin check that trusted a spoofable Host or X Forwarded Proto header, an infinite loop in the tool call loop's own termination condition, embedding provider errors leaking into user facing error messages, and tool errors formatted from a raw, unsafe message rather than a sanitized one. Each is a real, distinct failure mode with its own mechanism and its own fix. None of them shares the identity filter's mechanism, and grouping them together would manufacture a false pattern where the actual evidence supports a narrower and more precise one: this record is about CVE-2026-82564 and GHSA-jg86-xh36-h5xc specifically, not about AshAi's release as a whole.

What this does not establish

This is a patched, maintainer disclosed vulnerability, not a confirmed exploitation. The GitHub Security Advisory, read directly, states the affected version range as 0.6.0 up to but not including 1.0.0 and the patched version as 1.0.0, records a CVSS 4.0 base score of 7.1, High severity, under CWE-639, authorization bypass through a user controlled key, and this record found no evidence, in the advisory or in independent search, of exploitation against a real deployment before the patch. The advisory publication, 30 August 2026, and CVE-2026-82564's own separate entry into the broader CVE ecosystem are kept distinct in this record's own dating: the advisory itself already carries the CVE identifier, and this session's direct access to the CVE Program's own record and to the National Vulnerability Database was blocked at its network egress policy, so this record does not independently confirm a specific CVE List publication date beyond what the GitHub advisory itself states and carries as reported chronology rather than as separately verified by direct fetch. Nor does this record claim every AshAi deployment before 1.0.0 was reachable by an untrusted caller. Whether an application exposed an identity configured update or destroy tool to anyone other than a trusted internal caller, over MCP or any other transport, is a fact about that specific deployment, not about AshAi generally, and the maintainers' own reproduction used one specific, unauthenticated MCP configuration to demonstrate the mechanism, not a claim about how every integration is configured.

Where this sits against what this desk has already argued

A patched Cursor vulnerability showed an approved command's own text staying exactly what a developer approved while an unmonitored piece of shared state decided what that command actually did once it ran. This vulnerability is a different member of the same family, not the same failure. Cursor's gap sat between an approval and an execution separated by ambient, mutable state a human never saw. AshAi's gap has no approval step in it at all: it is a machine enforced authorization mechanism, the identity filter, whose own single argument was itself the thing reinterpreted, with no environment in between to poison.

A cluster of patched AWS vulnerabilities showed a field that decided a control's own applicability, a consent gate's off switch among them, sitting in the same schema as the action itself. AshAi's identity argument is not a field that governs whether a control applies. It is the field the control exists to interpret. The consent gate parallel would be closer if an off switch decided whether the identity check ran at all; here the check always ran, and still resolved the wrong target, because the value it was checking against could mean two different things depending on its shape.

Rubrik's Agent Identity, as the vendor documents it, mints a scoped credential for one tool call only after that call has already cleared an intent and access decision, which is a claim about when authority for a call is granted, before or after the call is known. AshAi's vulnerability is not about timing. The tool call was already fully known, the operation was already authorized, and the failure was in what the target resolution step did with an argument that had already arrived.

The broader argument that authority has to be evaluated independently at the moment an action executes, not assumed from an earlier instruction or grant, is the family this record belongs to, and this vulnerability sharpens a part of it the corpus had not yet isolated on its own terms. Evaluating authority at execution only closes a gap like this one if what gets evaluated includes the exact, normalized value an argument resolves to, not only whether the operation and the resource type were permitted. A value that reaches an execution layer still shaped as something other than the literal the authorization decision assumed it was is a target the authorization decision never actually covered, whatever it decided about the operation.

The question worth carrying forward

An identity argument and a query predicate can occupy the exact same shape in a JSON payload, and only the code on the receiving end decides which one it is being handed. AshAi's fix answers the question the same way this desk has seen answered elsewhere in different vocabulary: normalize the value to the literal type the authorization decision actually requires before it reaches anything capable of reading it as more than that. The gap itself is not specific to AshAi, to Ash Framework, or to this one CVE. It is specific to any tool whose target identifying argument reaches a query layer without first being pinned to the exact value its own authorization was supposed to mean.

Sources

This analysis interprets third-party reporting, research and announcements. Moona is not the original reporter of the underlying events.

[2]
chore: release version v1.0.0
ash-project/ash_ai (GitHub) · 30 August 2026 · Primary source
[3]
ash_ai, lib/ash_ai/tool/execution.ex at tag v0.8.2 (last release before the fix)
ash-project/ash_ai (GitHub) · 3 August 2026 · Primary source
[4]
ash_ai, lib/ash_ai/tool/execution.ex at tag v1.0.0 (patched)
ash-project/ash_ai (GitHub) · 30 August 2026 · Primary source
[5]
ash-project/ash_ai
ash-project (GitHub) · 30 August 2026 · Technical documentation
[6]
AshAi DSL, tool entity reference
HexDocs, ash_ai · 30 August 2026 · Technical documentation
[7]
CVE-2026-82564
CVE Program · 31 August 2026 · Regulatory source

Protocol evidence

This record does not assess these architectures. The connection runs through the Risk Registry requirement each one bears on, and these published authority architectures are what the evidence says about that requirement.

Protocol evidence related through AEW-013 Authorization over an operation treated as authorization over its target

  • Supports requirement

    Agent Action Decision Protocol (AADP)

    Shamik Saha, individual submission to the IETF

    Requirement The draft separates agent identity and standing access from authorization of one concrete action

    AADP's own separation states that identity and token layers answer who is acting and what a principal may reach, while the authorization decision itself must evaluate a specific proposed action with specific argument values. AshAi's identity filter collapsed exactly that separation: identity configuration named which attribute should address the target, and the runtime accepted whatever value shape the argument carried instead of the specific value the operation required.

    This record is the cited evidence for this relationship.

    View protocol evidence

  • Supports requirement

    EP Authorization Receipts (EMILIA Protocol)

    Iman Schrock, EMILIA Protocol, Inc., individual submission to the IETF

    Requirement The Action Object in revision 12 names ep_version, action_type, target, parameters, initiator, policy_id and requested_at as its required fields, canonicalized under RFC 8785 and hashed with SHA 256

    EMILIA's Action Object treats target as a required field, canonicalized and hashed alongside the action type, before anything is authorized, so a target cannot be silently reinterpreted after the fact. AshAi's vulnerable identity filter shows the consequence of a target identifying value that is not pinned this way: a caller supplied structure the executor could parse as a broader expression rather than the one value it named.

    This record is the cited evidence for this relationship.

    View protocol evidence

Related Intelligence

All Intelligence Records →