M8: the redact README lists every fail-closed trigger and the metadata it doesn't scrub (#2448)

Review on #2539 (nitpick): the README named only the collection limit
and unreadable XMP as fail-closed cases. It now lists all of them (the
run and run-length limits, a removed glyph with unknown text, a form
XObject removed unread past the depth limit or unreadable, the term
limits, the search work limit, oversized or undecodable XMP) and the
metadata the check leaves to Sanitize: XMP on objects other than the
catalog and pages, bookmark titles, and attachments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
This commit is contained in:
mtlcl410andClaude Opus 5.5 committed 2026-10-31 08:05:51 +08:00
1 parent aed4599ed1
commit 409477067a
1 file changed
+18 -8
+18 -8
View File
@@ -34,14 +34,24 @@ clear_marks(&mut doc, None)?; // remove marks without app
part of it as whole words (three letters or digits at least), and the catalog's and pages'
XMP streams whose text (element content, attribute values other than `xmlns` declarations,
comments and processing instructions, entities decoded) contains it or has a value wholly
part of it, removed whole, together with any PDF/A identification in that packet. XMP that
isn't valid UTF-8 or UTF-16 goes too, and when the removed text was too much to collect
every `/Info` string and every XMP stream goes (fail-closed). The removed glyphs' text is
collected in runs by position (character and word spacing included, so letter-spaced text
stays one word), and each run (two or more letters or digits) and each of its words of three
or more are matched without regard to case or spacing. A value that shares only words of one
or two letters with the removed text, and isn't wholly part of it, stays: Sanitize's
metadata category removes the rest. `Report::metadata` counts what went.
part of it, removed whole, together with any PDF/A identification in that packet. The removed
glyphs' text is collected in runs by position (character and word spacing included, so
letter-spaced text stays one word), and each run (two or more letters or digits) and each of
its words of three or more are matched without regard to case or spacing. A value that shares
only words of one or two letters with the removed text, and isn't wholly part of it, stays:
Sanitize's metadata category removes the rest. `Report::metadata` counts what went.
- **Fail-closed.** When the check can't know what the removed text was, every `/Info`
string and every catalog and page XMP stream goes: more than 100,000 runs or a run over
64 KiB removed (the collection limit); a removed glyph whose text isn't known (no
`/ToUnicode` or encoding gives it); a form XObject removed without being read, because it
is nested past the depth limit (`MAX_DEPTH`, 50) or its content can't be read; or more
than 20,000 terms, or a run longer than 4,096 characters, to search for. Once the
search's work limit (2^28 steps) runs out, every entry and packet not yet checked goes
too. An XMP packet that isn't valid UTF-8 or UTF-16, can't be decoded, or decodes to
more than 16 MiB goes without being read.
- **Not scrubbed** (Sanitize's categories cover them): XMP streams on anything but the
catalog and the pages (images, fonts, form XObjects, annotations and other objects),
bookmark titles, and attachments (embedded files, their names and descriptions).
A verification pass re-reads each redacted page and fails the operation if any glyph or inline
image is still under an area. It checks every content stream the page had, whatever its