Skip to content

russalo/purexml

Repository files navigation

purexml logo

purexml

Safely parse untrusted XML using only the Python standard library.

PyPI tests Python versions runtime deps: zero pure stdlib coverage ≥90% lint: ruff types: mypy differentially fuzzed License: MIT

purexml is a zero-dependency, drop-in replacement for defusedxml. It hardens Python's standard-library XML parsers against the known attack classes — entity-expansion bombs, external-entity resolution (XXE), and external-DTD retrieval — and hands back the same standard xml.etree / minidom / SAX objects your code already expects. Migrating is a literal find-and-replace: s/defusedxml/purexml/.

How purexml defends untrusted XML in two layers: purexml's own Python-layer handlers block entity bombs, XXE and external-DTD resolution version-independently, while the libexpat parser layer mitigates parser-level DoS depending on your runtime; the result is a standard, trusted xml.etree tree.

Why purexml

  • Zero runtime dependencies. Pure standard library — drop a third-party dependency with no loss of protection (same parses succeed, same attacks blocked).
  • A real drop-in. Mirrors defusedxml's API and defaults across the surface the ecosystem actually imports, validated oracle-gated against defusedxml itself.
  • Maintained. defusedxml has been frozen since 2021 — the XML threat landscape hasn't. purexml tracks the moving libexpat / CPython mitigations and can report what your runtime is actually protected against.
  • Opt-in hardening. Structural-DoS caps and a posture report that defusedxml lacks — off by default, so the drop-in promise is never broken.

Deciding whether to switch? See why switch from defusedxml — the full case, and the honest boundaries (when not to).

Install & migrate

pip install purexml

Then the only change to your code is the import path:

# before:  from defusedxml.ElementTree import fromstring
from purexml.ElementTree import fromstring

root = fromstring(untrusted_xml)   # raises on bomb / XXE / external DTD; returns a standard Element

Defaults match defusedxml exactly (forbid_entities=True, forbid_external=True, forbid_dtd=False), so nothing else changes.

Runnable examples for every surface: examples/ · Module-by-module migration guide: docs/MIGRATING.md

What it covers

Drop-in replacements for the defusedxml modules the ecosystem imports:

purexml module replaces surface
purexml.ElementTree defusedxml.ElementTree fromstring, parse, iterparse, XML, XMLParser, tostring, forbid_*
purexml.minidom defusedxml.minidom parse, parseString
purexml.sax / .expatreader defusedxml.sax make_parser, parse, parseString
purexml.xmlrpc defusedxml.xmlrpc monkey_patch, unmonkey_patch
purexml.common defusedxml.common exception catch-site aliases

defusedxml.pulldom is deferred (low measured demand); defusedxml.lxml is excluded by the zero-dependency contract (it wraps the third-party lxml).

Opt-in defense-in-depth

Bounded protections defusedxml never had — default-off, so the strict mirror is unchanged until you ask:

import purexml
from purexml import fromstring, RECOMMENDED_LIMITS

# Bound structural DoS (deep nesting / attribute floods / giant documents):
root = fromstring(untrusted, limits=RECOMMENDED_LIMITS)   # raises LimitExceeded past the caps

# See what THIS runtime is actually protected against (read-only; no parse):
print(purexml.security_report())

There's also a posture CLI — python -m purexml (--json for machine output, or --check --min-expat X.Y.Z as an opt-in CI gate).

Status

v1.0.0 — the public contract is frozen and binding. The defusedxml-mirror surface won't move under you without a 2.0 (see PUBLIC_CONTRACT.md).

  • Runs on CPython 3.10–3.13, zero runtime dependencies.
  • Correctness is oracle-gated against defusedxml every release — C14N + event-stream equivalence over a real corpus, an adversarial attack battery, and differential fuzzing (see docs/EQUIVALENCE.md).
  • The contract freeze was ratified with the anchor consumer (file-observer) — see docs/v1.0.0_RFC_Specification.md. The opt-in defense-in-depth (Limits, security_report()) stays provisional (it tracks the moving libexpat threat landscape).
  • On PyPI: pip install purexml — zero runtime dependencies.
  • License: MIT.

Documentation

License

MIT — purexml is give-it-away, zero-dependency infrastructure: pure open source, maximum reuse.


Built by russalo — more at blog.russalo.com.

About

Safely parse untrusted XML using only the Python standard library — a zero-dependency defusedxml replacement.

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages