harden: show a personal message's hex and mark control characters in its text (closes #403)
check / check (push) Failing after 3s
e2e / e2e-chrome (push) Failing after 3s
e2e / e2e-firefox (push) Failing after 2s

The signature screen showed only the text a personal message decodes
to, with bidirectional and zero-width characters acting on it, so a
site could make the message read differently from the bytes that are
signed, and a message that was not hex was decoded into NUL
characters. The screen now shows the hex as "Raw data" alongside the
decoded text, and each control or format character in the text as a
U+XXXX mark. Signing takes the bytes from the hex, so a message that
is not hex cannot be signed: it is shown as plain text, with the error
line saying so and "Sign" disabled.

Model: opus-5-5
This commit is contained in:
2026-10-04 16:52:46 +00:00
parent de3f7a9a11
commit 248057a25d
5 changed files with 229 additions and 13 deletions
+15 -2
View File
@@ -1920,8 +1920,13 @@ view would leave a wallet one click from deletion.
- Danger warning box (shown for `eth_sign`, which signs a raw hash)
- Type: "Personal message" or "Typed data (EIP-712)"
- From: color dot + full address + etherscan link
- Message: decoded UTF-8 text (personal_sign) or formatted domain/type/
message fields (EIP-712 typed data). The primary type shown is the one
- Message: for `personal_sign` and `eth_sign`, the text the message's bytes
decode to as UTF-8, with each control or format character (zero-width and
bidirectional characters among them) shown as a bordered `U+XXXX` mark
instead of acting on the text, so it reads in the order of the bytes that
are signed; a line feed is shown as a line break. Bytes that are not UTF-8
are shown as "This message is not text." For typed data, formatted
domain/type/message fields (EIP-712). The primary type shown is the one
ethers signs, derived from the typed data's `types`, not the type the site
states.
- Token permission warning, at the top of the message (typed data whose
@@ -1935,12 +1940,20 @@ view would leave a wallet one click from deletion.
domain's `verifyingContract`; any those fields do not give is shown as
`Unknown`, and the domain, type and message lines still follow. Only typed
data that cannot be read at all is shown as raw text.
- Raw data (`personal_sign` and `eth_sign`): the message's hex exactly as
the site sent it. The bytes it encodes are what is signed, as an EIP-191
personal message.
- Password input and an error line
- "Sign" / "Reject" buttons
- **Transitions**:
- Typed data that states no primary type, or one other than the type it
would be signed as, or that cannot be read → shown with the error line
saying so and "Sign" disabled; only "Reject" remains
- A `personal_sign` or `eth_sign` message that is not hex (`0x` and an even
number of hex digits) → shown as plain text, with the error line "This
message is plain text, not hex, so it cannot be signed." and "Sign"
disabled; signing takes the bytes from the hex, so such a message has none
to sign
- "Sign" (correct password) → signs locally → closes popup (returns
signature)
- "Sign" (wrong password, or a signing failure) → error line, no screen