SafeXML 🛡️
The memory-safe, GIL-free, ultra-fast XML parser and drop-in defusedxml successor built in Rust.
⚡ The Case for SafeXML: Why DefusedXML is Obsolete
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:
1. Immune to C Memory Corruption (CVE Hell)
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.
2. True GIL Release for Modern Multi-Threaded Services
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.
3. Defeats Modern XML Attacks That defusedxml Misses
defusedxml only guards against traditional DTD and entity expansions. It completely misses modern XML attack vectors:
- Attribute Flood / Attribute Hash DoS (CWE-400): Attackers submit elements with 100,000 attributes.
defusedxmlconstructs a massive Python dictionary, causing quadratic hash collisions and OOM. 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.
🥊 Comprehensive Attack Surface & Feature Matrix
| Security / Feature Matrix | safexml (Rust) |
defusedxml (Python + C) |
lxml (C libxml2) |
xml.etree (Python stdlib) |
|---|---|---|---|---|
| Billion Laughs / Exponential Entity Bomb | 🛡️ BLOCKED | 🛡️ BLOCKED | ⚠️ Config-dependent | ❌ VULNERABLE |
| Quadratic Blowup Entity Attack | 🛡️ BLOCKED | 🛡️ BLOCKED | ⚠️ Config-dependent | ❌ VULNERABLE |
| External Entity (XXE) / SSRF | 🛡️ BLOCKED | 🛡️ BLOCKED | ⚠️ Config-dependent | ❌ VULNERABLE |
| External DTD Retrieval | 🛡️ BLOCKED | 🛡️ BLOCKED | ⚠️ Config-dependent | ❌ VULNERABLE |
| Attribute Flood / Hash 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) | ⚠️ Partial | ❌ No (Blocks GIL) |
| Test Coverage Enforced | 🎯 100.00% Coverage | Unknown / Partial | Unknown / Partial | N/A |
PEP 561 Type Annotations (py.typed) |
✅ YES | ❌ No (#104) | ⚠️ Separate stub | ⚠️ Standard library |
| Rich / IPython Traceback Compatible | ✅ YES | ❌ Crashes (#105) | ✅ Yes | ✅ Yes |
ElementTree indent() Built-in |
✅ YES | ❌ Missing (#87) | ✅ Yes | ✅ Yes |
| Modern Python Target | 🐍 Python >= 3.12 | 🏚️ Python 2 / Legacy | 🐍 All | 🐍 Standard library |
🚀 Benchmarks: SafeXML vs DefusedXML
Benchmarks run on Linux x86_64, Python 3.12.14, comparing safexml against defusedxml and stdlib xml.etree:
1. Single-Threaded Throughput & Latency
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]
2. Multi-Threaded Concurrency (GIL-Release Benchmark)
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.
🛠️ Upstream defusedxml Issues Resolved
safexml directly resolves the top open issues and long-standing bugs reported against tiran/defusedxml:
- tiran/defusedxml#105: Exception formatting crashed when printed by
richtracebacks due to missingargs. SafeXML properly populatessuper().__init__(msg). - tiran/defusedxml#104: Complete lack of PEP 561 typing. SafeXML ships with
py.typedand comprehensive type annotations. - tiran/defusedxml#87: Missing
xml.etree.ElementTree.indent(). Full support provided. - tiran/defusedxml#80: Missing
Elementclass export. Re-exported directly. - tiran/defusedxml#79: Missing
register_namespace()helper. Fully supported. - tiran/defusedxml#78:
fromstring()rejectedparser=keyword argument. Standard signature parity supported. - tiran/defusedxml#76, #77, #88:
defuse_stdlib()monkeypatching brokeopenpyxlandxmlschema. Safe, non-destructive stdlib defuser implemented.
📦 Installation
Prebuilt abi3 binary wheels are available on PyPI for Linux, macOS (Apple Silicon & Intel), and Windows:
pip install safexml
Requirements: Python >= 3.12.
💡 Quickstart: 10-Second Migration
1. Direct Drop-in Replacement for defusedxml.ElementTree
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"
# Pretty-printing (resolves tiran/defusedxml#87)
ET.indent(root)
print(ET.tostring(root, encoding="unicode"))
2. Global Non-Destructive Stdlib Defusing
If you have third-party dependencies (like openpyxl, boto3, or saml2) using Python's standard xml.etree.ElementTree, you can secure your entire application with one call:
import safexml
# Safely patches xml.etree.ElementTree without breaking third-party packages
safexml.defuse_stdlib()
3. Catching Security Violations
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}")
🎯 100% Enforced Code Coverage
SafeXML maintains a strict 100.00% statement and branch coverage requirement across every module. This is enforced directly in CI (pytest --cov-fail-under=100) and git pre-push hooks:
Name Stmts Miss Branch BrPart Cover
-----------------------------------------------------------------
python/safexml/ElementTree.py 43 0 10 0 100%
python/safexml/__init__.py 13 0 0 0 100%
python/safexml/common.py 41 0 0 0 100%
-----------------------------------------------------------------
TOTAL 97 0 10 0 100%
🏗️ Architectural Distinction: safexml vs polyxml
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 |
📄 License
MIT License. Engineered with pride under the PolyXML organization by Bailey Nguyen.
Metadata
Release files for safexml 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| safexml-1.0.2.tar.gz | 44.5 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| safexml-1.0.2-cp312-abi3-win_amd64.whl | CPython 3.12 | abi3 | Windows x86-64 | Details |
| safexml-1.0.2-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| safexml-1.0.2-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| safexml-1.0.2-cp312-abi3-macosx_11_0_arm64.whl | CPython 3.12 | abi3 | macOS 11.0+ ARM64 | Details |
| safexml-1.0.2-cp312-abi3-macosx_10_12_x86_64.whl | CPython 3.12 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 1.2 MB
Release files / safexml-1.0.2.tar.gz
| Download URL | safexml-1.0.2.tar.gz |
|---|---|
| Size | 44.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
087670d519d4a52f7842f23876e4edd6e68229ff4f94d4983d6489e52b5382c8
|
|
BLAKE2b-256 checksum How to use checksums |
cb2711712495f578c678cfcf5bd6aa672c1d5fdb35c00077e089bfff5aec08ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / safexml-1.0.2-cp312-abi3-win_amd64.whl
| Download URL | safexml-1.0.2-cp312-abi3-win_amd64.whl |
|---|---|
| Size | 160.3 kB |
| Tags | CPython 3.12 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
4141796c7aae68c295f0f6ae5089fbeb67526513fa1e888fe273b691f6a4883a
|
|
BLAKE2b-256 checksum How to use checksums |
9baf5ba503ef25d123ff37aabf2f25c82203534f9c9a0652aa12787a862bcb64
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / safexml-1.0.2-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | safexml-1.0.2-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 242.8 kB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
997f621b860c6f008816a1cafeb5b08cdcb19febbcf6ed577631e98ed8cbf279
|
|
BLAKE2b-256 checksum How to use checksums |
8567aab2445044a8f320d9d4618a3bce5dd435fc8ce15716754fba9a603760fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / safexml-1.0.2-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | safexml-1.0.2-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 236.9 kB |
| Tags | CPython 3.12 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
c7faa449fa29878497c0210ac4e8506074f665a5325d87149c017dfb5155bf6e
|
|
BLAKE2b-256 checksum How to use checksums |
18332fa60fedcec8ecc5d09c3591e9b7a773c5d9d54660741cf2556107f355f3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / safexml-1.0.2-cp312-abi3-macosx_11_0_arm64.whl
| Download URL | safexml-1.0.2-cp312-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 229.8 kB |
| Tags | CPython 3.12 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
3e94bd43e740b27b8aa0f4cdb60985dfb301e605c6ecd2668302d884538bea16
|
|
BLAKE2b-256 checksum How to use checksums |
d7ac7d57c5d53860bce01a47206933c5a4d7492fa65aa2ea4f064951065456be
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / safexml-1.0.2-cp312-abi3-macosx_10_12_x86_64.whl
| Download URL | safexml-1.0.2-cp312-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 236.5 kB |
| Tags | CPython 3.12 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
da315f8b0ce1808228061aa117788730cf5c63e75a636f4e1a70159ed55aad44
|
|
BLAKE2b-256 checksum How to use checksums |
7abef9c7d644e798ecb4c561c43992c2bf73d7fbf9ce0e9158e4cdf410219f4b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency log