cadrumo.adapters.inbound.einvoice.xml module

Hardened XML read boundary for externally-authored e-invoice documents.

Every byte parsed here arrives from outside the host: an operator’s attachment, a supplier’s emailed invoice, a payload embedded in a PDF. The document is therefore treated as hostile input, and the parse is bounded on all four axes an XML reader can be attacked on.

  • Entity resolution and external DTD loading are disabled, which is what defusedxml is for and why this module exists rather than each parser reaching for ElementTree directly. An XXE probe cannot read a local file or open a network connection.

  • Payload size is bounded before parsing begins, so a multi-gigabyte document is refused rather than buffered.

  • Nesting depth is bounded after parsing, which is the residual billion-laughs-adjacent risk once entity expansion is already off: a deeply nested document can still exhaust the recursion budget of a naive walker.

defusedxml is already a declared [project.dependencies] entry, so exact structured reading costs no new dependency, no model, no GPU and no extra – which is what lets it live in the deterministic core rather than behind the inference boundary.

local_tag_name(tag)[source]

Return an XML tag without its namespace prefix.

ElementTree reports a namespaced tag as {uri}local. Three parsers each carried this strip, which meant three places decided what a tag’s local name is.

Return type:

str

Parameters:

tag (str)

MAX_XML_PAYLOAD_BYTES

Largest e-invoice payload accepted. Two orders of magnitude above any real EN16931 or Facturae document, so it refuses abuse without refusing a genuine invoice carrying embedded attachments.

MAX_XML_DEPTH

Deepest element nesting accepted. EN16931 and Facturae both bottom out well under 20; 100 leaves generous headroom while still bounding a walker.

exception EInvoiceXmlParseError(message=None, *, context=None, translated_message=None)[source]

Bases: CadrumoError

Raised when an e-invoice XML payload is refused at the read boundary.

Deliberately a refusal rather than a partial result. A structured reader that returned half a record on malformed input would be worse than a model: it would look exact while being wrong.

CadrumoError binds the refusal to the error registry, so an operator receives a stable code and translated message. XML parsing is not a Pydantic callback, so the adapter raises this registered type directly rather than leaking a builtin validation base.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • translated_message (str | None)

Return type:

None

code
parse_hardened_xml(payload)[source]

Parse payload with entity resolution and external DTDs disabled.

Parameters:

payload (bytes) – Raw document bytes from outside the host.

Return type:

Element

Returns:

The parsed root Element.

Raises:

EInvoiceXmlParseError – If the payload is empty, exceeds MAX_XML_PAYLOAD_BYTES, is not well-formed XML, resolves a forbidden entity, or nests deeper than MAX_XML_DEPTH.