diff --git a/tests/test_posting_guard.py b/tests/test_posting_guard.py index bed1935f..d182993b 100644 --- a/tests/test_posting_guard.py +++ b/tests/test_posting_guard.py @@ -605,6 +605,39 @@ class Calibration(GuardFixture): # href targets are attributes, not text: hidden nav links are not "hidden URLs". self.assertEqual(self.findings_of(out)["hidden_urls"], []) + def test_decoration_and_custom_properties_do_not_hide_visible_posting_text(self): + # These declarations are serialized by the browser after assigning the + # DOM style properties; each retains black, visible text. + styles = [f"{name}: transparent" for name in ("background-color", "border-color", "text-decoration-color")] + styles += ["stroke-opacity: 0", "--opacity: 0", "--display: none"] + for style in styles: + with self.subTest(style=style): + page = self.write("posting.html", ( + f'
' + 'Software Engineer: Python and SQL experience
' + '
Hidden content
' + )) + code, out, err = self.run_main("--in", str(page), "--html", "--json") + self.assertEqual(code, 0, err) + self.assertIn("Software Engineer: Python and SQL experience", self.text_of(out)) + self.assertNotIn("Hidden content", self.text_of(out)) + self.assertEqual(self.findings_of(out)["hidden_blocks"], ["Hidden content"]) + + def test_offscreen_margins_still_hide_once_names_are_anchored(self): + # Anchoring the property names must not lose the off-screen margin + # trick, which the unanchored pattern only caught as a suffix of "left". + for style in ("margin-left: -9999px", "position: absolute; margin-top: -10000px"): + with self.subTest(style=style): + page = self.write("posting.html", ( + '
Software Engineer: Python and SQL experience
' + f'
Hidden content
' + )) + code, out, err = self.run_main("--in", str(page), "--html", "--json") + self.assertEqual(code, 0, err) + self.assertIn("Software Engineer: Python and SQL experience", self.text_of(out)) + self.assertNotIn("Hidden content", self.text_of(out)) + self.assertEqual(self.findings_of(out)["hidden_blocks"], ["Hidden content"]) + def test_leading_bom_is_encoding_noise(self): code, out, err = self.run_main("--text", "--json", stdin_text="Plain posting.") self.assertTrue(out.startswith("posting_guard: clean"), out.splitlines()[0]) diff --git a/tools/posting_guard.py b/tools/posting_guard.py index 1c80eb63..1c4fb06e 100644 --- a/tools/posting_guard.py +++ b/tools/posting_guard.py @@ -97,14 +97,14 @@ VARIATION = re.compile("[︀-️\U000E0100-\U000E01EF]") VARIATION_RUN = re.compile("[︀-️\U000E0100-\U000E01EF]{2,}") HIDDEN_STYLE = re.compile( - r"display\s*:\s*none" + r"(?:^|;)\s*(?:display\s*:\s*none" r"|visibility\s*:\s*hidden" r"|opacity\s*:\s*0(?![.\d]*[1-9])" r"|font-size\s*:\s*0(?:\.0+)?(?:px|pt|em|rem|%)?(?![\d.])" r"|color\s*:\s*transparent" r"|clip\s*:\s*rect\(\s*0" r"|clip-path\s*:\s*inset\(\s*100%" - r"|(?:left|top|text-indent)\s*:\s*-\d{4,}px", + r"|(?:(?:margin-)?(?:left|top)|text-indent)\s*:\s*-\d{4,}px)", re.I, ) PRESTRIP = re.compile(r"]*>|<\?[^>]*\?>", re.I)