docs(clawgate): the picker privacy choke point, and why the obvious test can't fail - #283
Merged
Merged
Conversation
…est can't fail Two lessons from the 1.4.0 element-ref enrichment leak (homelab-infra fcaca875 / PR #276), plus three corrections to a developer note that had been wrong since it was written. reference/element-references.md - Correct the note: the walk uses previousSibling/nextSibling (NODE variants — text nodes count), not the Element variants, and slices at 40 chars, not 120 (refText then caps at MAX_REF_TEXT_CHARS=80). Both claims were false as written. - Record the choke point: every page-text read must go through adjacentText → pageText. Reading .textContent directly is what shipped the leak. Never add a second "is this a text field" predicate — that root cause has now recurred seven times. - 🔴 New section: most obvious tests of this guard CANNOT fail. A native <input>/<textarea> never exposes its current value via textContent (typing updates .value), so a canary typed into a form field passes under the leaking build too. Three consecutive live captures were wasted this way (tasks #147-#149), each read as a pass. Only contenteditable / role=textbox and a textarea's SERVER-RENDERED initial content carry user text. Documents the fixture that actually settled it (#150), including the positive control that catches a blanket refusal. SKILL.md - The extension does NOT ship via Flux: merging to trunk deploys nothing. Brave loads a flat copy at ~/clawgate-extension ON THE WORKBENCH, delivered by sync-clawgate-extension.sh, and does not hot-reload unpacked extensions. - A missing .synced-from stamp means a hand-copy: that is how a build which never passed CI ran live for ~11 hours while trunk looked clean. - Workbench firewall allowedTCPPorts is a short allowlist — serve scratch test pages on localhost, don't open a port for a throwaway.
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.
Captures two lessons from the clawgate extension 1.4.0 element-ref enrichment leak (homelab-infra
fcaca875/ PR #276), and corrects a developer note that had been wrong since it was written.reference/element-references.mdThree factual corrections. The note claimed the enrichment walks
previousElementSibling/nextElementSiblingand caps "each at 120 chars". Both false: it walkspreviousSibling/nextSibling— the node variants, so text nodes count, which is a distinct leak surface — and slices at 40 chars, withrefTextcapping atMAX_REF_TEXT_CHARS(80). Verified against the merged code, not restated from intent.The choke point, recorded. Every page-text read in the walk must go through
adjacentText→pageText. Reading.textContentdirectly is what shipped the leak, and adding a second "is this a text field" predicate is the root cause that has now recurred seven times — six audit rounds on the picker, then again in the enrichment.New section: the obvious test cannot fail. A native
<input>or<textarea>never exposes its current value throughtextContent— typing updates.value, while.textContentkeeps whatever the server rendered. So a canary typed into a form field and picked from a neighbouring element passes under the leaking build too. Three consecutive live captures were burned this way (tasks #147–#149), each initially read as a pass. Onlycontenteditable/role="textbox"surfaces and a<textarea>'s server-rendered initial content actually carry user text.Documents the fixture that settled it (task #150): a button flanked by two
contenteditabledivs holding known canaries, plus a second button beside a plain<span>as a positive control — the guard must withhold the canaries and still capture the span, or it's a blanket refusal rather than a fix. Also notes thatchrome.commandsis unreachable from Playwright, so a hand capture is the only way to prove which build Brave actually loaded.SKILL.mdThe skill had no mention of extension delivery at all, which is part of how this went wrong:
trunkdeploys nothing. Brave loads a flat copy at~/clawgate-extensionon the workbench, delivered byscripts/sync-clawgate-extension.sh, and does not hot-reload unpacked extensions..synced-fromstamp means a hand-copy. That is exactly how a build which never passed CI ran live for ~11 hours on 2026-08-01 whiletrunklooked clean — copied straight from a working tree, soclawgate-cinever saw it.brave://extensions/shortcuts.allowedTCPPortsis a short allowlist, so serve scratch test pages onlocalhostrather than opening a port for a throwaway.Scope
Docs only — two files, no code. Unaffected by the
test_skill_size.pybyte gate, which resolves toscripts/browser-bridge/SKILL.md.⚠ These files resolve into
/nix/store(read-only copies), so they need ahome-manager switchto go live — editing the devrc source alone does not update~/.claude/skills/clawgate/.