harden: escape every interpolation into popup innerHTML, and add default-src to both manifests (closes #307)
A hostile ERC-20's symbol() reached an innerHTML string unescaped, and neither manifest declared default-src, so an attacker deploying a token with 1,000+ holders and airdropping one unit could render a full-viewport cross-origin iframe over the wallet's own UI, on screens where the user types their password. escapeHtml is now a pure string replace over & < > " ' — the old version round-tripped through textContent, which escapes neither quote, while already being used inside data-copy="...". All 19 files in src/popup/views/ were audited: beyond the reported symbol site, the explorer-supplied directionLabel in all three transaction lists, wallet.name, addr.ensName, the blockie data: URIs and two ad-hoc quote-only escapes were also unescaped. Explorer URLs now go through one helper that percent-encodes the path segment. Both manifests add default-src 'self', frame-src 'none', form-action 'none' and base-uri 'none'. Three loosenings are pinned in tests/manifest.test.js and justified in README.md: style-src 'unsafe-inline' (39 static style attributes; Firefox implements neither style-src-attr nor 'unsafe-hashes'), img-src data: (blockies), connect-src https: http: (user-configurable RPC). Note frame-src 'none' blocks a frame loading, not the element existing, so the zero-iframe assertion is a claim about the escaping alone; the test asserts the element count and the literal rendered text separately, taking the count before any click an overlay could intercept. Verified: make check 39 suites / 811 tests, test-e2e 55/55 including the WebAssembly-under-CSP assertion, test-e2e-firefox 8/8, zero CSP violations asserted rather than merely unobserved. Reverting only balanceLine's interpolation reproduces the attack as 2 iframes on the address screen.
This commit was merged in pull request #327.
This commit is contained in:
22
TODO.md
22
TODO.md
@@ -44,6 +44,28 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-20: A hostile ERC-20 symbol no longer renders as live HTML in the
|
||||
popup ([#307](https://git.eeqj.de/sneak/AutistMask/issues/307)). A token
|
||||
symbol is whatever the contract's `symbol()` returns, the block explorer
|
||||
passes it through unfiltered, and `balanceLine()` interpolated it into an
|
||||
`innerHTML` string — so a token with the 1,000 holders the spam filter asks
|
||||
for, airdropped to the victim, could paint a full-viewport cross-origin iframe
|
||||
over the wallet's own UI, on the screens where the user types their password.
|
||||
`escapeHtml` moved to `src/shared/html.js` as a pure string replace over `&`,
|
||||
`<`, `>`, `"` and `'`: the old implementation round-tripped through a detached
|
||||
element's `textContent`, which does not escape quotes, and it was already
|
||||
being used inside `data-copy="..."`. Every interpolation into an `innerHTML`
|
||||
string across `src/popup/views/` was audited, not just the reported one — the
|
||||
transaction lists' direction label, the wallet name and ENS name in the Home
|
||||
list, the `href` in the explorer link, and the confirmation screen's warning
|
||||
line were all unescaped as well. Both manifests now declare
|
||||
`default-src 'self'` with `frame-src 'none'`; the four directives that had to
|
||||
stay looser than `'self'` are named and justified in the Content Security
|
||||
Policy section of README.md, and `tests/manifest.test.js` pins the whole set
|
||||
exactly. A display cap of 12 characters bounds the symbol, matching the bound
|
||||
`lookupTokenInfo()` already applied on the contract-read path. Not repurposed
|
||||
for any of this: `isSpoofedSymbol()`, which answers a different question and
|
||||
would have been the wrong control.
|
||||
- 2026-08-20: A page asking which chain the wallet is on is told the chain the
|
||||
user is actually on ([#317](https://git.eeqj.de/sneak/AutistMask/issues/317)).
|
||||
`eth_chainId` and `net_version` answered from `currentNetwork()`, which reads
|
||||
|
||||
Reference in New Issue
Block a user