Follow-up from the #331 + #332 charset-family merges (both landed 2026-05-29).
validate_charset now rejects every short-form JSON control escape except \n, because legitimate multi-line display uses \n in ~30 call sites of the form `"Program ID: X\nData: Y"`. The carve-out is unavoidable at the central gate, but it leaves a UI-line-spoof primitive open:
An attacker who controls a string field in caller-supplied metadata (Ethereum ABI mapping function/parameter names, Solana IDL program/instruction names, Tron parameter.type_url, etc.) can embed \n to fake additional wallet UI lines:
```
"Approved spender\n\nRecipient: 0xMallory"
```
validate_charset accepts this (the \n is allowed); the wallet UI renders it across two visual lines, and the signer believes "Recipient: 0xMallory" is a separate trusted field.
Suggested API shape
Add a helper to the visualsign crate (next to validate_charset):
```rust
/// Strip newline and other layout-affecting control characters from caller-
/// supplied text before inserting it into a display field. Use at every sink
/// where untrusted metadata (function names, parameter labels, program names,
/// IDL/ABI string fields) becomes part of a SignablePayload string field.
///
/// The central validate_charset gate accepts \n because legitimate
/// multi-line display uses it; this helper closes the per-field UI-line-spoof
/// primitive that the central gate cannot.
pub fn sanitize_user_text(s: &str) -> String { ... }
```
Conservative scrub set: \n, \r, \t, \b, \f, \v, plus the ASCII DEL (0x7F). Probably also bidi controls (U+202A-U+202E, U+2066-U+2069) and zero-width chars (U+200B-U+200D, U+FEFF) for belt-and-braces, though those should already be blocked by validate_charset if they reach the central gate.
Sinks to migrate
Identified during the #331 + #332 reviews:
Acceptance criteria
Follow-up from the #331 + #332 charset-family merges (both landed 2026-05-29).
validate_charsetnow rejects every short-form JSON control escape except\n, because legitimate multi-line display uses\nin ~30 call sites of the form `"Program ID: X\nData: Y"`. The carve-out is unavoidable at the central gate, but it leaves a UI-line-spoof primitive open:An attacker who controls a string field in caller-supplied metadata (Ethereum ABI mapping function/parameter names, Solana IDL program/instruction names, Tron
parameter.type_url, etc.) can embed\nto fake additional wallet UI lines:```
"Approved spender\n\nRecipient: 0xMallory"
```
validate_charsetaccepts this (the\nis allowed); the wallet UI renders it across two visual lines, and the signer believes "Recipient: 0xMallory" is a separate trusted field.Suggested API shape
Add a helper to the
visualsigncrate (next tovalidate_charset):```rust
/// Strip newline and other layout-affecting control characters from caller-
/// supplied text before inserting it into a display field. Use at every sink
/// where untrusted metadata (function names, parameter labels, program names,
/// IDL/ABI string fields) becomes part of a SignablePayload string field.
///
/// The central
validate_charsetgate accepts\nbecause legitimate/// multi-line display uses it; this helper closes the per-field UI-line-spoof
/// primitive that the central gate cannot.
pub fn sanitize_user_text(s: &str) -> String { ... }
```
Conservative scrub set:
\n,\r,\t,\b,\f,\v, plus the ASCII DEL (0x7F). Probably also bidi controls (U+202A-U+202E, U+2066-U+2069) and zero-width chars (U+200B-U+200D, U+FEFF) for belt-and-braces, though those should already be blocked byvalidate_charsetif they reach the central gate.Sinks to migrate
Identified during the #331 + #332 reviews:
chain_metadata.abi_mappings(post-fix(ethereum): reject unsigned ABI mappings in chain metadata #335 the body is signature-validated but the strings still flow through to display).program_namefor non-trusted programs (fix(solana): reject IDL overrides for trusted programs and validate signatures #336'scanonical_nameshort-circuit covers trusted ones, but custom programs render attacker-controlled names verbatim).parameter.type_url— flagged in fix(visualsign): reject JSON short-form control escapes in validate_charset #332's source comment at lines 922-924.Acceptance criteria
sanitize_user_texthelper added tovisualsign.\nrenders as a single visual line (no embedded newlines in the resulting field).DECODER_GUIDE.mdupdated to point new decoders at the helper for any caller-controlled string.