Repository navigation
Description
Problem
Invite::new is the only name validator in pkgs/id with no control-character refusal at all: an invite name may contain newline, tab and carriage return verbatim. Every sibling validator refuses them — petname::normalize_name and the directory's clean_name both reject char::is_control — but the invite name, which is attacker-chosen inside a link anyone can craft, goes straight to the /invite page.
Evidence (verified this run on main 9874fe59)
pkgs/id/src/invite.rs:31-45—Invite::newvalidates the name with exactly two checks: non-empty after trim, andchars().count() <= MAX_NAME_CHARS(64). No control, format or whitespace check.Invite::parse/from_query(the link a recipient actually opens) funnel through the samenew, and percent-decoding (invite.rs:133-158) happily restores%0A,%09,%0Dinto the name.- Sibling house rule:
pkgs/id/src/petname.rs:217-230—normalize_nameensures!name.chars().any(char::is_control)("a petname cannot contain control characters");pkgs/id/src/directory.rsclean_namedoes the same for account names. - Where the unchecked name lands:
pkgs/id/src/web/explore.rs:373-390—invite_pagerenders it as the page headline (<h1>Add {name}</h1>), the submit button text (Add {name}), and prefills it as the petname input's value.html_escapeneutralises markup, not control characters: a newline/tab/CR in the name renders into the page and the form field as-is, and a name the petname layer itself would refuse then fails the recipient's submit with an unexplained validation error. (Category-Cf/bidi characters in invite names are the display side of the same gap; the petname half of that class is already tracked in 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 — this issue is the missing Cc check at the invite boundary, which 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 does not cover.)
Probe (verbatim mirror, run on the laptop)
Mirror of Invite::new's name checks vs petname::normalize_name's (invite.rs sha256 79c069aa…, petname.rs sha256 ff6cba65…, main 9874fe59). Probe: ~/git/fleet/probe-2026-10-28-laneB-replay-invite/ (laptop), copy + log in the goal bookkeeping hidden_files/probe-2026-10-28-laneB-replay-invite/:
P2 control plain 'Ann Lee': invite_ok=true petname_ok=true
P2 newline 'Ann\nLee': invite_ok=true petname_ok=false
P2 tab 'Ann\tLee': invite_ok=true petname_ok=false
P2 CR 'Ann\rLee': invite_ok=true petname_ok=false
P2 bidi U+202E in name: invite_ok=true petname_ok=true (Cf display class, cf. #1674)
P2 zero-width U+200B in name: invite_ok=true petname_ok=true (Cf display class, cf. #1674)
P2 65 chars (over cap): invite_ok=false petname_ok=false
Why it matters
An invite link is a trust hand-off: the recipient's one confirmation screen says "Add {name}" for a key they cannot humanly verify. The name in that screen is the part a person actually reads, and it is entirely link-author-controlled. Today it can carry raw line breaks and tabs into the page and the prefilled petname field, and it is held to a weaker standard than every other name in the system — including the petname it is about to become, which refuses the same characters one step later.
Acceptance criteria
-
Invite::newrefuses a name containing control characters (at minimum Cc:\n,\r,\t), matching thenormalize_name/clean_namerule, with the refusal reachable throughInvite::parseandfrom_queryas well. - Tests in
invite.rs: newline, tab and CR names are refused; the existing round-trip, plus-as-space and 65-char refusal tests still pass (laptop or CI). - Triage pins whether category-Cf characters are also refused here or left to 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's petname-side fix; either way the decision is recorded on this issue.
Activity
developing-today commentedon Oct 28, 2026
CODE-718
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
14 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done (resumed from Lane D's park)
is_format_char; direct + percent-encoded tests; ordinary-space trim preserved), Codex review completed with no findings on head 0f31e2b.id-checkrun on main is the executing verification; a red run there is a fix-forward on this issue.Merge pull request #3759 from developing-today/queue/4799-1770-invite…