CVE-2026-48590 Overview
CVE-2026-48590 is an XML Injection vulnerability [CWE-91] in the joshnuss/xml_builder Elixir library. The flaw resides in the XmlBuilder module, specifically in lib/xml_builder.ex and the routines XmlBuilder.generate/1, XmlBuilder.generate/2, XmlBuilder.element/1, XmlBuilder.element/2, and XmlBuilder.element/3. Element names, attribute names, and doctype identifiers are interpolated verbatim into serialized XML without validation or escaping of structural characters (<, >, ", ', &). An attacker who influences a name argument, for example one derived from a JSON key or HTTP form field name, can inject arbitrary XML markup into the resulting document. The issue affects xml_builder from version 0.0.1 before 2.4.1.
Critical Impact
Attackers can inject arbitrary XML elements, comments, and event-handler attributes into generated documents, enabling content spoofing and downstream XML injection when generated output is consumed by other parsers or rendered to users.
Affected Products
- joshnuss/xml_builder Elixir library, versions 0.0.1 through 2.4.0
- Applications embedding xml_builder that pass untrusted input into element or attribute name arguments
- Downstream Elixir/Erlang services that consume XmlBuilder.generate/* output without secondary sanitization
Discovery Timeline
- 2026-08-21 - CVE-2026-48590 published to NVD
- 2026-08-24 - Last updated in NVD database
Technical Details for CVE-2026-48590
Vulnerability Analysis
The xml_builder library serializes Elixir tuples of the shape {name, attrs, content} into XML text. Before the fix, the format/3 clauses converted the name term directly with to_string/1 and concatenated it into the output stream. No structural character escaping was applied to element names, attribute names, or doctype identifiers.
When an application passes user-controlled data as an element or attribute name, for example XmlBuilder.element(user_supplied_key, %{}, value), the attacker controls raw bytes that the serializer emits between the < and > delimiters. This allows the attacker to break out of the intended tag, inject additional tags, insert comments, or add attributes such as event handlers.
The consequence is content spoofing in the produced XML and potential cross-context injection when the output is later parsed as HTML, SVG, RSS, or a signed SAML/SOAP payload. Because integrity of generated markup is broken while the producing process itself is not compromised, the impact is scoped to output integrity rather than confidentiality or availability of the host.
Root Cause
The root cause is missing output encoding of structural XML characters in name positions. The pre-patch code paths in lib/xml_builder.ex used to_string(name) inside the <...>, </...>, and attribute-key contexts. to_string/1 performs type conversion but no XML escaping, so characters such as <, >, ", ', and & were emitted literally. This maps to [CWE-91]: XML Injection (improper neutralization of data within XML).
Attack Vector
Exploitation requires that an attacker control input that reaches a name argument of XmlBuilder.element/* or the doctype identifier used by XmlBuilder.generate/*. Common paths include:
- Serializing a Map to XML where map keys are derived from a JSON body or HTTP form field names.
- Rendering XML feeds where element names are copied from a database column populated by user submissions.
- Building doctype declarations from configuration or request parameters.
A payload such as foo><script>alert(1)</script><foo supplied as a name causes the serializer to emit unexpected tags. Injected event-handler attributes can alter parser interpretation in downstream consumers.
# Pre-patch behavior in lib/xml_builder.ex (excerpt from the fix commit)
defp format({name, attrs, content}, level, options)
when is_blank_attrs(attrs) and is_blank_list(content),
- do: [indent(level, options), ~c"<", to_string(name), ~c"/>"]
+ do: [indent(level, options), ~c"<", sanitize!(name), ~c"/>"]
defp format({name, attrs, content}, level, options) when is_blank_list(content),
- do: [indent(level, options), ~c"<", to_string(name), ~c" ", format_attributes(attrs), ~c"/>"]
+ do: [indent(level, options), ~c"<", sanitize!(name), ~c" ", format_attributes(attrs), ~c"/>"]
defp format({name, attrs, content}, level, options)
when is_blank_attrs(attrs) and not is_list(content),
do: [
indent(level, options),
~c"<",
- to_string(name),
+ sanitize!(name),
~c">",
format_content(content, level + 1, options),
~c"</",
- to_string(name),
+ sanitize!(name),
~c">"
]
Source: GitHub Commit d5c0aec
The patch introduces a sanitize!/1 helper and a dedicated exception module. A SanitizationError is raised when a name contains disallowed structural characters:
# lib/xml_builder/sanitization_error.ex
defmodule XmlBuilder.SanitizationError do
defexception [:message]
end
Source: GitHub Commit d5c0aec
Detection Methods for CVE-2026-48590
Indicators of Compromise
- Generated XML documents that contain unexpected tags, comments, or attributes not present in the calling code's tag schema.
- Runtime raises of XmlBuilder.SanitizationError after upgrading, indicating call sites that pass attacker-influenced names.
- Log entries showing XmlBuilder.element/* invocations where the name argument originates from HTTP form fields, JSON keys, or database-backed user content.
Detection Strategies
- Perform a dependency inventory across Elixir and Erlang projects and flag any mix.lock entry pinning xml_builder below 2.4.1.
- Use static analysis or grep for calls to XmlBuilder.element, XmlBuilder.generate, and XmlBuilder.doctype where name arguments are not compile-time literals or atoms from a fixed allow list.
- Add fuzz test cases that pass XML metacharacters as element and attribute names to affected code paths and verify the serialized output is well-formed.
Monitoring Recommendations
- Monitor application error streams for XmlBuilder.SanitizationError exceptions post-upgrade and treat repeated occurrences from the same source as probable abuse.
- Log and alert on outbound XML documents that fail schema validation at consumers, which can indicate injected structural characters.
- Track versions of xml_builder deployed across build pipelines using Software Bill of Materials (SBOM) tooling to prevent regression to a vulnerable release.
How to Mitigate CVE-2026-48590
Immediate Actions Required
- Upgrade xml_builder to version 2.4.1 or later in every affected project and rebuild release artifacts.
- Audit call sites of XmlBuilder.element/*, XmlBuilder.generate/*, and doctype builders for name arguments sourced from untrusted input.
- Refactor code so element and attribute names come from a fixed allow list of atoms rather than dynamic strings.
Patch Information
The fix is delivered in xml_builder2.4.1 via commit d5c0aec. It introduces a sanitize!/1 function invoked at every name emission site and adds XmlBuilder.SanitizationError to signal invalid inputs at runtime. See the GitHub Security Advisory GHSA-r82p-3q2p-728w and the Erlang Ecosystem CNA advisory for authoritative details. The OSV record EEF-CVE-2026-48590 provides machine-readable version ranges for automated scanning.
Workarounds
- Where an immediate upgrade is not feasible, wrap all calls to XmlBuilder.element/* with a caller-side validator that rejects names containing <, >, ", ', &, or whitespace.
- Restrict element and attribute names to atoms defined in the calling module, never derived from request-time data.
- Validate downstream XML with a strict schema (XSD or Relax NG) at the consumer boundary to detect and reject injected markup.
# Update the xml_builder dependency in mix.exs
# {:xml_builder, "~> 2.4.1"}
# Refresh the lockfile and fetch the fixed release
mix deps.update xml_builder
mix deps.get
# Verify the resolved version
mix deps | grep xml_builder
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

