Skip opacity:0 strip when the element is animating opacity#128
Merged
Conversation
Primer's <dialog> backdrop renders at opacity:0 with an opacity transition, then fades to 1 once GitHub's chooser opens. The subtree watcher caught the wrapper mid-transition and stripped the whole modal subtree before the user ever saw it. An animating opacity:0 is a transient state, not the hidden-injection signal the rule defends against — by definition the text will become visible to sighted users when the animation completes, so the asymmetry the rule relies on doesn't hold. The new guard accepts both the expanded longhand (real browsers populate transitionProperty / transitionDuration / animationName / animationDuration) and the shorthand (jsdom keeps only `transition` / `animation`). A plain opacity:0 with no transition or animation is still stripped. Closes #126 Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
4 tasks
There was a problem hiding this comment.
1 issue found.
About Unblocked
Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.
📖 Documentation — Learn more in our docs.
💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.
👍 Give feedback — React to comments with 👍 or 👎 to help us improve.
⚙️ Customize — Adjust settings in your preferences.
Unblocked flagged the original shorthand check on PR #128: the regex matched every CSS time literal including `0s` / `0ms`, so an attacker could declare `transition: opacity 0s` and the shorthand block would short-circuit the strip even though the text stays permanently invisible. The longhand check on the same path correctly rejected zero durations, but in a real browser both longhand and shorthand are populated so execution fell through to the broken shorthand block. Two changes: - Gate the shorthand check behind `!style.transitionProperty` / `!style.animationName`. Real browsers always populate the longhand (the canonical signal), so the shorthand is only consulted in jsdom where the longhand is empty. - Parse magnitudes numerically when scanning a shorthand. The refactored `shorthandHasNonzeroDuration` walks every duration token and returns true only when at least one has a value > 0. Regression tests cover `transition: opacity 0s`, `animation: fadeIn 0s`, and `transition: transform 150ms` paired with `opacity: 0` — all three must still be stripped. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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
hidden-text-stripwas the rule hiding GitHub's "Create new issue" modal. Primer's<dialog>backdrop (prc-Dialog-Backdrop-*) renders atopacity: 0with an opacity transition, then fades to 1 once the dialog opens — the subtree watcher caught the wrapper mid-transition and removed the entire modal subtree before the user ever saw it.opacity-0strip when the element has an in-flight CSS transition or animation involvingopacity(orall). A plainopacity: 0with no animation is still stripped — the regression test for that case is added explicitly so the guard can't drift permissive.Why this is safe
An animating
opacity: 0is a transient state — the text will become visible to sighted users when the animation completes, so the asymmetry the rule defends against (text invisible to humans but readable to agents) doesn't hold. The guard requires a non-zero duration to fire, sotransition: opacity 0s/ unset durations still strip normally.jsdom note
Real browsers expand the
transition/animationshorthand intotransitionProperty/transitionDuration/animationName/animationDuration. jsdom keeps only the shorthand. The detector accepts both: longhand is the production path, shorthand fallback keeps the tests honest.Test plan
bun run test— 995 passing, including 4 new cases (transition withopacity, transition withall, keyframe animation, regression guard that plain opacity:0 still strips).bun run check+bun run typecheck.extension/dist/as unpacked, visitgithub.com/pixiebrix/agent-browser-shield/issues, click New issue — the template chooser modal should appear.Out of scope
The
prc-components-Label-*clip-to-zero strip you also saw in the console (theSelect all issuesSR-only label) is a separate false positive — Primer's label is a real SR-only element that should be admitted byhasStructuralSrOnlyPattern, but Primer must size it slightly differently than what we recognize. Worth a follow-up issue if it's hurting GitHub a11y output.Closes #126
🤖 Generated with Claude Code