Repository navigation
Description
Problem
The directory's name and description cleaners in pkgs/id promise "must not contain control characters", but the check is 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 cleaners, so an account name, a group name, or a group description can carry invisible or text-reordering characters into every view that displays it. This is the same Cf class as #1674, but a different module, different functions, and a different fix: #1674 is scoped to petname.rs (normalize_name/fold/skeleton), and #1770 is scoped to invite.rs, which has no check at all. Neither covers the directory — the layer that creates the alleged names the petname layer then has to defend viewers against.
Evidence (verified this run on main 35726e8e, src/directory.rs sha256 9ef8669c…, identical on sandbox and laptop clones)
clean_name(pkgs/id/src/directory.rs:1846-1859): trim, non-empty, ≤64 chars, then!name.chars().any(char::is_control)with error text "name must not contain control characters". Rust'schar::is_controlis general category Cc only.clean_description(directory.rs:1861-1875): same check (allowing\n), same gap.- Call sites: account creation (
insert_account,directory.rs:1463, reached fromsign_up/sign_up_keyedapply paths), group creation (directory.rs:1588), group update — both the entry point (update_group,directory.rs:862) and the journal apply path (directory.rs:1611). Because the journal apply path re-cleans, a fix here covers replayed entries too. - Display surfaces:
directory_view.rs:1609feedsaccount.nameas theallegedname intopetname::labels(the viewer-facing contact labels);directory_view.rs:1666,1679render group and member names;web/explore.rs:399rendersgroup.descriptionthroughhtml_escape, which neutralises markup but not bidi/format characters. - Probe (faithful Python mirror of
clean_name/clean_description— Rustis_control≡ category Cc — run on the laptop; probe + log at~/git/fleet/probe-2026-10-28-laneB-dircf/):- Control:
Alice<TAB>Adminname → REFUSED (the Cc guard works); LF in a description → accepted, by design. - BUG:
Alice+U+200B → ACCEPTED;Alice+U+202E+Admin→ ACCEPTED;Alice+U+2066+Admin+U+2069 → ACCEPTED;Owners+U+2060 → ACCEPTED; U+FEFF+Alice→ ACCEPTED;Al+U+00AD+ice→ ACCEPTED; descriptionsafe text+U+202E+evil→ ACCEPTED. All are category Cf. - Plain
Alice Smith→ accepted, unchanged.
- Control:
Why it matters
Directory names are self-asserted and shown to other people: account names become the "alleged" half of every contact label, and group names/descriptions are rendered in the directory and web views. An invisible-character account or group name renders identically to a legitimate one (the exact spoof shape the petname module exists to prevent, created here at the source), and a U+202E override in a name or description can reorder the visible text around it. The cleaners' own error messages claim a guarantee they do not deliver.
Acceptance criteria
-
clean_namerefuses (or strips, with the choice pinned and documented) Unicode format characters (category Cf) — at minimum U+200B, U+202A–U+202E, U+2060, U+2066–U+2069, U+FEFF, U+00AD — so the "must not contain control characters" promise holds;clean_descriptiongets the same rule (keeping its\nallowance). - Tests in
directory.rscover at least: ZWSP in an account/group name, RLO in a name, RLO in a description, and a plain name/description with ordinary spaces and punctuation still accepted. - Existing directory tests still pass (laptop or CI).
- Triage records the layering against 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 (petname-side) and id(invite): Invite::new has no control-character check at all — a crafted invite name carries newline/tab/CR into the /invite page and the prefilled petname #1770 (invite-side): whether one shared name-cleaning helper should own the Cf rule for all three, or each module keeps its own check.
Dedup
Searched open + recently closed issues for Cf / format character / U+200B / clean_name terms: only #1674 (petname.rs-scoped), #1770 (invite.rs, no Cc check at all), and closed lab-ops-hub UTS-46 items (#652/#516, different package). No filing covers directory.rs clean_name/clean_description.
Activity
developing-today commentedon Oct 28, 2026
CODE-720
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
165 remaining items
dezren39 commentedon Nov 2, 2026
🤖 Agent note · parked — Drew-owned PR boundary
dezren39 commentedon Nov 2, 2026
agent-note:v1 — parked (lane F, task 4889)
dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up (ownership correction + CI disposition)
queue/4889-1778-id-directory-cf, body "Fixes 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", worktree~/git/fleet/4889. Noheld/user/owned/do-not-merge/do-not-auto-editlabel on 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 or PR fix(id): reject Cf in directory names and descriptions #3945 (verified fresh).cargo test -p id --lib --no-default-features --features world directory::tests43 passed; invite Cf test passed; rustfmt + diff-check passed. Codex review on the fix commit c1d3d68: no findings, none unresolved. Head 85877d4 is only a clean main-merge (no directory.rs overlap).fix(id): reject Cf in directory names and descriptions (#3945)
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done
directory::tests43 passed + invite Cf test passed (PR body); Codex no findings on the fix commit; CI run 41343718780 was infra-poisoned (see pickup note) and did not block the merge under standing authority.