fix(audit): find rectangle covers closed with h or painted with b/b* (#1817) (#1865)

The redaction audit turned an re rectangle into an unknown path on a
redundant h, and its fill branch left out the close-fill-stroke operators b
and b*, so those opaque covers over extractable text produced no finding.
Keep a closed rectangle closed under h and treat b/b* like the other fills.


Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mtlcl410
2026-10-15 14:27:23 +08:00
committed by GitHub
parent 81982b092b
commit 424459f386
2 changed files with 15 additions and 3 deletions
+4 -1
View File
@@ -219,10 +219,13 @@ impl<'a> Walker<'a> {
b"h" => {
path = match path {
Path::Lines(pts, _) => Path::Lines(pts, true),
// `re` already closed its subpath, so a further `h` changes nothing.
Path::Rect(r) => Path::Rect(r),
_ => Path::Other,
};
}
b"f" | b"F" | b"f*" | b"B" | b"B*" => {
// `b`/`b*` close the path before filling it, which the fill does anyway.
b"f" | b"F" | b"f*" | b"B" | b"B*" | b"b" | b"b*" => {
if let Some(r) = std::mem::replace(&mut path, Path::Empty).rect()
&& gs.alpha >= 1.0
{
+11 -2
View File
@@ -232,8 +232,14 @@ fn self_drawing_form_fails_the_audit() {
fn rectangle_drawn_as_a_path_covers_text_like_re() {
// The same cover as `re`, built from `m`/`l` (closed with `h`, a repeated start point, or
// only by the fill itself), or in a scaled user space: all are faux redactions (#1510).
let covers: [&[u8]; 5] = [
// So are `re` with a redundant `h`, and either path painted with `b`/`b*` (#1817).
let covers: [&[u8]; 10] = [
b"0 g\n100 688 220 32 re f\n",
b"0 g\n100 688 220 32 re h f\n",
b"0 g\n100 688 220 32 re b\n",
b"0 g\n100 688 220 32 re h b*\n",
b"0 g\n100 688 m 320 688 l 320 720 l 100 720 l b\n",
b"0 g\n100 688 m 320 688 l 320 720 l 100 720 l h b*\n",
b"0 g\n100 688 m 320 688 l 320 720 l 100 720 l h f\n",
b"0 g\n100 688 m 320 688 l 320 720 l 100 720 l 100 688 l h f\n",
b"0 g\n100 688 m 100 720 l 320 720 l 320 688 l f\n",
@@ -253,9 +259,12 @@ fn rectangle_drawn_as_a_path_covers_text_like_re() {
#[test]
fn non_rectangular_path_is_not_a_cover() {
// Each path's bounding box covers the text, but the shape itself does not hide it all.
let covers: [&[u8]; 5] = [
let covers: [&[u8]; 7] = [
// A triangle.
b"0 g\n100 688 m 320 688 l 320 720 l h f\n",
// A triangle painted with `b`, and a trapezoid with `b*`.
b"0 g\n100 688 m 320 688 l 320 720 l b\n",
b"0 g\n100 688 m 320 688 l 320 720 l 140 720 l b*\n",
// A triangle, then a line from its start point after `h`: not a fourth corner.
b"0 g\n100 688 m 320 688 l 320 720 l h 100 720 l f\n",
// A trapezoid: four corners, one slanted side.