Files
parsedmarc/tests
Sean WhalenandClaude Opus 5.5 6b7c203126 Merge commit from fork
* Fix three security advisories (DOCTYPE amplification, poison mailbox message, byte-length filenames)

GHSA-m47j-wp78-fqjg: reject any DOCTYPE in aggregate report XML. An
internal subset's <!ATTLIST> default attributes are applied by expat to
every matching element, so 64 KB of XML (K=1000, N=10000) took 4.0 s and
1.4 GB here. xmltodict now gets an expat shim whose parsers raise on
StartDoctypeDecl; the lxml recovery path checks docinfo and serializes
only the root element.

GHSA-qxwv-2vwj-v828: parse_report_email() now raises only ParserError
subclasses (HeaderWriteError / RecursionError from re-serializing a part
used to escape). Content errors raised while fetching a message
(email.errors.MessageError, RecursionError) are returned as ParserError
and the message is disposed of as invalid; transport errors still
propagate. A failed invalid-message move/delete is logged instead of
aborting the batch, because mailsuite's Maildir move_message()
re-serializes the message too. mbox reads use get_bytes() instead of the
re-serializing get_string().

GHSA-j53g-34vj-g25q: get_filename_safe_string() bounds names to 100
UTF-8 bytes as well as 100 characters; filesystems limit a name to 255
bytes, so 84 CJK characters + ".eml" failed with ENAMETOOLONG.

Reported by helixnebulatech.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: recursion margins, mbox wording, DOCTYPE check order

- Nest the RecursionError poison 400 levels deep (POISON_NESTING_LEVELS)
  instead of 250: the smallest failing depth is ~247 from a shallow
  stack, 245 in a fork worker and ~222 at +100 frames, so 250 left a
  margin of a few levels. The shallow case uses 50 levels and now asserts
  InvalidDMARCReport ("is not a valid report"); the deep case asserts
  "recursion" in the message.
- Poison-loop tests skip a benign poison per message (continue) and
  assert at least one poison was exercised, so they cannot pass vacuously.
- lxml recovery: check for a DOCTYPE before serializing the root, and
  comment why the str() around tostring(encoding="unicode") stays
  (pyright, no lxml stubs).
- CHANGELOG: describe what the walk actually serializes, drop the
  depth-dependent 245 figure, and say plainly that an mbox run still
  stops at such a message, now with a ParserError naming it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Make the Maildir poison-message test interpreter-independent

Whether MaildirConnection.move_message() can store the control-character
message depends on the interpreter: upstream CPython (3.12.9) runs the
CVE-2024-6923 generated-header check only in the text Generator used by
as_string(), so mailbox.Maildir.add()'s BytesGenerator stores it, while
Ubuntu's patched 3.12.3 also checks in BytesGenerator._write_headers and
the move fails. The deeply nested message fails the move everywhere.

The test now decides per message, by trying the same Maildir round trip,
whether the message lands in Archive/Invalid with no mailbox error logged
or stays in INBOX with the move error logged; both branches assert the
batch completed with the aggregate archived. CHANGELOG and the
_dispose_invalid_message docstring now say which messages stay put.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:05:36 -04:00
..
2026-10-26 22:05:36 -04:00
2026-10-26 22:05:36 -04:00