Skip to main content

SafeXML 🛡️

The memory-safe, GIL-free, ultra-fast XML parser and drop-in defusedxml successor built in Rust.

CI PyPI version Python versions Coverage: 100% Rust: 100% Safe GIL: Detached License: MIT


⚡ 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. defusedxml constructs a massive Python dictionary, causing quadratic hash collisions and OOM. SafeXML enforces max_attributes (default 1,000).
  • Giant Attribute Value Bombs (CWE-400): A single 50MB attribute value crashes memory. defusedxml does not inspect attribute lengths. SafeXML enforces max_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, defusedxml saturates the GIL and stalls. safexml frees 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 rich tracebacks due to missing args. SafeXML properly populates super().__init__(msg).
  • tiran/defusedxml#104: Complete lack of PEP 561 typing. SafeXML ships with py.typed and comprehensive type annotations.
  • tiran/defusedxml#87: Missing xml.etree.ElementTree.indent(). Full support provided.
  • tiran/defusedxml#80: Missing Element class export. Re-exported directly.
  • tiran/defusedxml#79: Missing register_namespace() helper. Fully supported.
  • tiran/defusedxml#78: fromstring() rejected parser= keyword argument. Standard signature parity supported.
  • tiran/defusedxml#76, #77, #88: defuse_stdlib() monkeypatching broke openpyxl and xmlschema. 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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for safexml 1.0.1
File Size Uploaded
safexml-1.0.1.tar.gz 36.7 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for safexml 1.0.1
File
safexml-1.0.1-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details
safexml-1.0.1-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.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.12 abi3 Linux glibc 2.17+ ARM64 Details
safexml-1.0.1-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details
safexml-1.0.1-cp312-abi3-macosx_10_12_x86_64.whl CPython 3.12 abi3 macOS 10.12+ x86-64 Details

Total release size: 1.1 MB

Release files / safexml-1.0.1.tar.gz

Download URL safexml-1.0.1.tar.gz
Size 36.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e0b2e2d5d7c6ffe8dd31b10f608bc35278ef07e707c0a537a2d683b2ddb7f202
BLAKE2b-256 checksum
How to use checksums
05858f40ae20f090dd68696475a3af16b881170437db79d22ad94710f6e9eca0
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

Release files / safexml-1.0.1-cp312-abi3-win_amd64.whl

Download URL safexml-1.0.1-cp312-abi3-win_amd64.whl
Size 160.7 kB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
ed1719d2a185367f4f595cc04fd2065e5075f5315c9e36cb7ca6c0f24aaba08d
BLAKE2b-256 checksum
How to use checksums
a5691990e9ebb997c480d73710c2550848410c99c887021accf93eae1b8ccb6e
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

Release files / safexml-1.0.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL safexml-1.0.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 243.1 kB
Tags CPython 3.12 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
fdba1cc805e3b9362c79b28ce3e41448de02e50788c63557054f2c43b12f473e
BLAKE2b-256 checksum
How to use checksums
3f601d722c21f345dced0ee7cf767cf687b12ed4e951d6340e3fc55a5d565b75
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

Release files / safexml-1.0.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL safexml-1.0.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 237.5 kB
Tags CPython 3.12 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
01b042d3d565c0c14494017cf7baa6f7297cefdd1d41031db792467dcb79bd63
BLAKE2b-256 checksum
How to use checksums
2c0adc905a954be5c0855400d10cc65fa860b6689a77912899382b4eaa40ec87
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

Release files / safexml-1.0.1-cp312-abi3-macosx_11_0_arm64.whl

Download URL safexml-1.0.1-cp312-abi3-macosx_11_0_arm64.whl
Size 229.9 kB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
b1abccd2b6695d6f3bd18cba9d477b7e9058b5af359ad776543b3031f99a1fe9
BLAKE2b-256 checksum
How to use checksums
70bb6c8e6af2aba83ea2115d74edee4c21de67a7dab43d79f78541cebf6d02da
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

Release files / safexml-1.0.1-cp312-abi3-macosx_10_12_x86_64.whl

Download URL safexml-1.0.1-cp312-abi3-macosx_10_12_x86_64.whl
Size 237.1 kB
Tags CPython 3.12 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
30cf98c8e41f96e51354b8e4eb8ac04ca30ad6a331f61e955176878c60b0f8b3
BLAKE2b-256 checksum
How to use checksums
900000a1429254623e6e60e3fe31a22245f24747dc5c48a1415586f36b167511
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

Release history Release notifications | RSS feed

1.0.2

6 release files

This release

1.0.1 This release

6 release files

1.0.0

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page