Safely parse untrusted XML using only the Python standard library — a zero-dependency defusedxml replacement.
Project description
purexml
Safely parse untrusted XML using only the Python standard library.
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/.
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 againstdefusedxmlitself. - Maintained.
defusedxmlhas 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
defusedxmllacks — 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
defusedxmlevery release — C14N + event-stream equivalence over a real corpus, an adversarial attack battery, and differential fuzzing (seedocs/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
purexmlname is not yet claimed — distribution is a separate, deliberate step. Depend on it via git/path until then. - License: MIT.
Documentation
examples/— runnable, copy-paste examples for every surfacedocs/MIGRATING.md— thes/defusedxml/purexml/migration guideCOMPATIBILITY.md— drop-in compatibility contract + exception edge casesLIMITATIONS.md— what purexml deliberately does not doSECURITY.md— security policy and how to report issuesPUBLIC_CONTRACT.md— consumer stability commitments (binds at 1.0)CHANGELOG.md— release notes ·HISTORY.md— full per-release recordSTACK.md— language, runtime, dependencies ·CONVENTIONS.md— project conventions
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bfd03bd7107dafe0868436246497b6cf5ea38180db0a65027f3fd04504afa254
|
|
| MD5 |
1d1c56f63342cdf18ed840c910b84135
|
|
| BLAKE2b-256 |
1e252fec55ef7bbeb2c92ee362d9b2309ddffd2dfa1887ab9d473aab065e6495
|
Provenance
The following attestation bundles were made for purexml-1.0.0.tar.gz:
Publisher:
publish.yml on russalo/purexml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
purexml-1.0.0.tar.gz -
Subject digest:
bfd03bd7107dafe0868436246497b6cf5ea38180db0a65027f3fd04504afa254 - Sigstore transparency entry: 2110149511
- Sigstore integration time:
-
Permalink:
russalo/purexml@7749733500ebd574623670546464a736b854213b -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/russalo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7749733500ebd574623670546464a736b854213b -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19f5cf53b1e9e98ffb6ed4ad7c352933c9a370141778d544ea950670c25e19b2
|
|
| MD5 |
42854076d194c0cee13ec0ac3a7b19dc
|
|
| BLAKE2b-256 |
7175506c1ade0ac0f4cecbf4d716f62adf78e592788679d88294bfdb62e9829b
|
Provenance
The following attestation bundles were made for purexml-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on russalo/purexml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
purexml-1.0.0-py3-none-any.whl -
Subject digest:
19f5cf53b1e9e98ffb6ed4ad7c352933c9a370141778d544ea950670c25e19b2 - Sigstore transparency entry: 2110149569
- Sigstore integration time:
-
Permalink:
russalo/purexml@7749733500ebd574623670546464a736b854213b -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/russalo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7749733500ebd574623670546464a736b854213b -
Trigger Event:
release
-
Statement type: