Run inline text through the setter, so scramble actually happens - #20
Open
ithiria894 wants to merge 1 commit into
Open
ithiria894 wants to merge 1 commit into
ithiria894 wants to merge 1 commit into
Conversation
`<nocap-secret scramble>TEXT</nocap-secret>` never scrambled anything. connectedCallback assigned `this.#secret = inline`, the private field directly, which skips `set secret(value)` where the entire scramble branch lives. The attribute was present, the element rendered, nothing warned, and #secret held the plaintext in order. A security feature silently inert on one of its two documented usage paths. Measured before, secret assembled in JS so it is never a literal in the source: delivery scramble in heap #secret fields rebuilt markup off True 'PASS-7391' False - markup ON True 'PASS-7391' False - <- inert property off True 'PASS-7391' False - property ON False '' True 'PASS-7391' And after, with markup now matching property on both rows. The fix is the setter rather than the field. Its render() is a no-op here because #flicker does not exist yet, and the explicit render further down still runs, so ordering is unchanged. textContent is cleared first so the setter cannot observe it. Worth saying plainly: none of the 65 tests could have caught this, because the scramble branch needs an element and the runner has no DOM. That is the second defect today that was invisible to a green suite for the same reason. Verified in Chrome, both delivery paths, scramble on and off: analysis/scramble_reach.py in the audit repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uZYmyLEmJLTjJXiAUC3wj
This branch has not been deployed
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.
You said scramble is what blocks the heap route. It is — on one of the two ways of giving the element a secret.
<nocap-secret scramble>TEXT</nocap-secret>never scrambledconnectedCallbackassignsthis.#secret = inline— the private field directly — which skipsset secret(value), where the whole scramble branch lives. Attribute present, element renders, nothing warns, plaintext stored in order.Measured, with the secret assembled in JS so it is never a literal in the page source:
#secret#chars/#slotsreadable'PASS-7391''PASS-7391''PASS-7391''''PASS-7391'Row two is the bug. After the fix, markup matches property on both rows.
What scramble does and does not buy, now that it runs
It does defeat the cheap attack. A plain string search of a 27MB snapshot finds nothing. That is real and it is the attack most people would try first.
It does not survive the console.
Runtime.getPropertiesreturns private fields, which is exactly what expanding the element in DevTools does.#charsand#slotsboth read, and zipping them is one line:Your own comment already says this — "anyone who reads both reconstructs the value immediately" — so this is confirmation rather than news. The cost to an attacker goes from Ctrl+F to knowing which two fields to look at.
Two things I would consider separately
#secretand#slotscould be one field.#slotsis only ever used to put the glyphs back. If the shuffle order lived somewhere less discoverable than a sibling field on the same object, the console route would need more than expanding one node. Not a fix, just a higher wall.A warning when scramble is set but inert. The element already warns for the fake/scramble clash and for
letterSpacingsupport. This was the same shape of silent-inert and had no warning, which is why it lasted.Coverage
None of the 65 tests could have caught this, because the scramble branch needs an element and the runner has no DOM. That is the second defect today invisible to a green suite for the same reason — the other was mine, an
opts.gammain a method with noopts, which passed everything and threw on first render.Might be worth a minimal DOM shim in the test setup. The element is the part of this library that most benefits from being wrong loudly.
Verified in Chrome on both paths:
analysis/scramble_reach.py.🤖 Generated with Claude Code
https://claude.ai/code/session_013uZYmyLEmJLTjJXiAUC3wj