Follow-up to E29. When the userscript runs in an isolated world
(Tampermonkey @sandbox DOM, Violentmonkey @inject-into content, checked
via GM_info), frame commands travel through the manager's per-script
storage (GM_setValue + GM_addValueChangeListener on random per-instance
keys), which pages can't write. Without isolation it stays same-origin
only: in page mode the attacker page can fake navigator.getGamepads for
SofaNav in its own top frame (shown by a negative control).
Also: activate() passes document.defaultView as the event view, since
userscript managers may wrap window (PointerEvent threw in VM page mode).
test:security now installs the userscript in a pinned, sha256-checked
Violentmonkey and adds a fake-controller attack.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
External audit (ChatGPT, commit 7650661): the frame client trusted
postMessage from its parent, but a page that embeds another site is its
parent. A malicious page could fetch element ids and send "activate",
clicking inside the embedded site with no user input (its cookies and
CSRF token included). Reproduced with the real extension and as a page
script: an invisible victim frame's form was submitted 22 times.
- Extension: commands and replies go through extension messaging, relayed
by the background script with browser-provided tab/frame ids; it only
forwards commands from the tab's top frame and replies to it.
postMessage only carries a random single-use probe nonce that maps an
<iframe> to its frame id; forged probes are ignored.
- Userscript / page script: no unforgeable channel exists, so a frame is a
client only when its parent is same-origin; cross-origin frames are one
plain element.
- scripts/security.js (npm run test:security, also in CI): runs the attack
in both setups with the real extension loaded, and checks the extension
still reaches cross-origin frames (negative control: fails without the
relay). Fails 2 of 3 on 7650661, passes now.
- Test pages: ?ext mode (rely on the installed extension), attack pages.
- Docs, README, website and demo updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>