Skip to content

Per-sink sanitization helper for caller-supplied text in display fields #345

@prasanna-anchorage

Description

@prasanna-anchorage

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

  • sanitize_user_text helper added to visualsign.
  • At least the four sinks above migrated.
  • Regression test per sink: caller-supplied text containing \n renders as a single visual line (no embedded newlines in the resulting field).
  • DECODER_GUIDE.md updated to point new decoders at the helper for any caller-controlled string.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions