GHSA-mrrj-6m2q-jch9
ISO 20022 business rules read a different amount and currency than the parsed XML, allowing over-limit and disallowed-currency payments to pass
Impact
The ISO 20022 business-rules engine in qwed-finance (CrossGuard.verify_iso20022_with_rules, also used by UCPIntegration.verify_iso20022_payment) extracts the settlement amount and currency from the raw XML text with a regular expression, instead of from the parsed XML tree that MessageGuard validates. An XML document can therefore show one amount and currency to the rules engine while every XML parser — and any downstream payment system — reads another.
Two constructs make the rules engine read a decoy:
- the operative element uses a namespace prefix containing a character that is a valid XML NameChar but outside the regex prefix class
[\w.-](for exampleU+00B7), so the regex never sees it; - a decoy amount element is placed inside an XML processing instruction (
<?...?>); comments and CDATA are stripped before matching, but processing instructions are not.
Reproduced on v3.0.0 with business rules max_amount=1000, allowed_currencies=["USD"]:
| Document | Parsers read | Rules engine result |
|---|---|---|
plain IntrBkSttlmAmt Ccy="RUB" 9,999,999.00 |
RUB 9,999,999.00 | rejected (both rules) |
same amount behind a prefixed element plus a PI decoy USD 100.00 |
RUB 9,999,999.00 | passed=True, no violations |
Through UCPIntegration(max_transaction_amount=1000, allowed_currencies=["USD"]).verify_iso20022_payment(...) the same document returns PaymentStatus.APPROVED, can_proceed=True.
Who is affected: applications that rely on verify_iso20022_with_rules or UCPIntegration.verify_iso20022_payment to enforce amount limits or currency allow-lists on ISO 20022 (pacs.008) messages produced by an LLM or another untrusted source. The npm SDK does not expose these code paths.
Root cause
cross_guard.py (_AMOUNT_ELEMENT_RE, _extract_xml_occurrences) performs regex extraction over raw document text. Earlier fixes (comment/CDATA stripping, all-occurrence matching, \w prefixes) narrowed the gap but kept a second, regex-based view of the document that can disagree with the XML tree.
Patches
Fixed in qwed-finance 3.0.1 (release). Upgrade with pip install -U qwed-finance==3.0.1.
The business-rules engine now takes IntrBkSttlmAmt amounts and Ccy currencies from the defusedxml-parsed XML tree (matched by local name in any namespace), not from a regular expression over raw text. In addition:
- an amount element containing child nodes (elements, comments, processing instructions) fails closed as unparseable;
- amounts must be a plain
xs:decimal— exponents and grouping separators are rejected; - character references are decoded exactly once, by the parser.
Workarounds
- Extract the amount and currency yourself from the parsed XML tree (by namespace-qualified local name) and enforce limits on those values; do not rely on
verify_iso20022_with_rulesfor limit enforcement. - Reject documents that contain processing instructions or non-default namespace prefixes on amount elements before verification.
References
Reproduced on qwed-finance 3.0.0 (bb0cdf3).