Fix: resolve modern CSS color syntaxes in hidden-text-strip#205
Merged
Conversation
Tailwind v4, Open Props, and any stylesheet authored against CSS Color
Level 4 emit `oklch()`, `lab()`, `color()`, and `color-mix()` in
computed style verbatim. hidden-text-strip's `parseColor` was
regex-only and accepted just `rgb()` / `rgba()`, so any element with a
modern-syntax `color` slipped past `detectColorMatch` and any modern
ancestor background made `effectiveBackgroundColor` bail.
A white-on-white injection painted via `color: oklch(0.99 0 0)` on a
plain white card was invisible to humans and invisible to the rule.
Add a 1×1 canvas probe as a fallback after the regex parser fails. The
browser's 2D context accepts any valid CSS color string and silently
ignores anything else — the two-sentinel probe (`#1a2b3c` then
`#fedcba`) reliably distinguishes a parsed value (both probes converge
to the same resolved fillStyle) from a rejected one (each probe keeps
its own sentinel). Once accepted, paint and `getImageData` to recover
the resolved RGBA including alpha.
Per-realm singleton canvas so we don't allocate on every parse. jsdom
returns null from `getContext('2d')` without the `canvas` package, so
the existing test path keeps running through the regex parser. New
tests install a deterministic stub canvas that mimics the spec's
silent-ignore behavior, exercising the rule against oklch / lab /
color-mix syntaxes and confirming that an unparseable value still
returns null.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes one item from the red-team audit in #203 (High, S).
hidden-text-strip'sparseColorwas regex-only and accepted justrgb()/rgba(). Tailwind v4, Open Props, and any stylesheet authored against CSS Color Level 4 emitoklch(),lab(),color(), andcolor-mix()in computed style verbatim, so:color: oklch(...)matching its background wasn't caught bydetectColorMatch—parseColorreturned null on the modern syntax and the candidate was preserved.effectiveBackgroundColorto bail (the existing intentional behavior, added to avoid strippingbg-orange-500buttons with white text inside a white card).The result: a white-on-white injection painted via
color: oklch(0.99 0 0)on a plain white card was invisible to humans and invisible to the rule.Approach
Add a 1×1 canvas probe as a fallback after the regex parser fails. The browser's 2D context accepts any valid CSS color string and silently ignores anything else.
#1a2b3cthen#fedcba) reliably distinguishes a parsed value (both probes converge to the same resolvedfillStyle) from a rejected one (each probe keeps its own sentinel).getImageDatato recover the resolved RGBA including alpha.The existing regex parser runs first, so the common
rgb()/rgba()path stays a single regex match.Test plan
bun run test -- hidden-text-strip— all 41 cases green (36 existing + 5 new).bun run preflight— codegen + Biome + ESLint + typecheck + knip + Jest all green.color: oklch(...)is correctly stripped.lab(...)is correctly stripped.color-mix()matching the ancestor background is correctly stripped.Notes for reviewers
getContext('2d')returns null without thecanvaspackage, so the canvas path silently no-ops in the existing test suite — the regex parser fully handles those cases. The new tests install a deterministic stub canvas that mimics the spec's silent-ignore behavior; the stub also wires the two sentinels through so the rule's probe round-trips correctly.__resetColorProbeForTestingis exported solely so the test can re-prime the cached canvas after installing the stub.rgb()/rgba()shape that browsers emit by default, and keeps all existing tests exercising the same code path they do today.fillStyleread-back for the#rrggbbform drops alpha; sampling viagetImageDatakeeps it.🤖 Generated with Claude Code