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 a research-grade, multi-tiered fuzz testing architecture to guarantee memory safety, differential parser immunity (preventing SAML/XXE auth bypasses), and strict resource limit enforcement:
- Structure-Aware Grammar Fuzzing: Typed AST generation (
fuzz/fuzz_targets/fuzz_structured_xml.rs) based on the Zest / Nautilus paradigm, synthesizing complex DTDs, scoped namespace hierarchies,<!ATTLIST>defaults, and entity unescaping with controlled fault injection. - Strict Post-Condition Invariant Oracles: Automated tree validation (
fuzz/fuzz_targets/fuzz_parser.rs) asserting that every parsed DOM strictly adheres tomax_depth,max_elements,max_attributes, andmax_name_sizewith zero limit escapes. - Metamorphic Invariance Oracles: Differential property checks (
fuzz/fuzz_targets/fuzz_metamorphic.rs) verifying canonical round-trip idempotence (parse(serialize(x)) == x), UTF-8/UTF-16 encoding equivalence, and monotonic security tightening. - Compositional Scanner Micro-Harness: Dedicated slice scanner fuzzer (
fuzz/fuzz_targets/fuzz_doctype_scanner.rs) testing internal DTD subsets, quoted string extractions, and XML declaration parsers against malformed byte/char boundaries. - Hypothesis Differential & Stream Fuzzing: Continuous property-based testing (
tests/test_differential_fuzz.py) verifying 1:1 DOM equivalence, rejection parity, chunked stream invariance, and amplification limits against CPython stdlibElementTreeanddefusedxml. - Nightly Sanitizer Pipeline & OSS-Fuzz: Automated GitHub Actions matrix (
.github/workflows/fuzz.yml) running all targets under AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), and LLVM Value Profiling (-use_value_profile=1), alongside continuous Google OSS-Fuzz integration.
For architecture details and test methodologies, see the Fuzzing & Verification Architecture Guide.
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.
