Repository navigation
fix(content): the word card opens only for the learner; filter, clip-path and mask count as hidden (review E-04, E-05) - #37
Merged
Merged
Conversation
Security review finding E-05 (slice 54, reviewer E). A swapped word sits in the page's own DOM, so a page could restyle it to cover the whole screen almost invisibly. The learner's pointer resting anywhere, or a click on one of the page's own buttons, then landed on the word and opened its card, and the page searched the card's text with window.find. The card now opens only on a word the learner can see: rendered, at least 10 % opaque counting every ancestor's opacity and filter: opacity(), under no clip-path, mask or url() filter, and with a box no more than about twice the size of its text. A hover, click, tap or long press must also land within 4 px of the word's drawn text. Enter on a focused word and the "Show details" command still open it. Kotiko's own text nodes count as the word's text; elements a page puts inside the word don't. Regression tests: test/security/page.spec.mjs (A-04b resting pointer, a click on a page button under a stretched word, a transparent word kept under the pointer, A-04c reading within one frame, and a real hover and click still opening) and test/dom/popover.test.mjs. Each failed before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
Security review finding E-04 (slice 54, reviewer E). checkVisibility() doesn't see text hidden with filter: opacity(0), a clip-path that clips everything or a fully transparent mask, so Kotiko swapped all of it and a page could read the learner's words there: 300 of 300 in reviewer E's test, in Chromium and Firefox. Text is now swapped only when it is at least 10 % opaque, counting every ancestor's opacity and filter: opacity(), and under no clip-path, mask or url() filter. A clip-path's shape can't be measured cheaply and a mask's image can't be read at all, so text under any of them waits, even where it would show; it is looked at again after a transition, an animation, or the learner's click, key or scroll, as hidden text already was. Each element's style is read once per 8 ms slice. Measured in Chromium: about 0.7 microseconds more per text node on the 100,000-node page and 7 on a page 30 elements deep, with no change in the first swap or the longest task on either page. No text in the e2e corpus pages is affected. Regression tests: test/security/page.spec.mjs (filter: opacity(0), clip-path inset(100%) and circle(0), a transparent mask, opacity 0.01: 0 of 300 words each; the same probe in plain view swaps, and swaps once the clip-path goes) and test/dom/content.test.mjs. Each failed before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
…rd opens For security review findings E-04 and E-05 (slice 54, reviewer E). The privacy policy moves to version 5. "What websites can see" now lists the hidden text Kotiko never swaps: text not displayed, see-through or nearly so (a filter included), clipped or masked. It also says what still counts as shown: very small, background-coloured, blurred or covered text. And it says the card opens only from the learner's own input on a word they can see, where it's drawn. A page that disguises a word under a resting pointer could still get its card opened. The inventory, the assurance case's residual risks and the changelog say the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
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.
Fixes E-04 and E-05 from reviewer E on
v1.0.0-rc.3(slice 54, SCR-476). Both made the privacy policy's "What websites can see" untrue. Each fix has regression tests intest/security/page.spec.mjsand the DOM suites, and the tests failed on the old code.E-05: only the learner opens a word's card. On rc.3, a page could restyle one of its swapped words as a nearly transparent, full-screen element. A resting mouse, or a click on the page's own button, then opened Kotiko's card, and the page read it with
window.find(the lead reproducedcardOpen: trueon hover alone). Now every way of opening the card (hover, click, tap, long press, Enter) first checks that the word is really there:opacityandfilter: opacity()included);url()filter covers it;Range.getClientRects()).Enter on a focused word still works.
E-04: text hidden by
filter,clip-pathormaskisn't swapped. Text below 10 % opacity, or under anyclip-path,maskorurl()filter, waits until it's actually shown. That is a conservative rule. All five variants from E's proofs now reveal 0 of 300 words; each revealed 300 before. The style lookups are cached per slice, and the first swap on a 100,000-node page is unchanged at about 700 ms.Privacy policy, version 5: it now says exactly which hidden text is never swapped, what still counts as shown (tiny, background-coloured, blurred or covered text), and when a card opens, including the remaining case of a word disguised in the background colour.
Lead check:
test/security/page.spec.mjs20/20 on this branch.Checks: lint; coverage 94.41 % lines, 85.07 % branches; perf; Playwright 125 passed;
npm test2072 pass ×3; spec and privacy sync; old-name; base-neutral; REUSE; DCO.🤖 Generated with Claude Code