Skip to main content

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

Project description

purexml logo

purexml

Safely parse untrusted XML using only the Python standard library.

tests Python 3.10–3.13 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.

Install & migrate

Until purexml is published to PyPI, depend on it via a git or path reference. 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).
  • Not yet published to PyPI, and the purexml name is not yet claimed — distribution is a separate, deliberate step. Depend on it via git/path until then.
  • License: MIT.

Documentation

License

MIT — purexml is give-it-away, zero-dependency infrastructure: pure open source, maximum reuse. Publishing to PyPI and claiming the purexml name remain a separate, deliberate call.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

purexml-1.0.0.tar.gz (69.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

purexml-1.0.0-py3-none-any.whl (36.0 kB view details)

Uploaded Python 3

File details

Details for the file purexml-1.0.0.tar.gz.

File metadata

  • Download URL: purexml-1.0.0.tar.gz
  • Upload date:
  • Size: 69.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for purexml-1.0.0.tar.gz
Algorithm Hash digest
SHA256 bfd03bd7107dafe0868436246497b6cf5ea38180db0a65027f3fd04504afa254
MD5 1d1c56f63342cdf18ed840c910b84135
BLAKE2b-256 1e252fec55ef7bbeb2c92ee362d9b2309ddffd2dfa1887ab9d473aab065e6495

See more details on using hashes here.

Provenance

The following attestation bundles were made for purexml-1.0.0.tar.gz:

Publisher: publish.yml on russalo/purexml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file purexml-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: purexml-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 36.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for purexml-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 19f5cf53b1e9e98ffb6ed4ad7c352933c9a370141778d544ea950670c25e19b2
MD5 42854076d194c0cee13ec0ac3a7b19dc
BLAKE2b-256 7175506c1ade0ac0f4cecbf4d716f62adf78e592788679d88294bfdb62e9829b

See more details on using hashes here.

Provenance

The following attestation bundles were made for purexml-1.0.0-py3-none-any.whl:

Publisher: publish.yml on russalo/purexml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page