Repository navigation
Description
Problem
The world runtime's two text sanitizers promise to strip control characters from participant display names and chat text, but they filter with char::is_control — Unicode category Cc only. Category-Cf format characters (U+200B ZERO WIDTH SPACE, U+202E RIGHT-TO-LEFT OVERRIDE, U+2066/U+2069 bidi isolates, U+2060 WORD JOINER, U+FEFF, U+00AD SOFT HYPHEN) pass both sanitizers untouched, so a participant can join with an invisible or bidi-reordering display name, and chat lines broadcast to every other participant can carry the same characters. A display name consisting solely of zero-width spaces also passes the join path's non-empty check, so a participant can be present under a literally invisible name.
This is the same Cf class as #1674 (petname), #1778 (directory) and #1770 (invite), but a different module and fix surface: none of those covers pkgs/id/src/world/util.rs or the world join/chat paths in world/core.rs. #1833 covers web/collab cursor names, a different subsystem.
Evidence (verified this run on main 42796be5)
sanitize_display_name(pkgs/id/src/world/util.rs:69-76):.filter(|ch| !ch.is_control()), thentake(48), then trim. Rust'schar::is_controlis general category Cc only.sanitize_chat(pkgs/id/src/world/util.rs:78-85): same filter (allowing\n/\t), then trim.- Call sites: join —
world/core.rs:847sanitizes the display name,core.rs:848-851refuses only if the sanitized name is empty; chat —world/core.rs:955(participant chat) andcore.rs:578(host/system chat) sanitize before the event is committed and broadcast. - Laptop probe (verbatim copies of both functions, main
42796be5,world/util.rssha2566c6cab01…; probe~/git/fleet/probe-2026-10-28-laneB-worldcf/):"Ann\u{202E}nimdA"→ name and chat output unchanged (RLO survives)."\u{200B}\u{200B}"(zero-width only) → output unchanged and non-empty, so the join empty-name refusal does not fire."Ann\u{200B}","An\u{00AD}n","\u{2060}\u{FEFF}Bo", chat"hi \u{2066}evil\u{2069}"and"pay \u{202E} 100"→ all unchanged.- Control: Cc
U+0007is stripped from both ("A\u{0007}nn"→"Ann"), as documented. is_control()returnsfalsefor U+200B, U+202E, U+2066, U+2060, U+FEFF and U+00AD.
Why it matters
Display names and chat are the identity other participants see inside a shared world. A RIGHT-TO-LEFT OVERRIDE in a name or chat line reorders the text around it for every viewer (the classic spoofing primitive), zero-width characters make two visually identical names distinct (or one name invisible), and bidi isolates can hide or fake quoted text. The sanitizers are the single choke point for both surfaces, so this is the layer where the Cf class has to be closed for worlds — the directory/petname fixes do not reach it.
Acceptance criteria
-
sanitize_display_nameandsanitize_chatinpkgs/id/src/world/util.rsremove (or refuse, per a contract pinned in triage) the Cf characters U+200B, U+202E, U+2066–U+2069, U+2060, U+FEFF and U+00AD — consistent with whatever shared rule id(petname): format characters (Cf) bypass both petname guards — U+200B makes a spoof of another contact's name, U+202E passes the 'no control characters' rule #1674/id(directory): format characters (Cf) bypass clean_name/clean_description — U+202E and U+200B pass the 'no control characters' rule for account and group names #1778 settle on, if one exists by then. - A display name that is only Cf/whitespace sanitizes to empty, so the existing join refusal in
world/core.rsfires for invisible names. - Chat text containing Cf reaches other participants without those characters; legitimate text (including
\n/\tin chat, non-Latin scripts, emoji/ZWJ sequences if the pinned rule allows them — ZWJ U+200D is also Cf and the rule must say explicitly what happens to it) is otherwise unchanged. - Unit tests in
world/util.rs(orworld/core.rs) cover: RLO in a name, a zero-width-only name, bidi isolates in chat, and the Cc control case continuing to pass. - Existing world tests pass.
Activity
developing-today commentedon Oct 29, 2026
CODE-1016
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
3 remaining items
dezren39 commentedon Nov 1, 2026
agent-note:v1
event: picked_up
task: 5089 (repo-health-code-pilot, OpenChamber scheduled lane)
state: Fresh GitHub shows #2112 OPEN, no held/do-not-edit labels, and no matching open implementation PR. I will reuse the existing petname Cf classifier as pinned by triage 3074.
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
queue/5089-2112-cf-sanitizers, worktree ~/git/fleet/task-5089 on the laptop, base main 74a872a. Note: is_format_char is alreadypubin pkgs/id/crates/id-core/src/petname.rs and re-exported as crate::petname (invite.rs already uses it), so no petname change is needed — the sanitizers consume the shared table directly.Merge pull request #4084 from developing-today/queue/5089-2112-cf-san…
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done
sanitize_display_name(pkgs/id/src/world/util.rs) now also strips the shared petname Cf table (crate::petname::is_format_char, already pub via id-core — no petname change needed), including U+200C/U+200D; a zero-width-only name sanitizes to empty and the join refusal fires.sanitize_chatstrips Cf except U+200C/U+200D, preserving ZWJ emoji sequences and joining scripts; newline/tab behavior unchanged.cargo test -p id --lib world::util: 8 passed, 0 failed; the pre-existing sanitize-named tests also pass (4 passed under thesanitizefilter run). Logs: /tmp/task5089-test2.log on the laptop, read back directly.fix(id): accept pre-#2112 legacy display names on restore (#2960)