The Template Was the Backdoor: ash_ai's Six-CVE Cluster and the Architecture of Unbounded Trust

CryptoEagle
Layer2

There is a single line of Elixir that should never have existed. Inside AshAi.Actions.Prompt, the ash_ai framework hands user-supplied prompt text directly to EEx.eval_string/2 β€” the Embedded Elixir runtime that parses and executes strings as code. Not a sanitization gap. Not an edge case that slipped past a boundary check. The prompt was the code.

That one design decision produced CVE-2026-77956, an unauthenticated remote code execution vulnerability rated CVSS 8.9. Any service running a vulnerable ash_ai version, exposed to a network, allowed whoever could reach the endpoint to execute arbitrary Elixir operations on the host. From reconnaissance to full host takeover, the entire kill chain collapsed into a single HTTP request carrying crafted prompt text.

The disclosure did not stop there. Six coordinated CVEs β€” striking prompt evaluation, tool execution, MCP origin authentication, query predicate construction, credential handling, and orchestration loops β€” were published the same week ash_ai finally reached v1.0.0. This was also the first coordinated AI-framework disclosure in the entire Elixir/Ash ecosystem. A project that had never shipped a stable release was already bleeding through every layer of its stack.

As someone who has spent the better part of a decade reconstructing protocol failures β€” from the Ethereum 2.0 Slasher specification to the Ronin bridge post-mortem β€” I have learned one uncomfortable invariant: when a system fails across six independent layers simultaneously, the fault is never in the code. It is in the design philosophy that produced the code. The proof is in the unverified edge cases: the corners where nobody asked what happens when an attacker controls the input before the template engine does.

The Elixir/Ash community has been building its AI story around a genuinely interesting stack. The Ash framework is a declarative application layer on the BEAM virtual machine, inheriting Erlang's concurrency model, fault tolerance, and hot code reloading. For agent workloads, these properties matter: supervision trees can isolate misbehaving subprocesses, immutable data structures simplify reasoning about state transitions, and the pattern-matching idiom encourages explicit control flow. The enthusiast narrative was seductive: functional programming as an innate safety net, Elixir as the rational alternative to Python's agent sprawl.

Then ash_ai entered the stack, bridging Ash applications to large language models and the Model Context Protocol. MCP itself was already struggling with security maturity. Earlier in 2026, coordinated research had surfaced high-severity MCP-related vulnerabilities in platforms like Splunk, HashiCorp, and ServiceNow. The protocol is young, evolving quickly, and its implementations have repeatedly demonstrated a troubling pattern: engineering teams competent at building integration layers, insufficiently skeptical about hostile input.

Into this landscape, the ash_ai six-CVE cluster arrived β€” not as a footnote, but as a case study in what happens when an agent framework optimizes for developer ergonomics ahead of threat modeling.

The headline vulnerability deserves precise reconstruction. EEx.eval_string/2 is the template evaluation core of Elixir's HTML/template tooling. It compiles the provided string as an EEx template, executing any embedded Elixir expressions in the caller's context. In legitimate usage β€” rendering server-side views, generating emails, constructing configuration files β€” this is powerful and conventional. The danger is existential when the template source originates from an external, untrusted party.

ash_ai treated the LLM prompt as a template. In doing so, it committed to the doctrine of template-as-code: the agent's most exposed input surface β€” the text that any user, anywhere, can influence β€” was compiled and executed in the server's runtime environment. This is not a subtle flaw that requires chaining multiple conditions. An attacker who could cause the agent to process a prompt containing Elixir expression syntax could execute arbitrary operations on the underlying host. The CVSS 8.9 rating is arguably conservative: the unauthenticated nature of the requirement, combined with the complete loss of confidentiality, integrity, and availability, borders on a 10.0 under any reasonable deployment assumption.

The forensic detail that deserves attention is the phrase "designed around." ash_ai did not accidentally invoke EEx.eval_string/2 in a legacy codepath. The implementation deliberately routed prompt construction through a template engine, presumably to enable dynamic prompt composition and variable substitution. This is the architectural sin: prioritizing expressive flexibility over the iron law of input isolation. In my 2017 audit of the Ethereum 2.0 Slasher prototype, the same class of flaw appeared in proposer slashing conditions β€” unvalidated state transitions were being passed to assertion logic, and the result was state-reversion vulnerabilities that could corrupt the beacon chain's finality accounting. We did not call those bugs. We called them design failures.

The second vulnerability in the cluster, CVE-2026-81315, is a textbook MCP origin bypass accomplished through what every web security engineer recognizes as a fifteen-year-old technique: attacker-controlled Host and X-Forwarded-Proto header manipulation. The ash_ai MCP server validated incoming requests based on origin information derived from these headers, allowing an attacker to forge a legitimate origin context and execute cross-site request-style attacks. In an MCP deployment, where the protocol broker mediates tool invocations on behalf of an agent, this translates into unauthorized tool invocation with the agent's full privilege set.

The pattern is not novel. DNS rebinding attacks, CSRF via header spoofing, and origin-confusion exploits have been documented, mitigated, and re-documented across generations of web frameworks. Yet here they are, resurrected in a 2026 AI infrastructure component, precisely because the implementing team treated MCP as a fresh protocol domain rather than an extension of the adversarial web environment. The protocol is new. The attack surface is ancient.

CVE-2026-82564 exhibits the third architectural failure mode: the agent's authorization layer parsed nested JSON payloads directly into Ash query predicate expressions. An attacker with access to any authorized β€” or even partially authorized β€” client path could inject nested structures that bypassed permission checks and executed arbitrary record updates or destructions. In agent terms, this is the difference between a tool that reads your calendar and a tool that deletes every calendar entry in the tenant. The vulnerability demonstrates that query construction in agent frameworks must be treated with the same severity as raw SQL string concatenation. When a framework's query layer accepts structured predicates, every node in that structure is an injection point until proven otherwise.

The remaining three vulnerabilities β€” credential leakage, an infinite-loop condition, and schema disclosure β€” look unremarkable in isolation. Low CVSS scores. Narrow exploit windows. The kind of findings that security engineers triage, patch, and close within a sprint. This is exactly the wrong assessment. Considered as a composite attack chain, they complete the arc from discovery to persistence: schema disclosure allows the attacker to map the application's data model, credential leakage elevates the attacker's access tier, and an infinite-loop condition enables denial-of-service or timing-based oracle attacks. Agent amplification is real: the sum of these vulnerabilities is materially more dangerous than each of their individual CVSS ratings. The security industry learned this lesson with chained web exploits and again with bridge hacks. The agent stack has now produced its own curriculum.

What separates this disclosure from routine vulnerability announcements is the vector that produced it. The researcher, PJUllrich, reported using LLM-assisted security research to identify and analyze the cluster. Let that settle. The same technology these frameworks are built to serve is now being used to dismantle them at scale. LLM-assisted exploitation is not a future threat β€” it is a current methodology, and it collapses the cost curve of vulnerability discovery. A junior security analyst with a competent LLM scaffolding tool and a public GitHub mirror can now enumerate attack surfaces across an entire language ecosystem in hours. The asymmetry is brutal: attackers now move at machine speed, while maintainers still patch at human speed.

This is the deeper reason the ash_ai disclosure matters beyond Elixir. The vulnerability research landscape for AI frameworks is being industrialized in real time. The implication is unambiguous: every agent framework with meaningful adoption is now operating in a high-throughput adversarial environment, regardless of whether its maintainers have chosen to belief it.

The contrarian angle β€” and I do not use that phrase lightly β€” is that the predominant industry response to this disclosure is precisely backwards. The coordinated disclosure process is being narrated as evidence of project maturity. ash_ai is an open-source project that reached out to vendors, produced patches, and shipped v1.0.0 with the bugs documented. The Elixir community can frame this as responsible security practice. Some will. Others will note that coordinated disclosure is the minimum viable behavior for any project that intends to be taken seriously β€” it is not a certification of security posture, and the absence of an announced zero-day exploitation wave is not evidence that this vulnerability cluster was never exploited in the wild.

Nor should functional programming be allowed to function as a security shield in the aftermath narrative. Yes, Elixir's immutability and Ash's declarative design resist entire categories of memory-safety and state-corruption bugs. But EEx.eval_string/2 does not care about immutability. It compiles strings into execution. The BEAM's fault tolerance does not care about authorization boundaries. It restarts the process that just executed your attacker's payload, which is exactly what you do not want. The feature that makes functional programming attractive in construction β€” explicitness β€” was explicitly abandoned at the moment prompt text was allowed to become code. Ronin did not fail; it was engineered to trust. ash_ai did not merely contain bugs; it was architected to evaluate.

When the math holds but the incentives break, the equilibrium shifts. The immediate incentive for a framework trying to gain adoption in the crowded agent market is maximum flexibility: dynamic prompts, expressive tool definitions, minimal configuration friction. The security incentive is the opposite: restrictive types, validated schemas, delegated execution contexts, sandboxing boundaries. These incentives are in direct tension, and when a framework prioritizes one at the expense of the other, the outcome is predictable. The ash_ai team is now paying the accumulated difference, in reputation, in patch cycles, and in the audit debt that every long-term user will demand before production deployment.

For security-conscious enterprises β€” finance, healthcare, government-adjacent workloads β€” the existence of an unauthenticated RCE in the prompt path is not an abstraction. Procurement reviews will extend, security questionnaires will grow, and the reasonable first question from any competent CISO is no longer "does it work?" but "was it designed so that this class of failure is structurally impossible?" Against established Python and JavaScript ecosystems like LangChain or LlamaIndex, ash_ai started with a community-size disadvantage. This disclosure has widened the credibility gap. Yet it has also opened a distinctive opportunity: a public, comprehensive threat model, a hardened template pipeline, and a documented defense-in-depth story could reposition ash_ai as the framework built for hostile environments. Elixir's BEAM foundation β€” supervision, isolation, hot upgrades β€” is genuinely compelling infrastructure for high-assurance agent deployments, if the framework layer stops contradicting it.

The broader regulator of this market will be the MCP protocol itself. The vulnerability recurrence across Splunk, HashiCorp, ServiceNow, and now ash_ai demonstrates that implementation-level patching will always lag attack engineering. The protocol needs mandatory origin verification, host pinning, and authentication semantics β€” not recommendations, not best-practice appendices, but normative requirements that implementations cannot silently omit. Until MCP makes request provenance structurally enforceable, every new implementation is a tribal-knowledge test of whether its authors learned web security in the last two decades. The user's LLM-tooling stack will keep absorbing the ignored lessons of the browser wars.

The technical community should also accept a strategic reality: the LLM-assisted security researcher is now a permanent part of the ecosystem. This is not an argument for panic. It is an argument for automation parity. Frameworks that embed security scanning into their CI pipelines, that fuzz their own template and query builders with adversarial prompts, and that treat their attack surface as a first-class artifact will survive. Projects that rely on periodic human audits and coordinated disclosure goodwill will not.

The next six months will be informative. Track ash_ai's commit history. Did v1.0.0 merely add input filtering, or did it excise the template-execution pattern from the prompt path? The answer distinguishes a patch from a repair. Track whether the Elixir community consolidates security investment around its AI libraries β€” a security team, a bug bounty, a peer-audited hardening process. Track MCP's specification process for mandatory security terms. And watch whether LangChain, LlamaIndex, or their peers produce a similar six-CVE cluster; if they do, the industry should stop calling these events anomalies and start treating them as the steady-state metabolism of a young infrastructure category.

Layer 2 is merely a delay in truth extraction; blockchain settled this for me years ago. The truth in ash_ai was always nested in the template expression. The specific CVEs will be patched, absorbed, and forgotten. The architecture that produced them β€” the unexamined assumption that prompts are benign and that flexibility can precede security β€” is the persistent vulnerability. It lives in every agent framework, in every MCP implementation, in every rapid-growth infrastructure project that believes it can postpone threat modeling until after adoption. Complexity is not a shield; it is a trap. The six CVEs of ash_ai should be read as a single message: if you build an agent framework, you are building a security boundary. The market will verify that boundary sooner than you expect β€” and it will use machines to do it.

The open question is not whether ash_ai recovers. It is whether the agent ecosystem, in any language, will stop treating security as a release-block checklist and start treating it as the fundamental architecture of trust.