fix(posting-guard): match hidden CSS declaration names (#717)

* fix(posting-guard): distinguish transparent text from decorations

* fix(posting-guard): anchor all hidden-style property names

* test(posting-guard): clarify browser style calibration cases

* docs(changelog): reference posting style fix PR 717

* fix(posting-guard): keep off-screen margins hidden once property names are anchored

The unanchored pattern caught margin-left/margin-top: -9999px only as a suffix of
left/top. Anchoring the names would have lost that, so list the margin forms
explicitly and pin them with a test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Mads Lorentzen <madslorentzen17@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-11-10 19:46:02 +01:00
1 parent a2b688ca84
commit 5612dd619b
2 files changed
+35 -2

No files matched your search

+33
View File
@@ -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'<article style="color: black; {style};">'
'Software Engineer: Python and SQL experience</article>'
'<article style="color: transparent;">Hidden content</article>'
))
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", (
'<article>Software Engineer: Python and SQL experience</article>'
f'<article style="{style};">Hidden content</article>'
))
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])
+2 -2
View File
@@ -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"<!DOCTYPE[^>]*>|<\?[^>]*\?>", re.I)