Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: ef4507e7308a459da2dac2d64b7b6e5a
Branch: dev/2-p290-clean-name-well-formed
Base: 4d851e8
At: 2026-10-31T13:48:52Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
A peer's or a room-share sender's string can arrive already ill-formed (a JSON escape like \ud83d is valid JSON and produces an unpaired surrogate). Every existing boundary fix (#1907, #1912, #1916, #1925, #1928, #1930, #1931) made our cuts land on whole code points; none of them heals an input that was broken to begin with. Two entry points then carry that broken string into text a human or a Bot reads.
Reproduction (live main 4d851e832084bdbf1fb8f8a2b1b78fce49bc4056)
Site 1 — a peer name in the chat header. core/src/host/delivery.js:38-41 cleanName strips control characters, brackets and quotes, trims, then cut(..., 60); it never normalizes well-formedness.
JSON.parse('{"name":"Ann\\ud83dX"}').name // "Ann\ud83dX" — legal JSON
cleanName('Ann\ud83dX') // "Ann\ud83dX", isWellFormed() === false
fenceUntrusted('Ann\ud83dX', 'hi') // '<untrusted-message from="Ann\ud83dX">…', isWellFormed() === falseReachable: remote-chat.js:293 shownName = cleanName(...) → :298 the message body line [Message from ${shownName} (${handle})] and :299 senderName; delivery.js:176 fromName (untrusted branch) → :193 senderName and headerFor(...)'s [Message from ${fromName}] first line. Those are the lines the owner reads in the chat, so an ill-formed name renders as Ann\ufffdX.
Site 2 — a room-share notice. core/src/shared/room-share.js:225-253 parseRoomShare returns the parsed JSON as-is; no field is normalized.
const body = { v: 1, op: 'join', roomId: 'r1', notice: 'Ann\ud83dX' } // JSON-escape the lone surrogate
parseRoomShare(`${ROOM_SHARE_MARK}\n${JSON.stringify(body)}`).notice // "Ann\ud83dX", isWellFormed() === falseThe guest stores it in room.notice, and core/src/host/groups.js:350 standingText puts room.notice verbatim into the bot-room system prompt block, so the ill-formed string reaches the model's prompt (and the room row rendering). Today core/src has 0 occurrences of isWellFormed.
Expected
At each boundary where an outside string becomes text we own, normalize well-formedness once (String.prototype.toWellFormed(), Node 20+): an unpaired surrogate becomes U+FFFD. Then the existing cleaning and cutting run as they do now — no length, no boundary rule and no path changes. Output headers, senderName values, room.notice and every rendered line are always well-formed, and a UTF-8 round trip of a header is byte-identical.
Scope
core/src/host/delivery.jscleanName— one.toWellFormed()at the head of the cleaning chain.core/src/shared/room-share.jsparseRoomShare— normalize the string fields this envelope carries (notice, rostername/ownerName, historywho/text) once, after the parse.- Tests:
core/test/fence.test.js(a peer name with a lone surrogate → well-formedcleanName, well-formedfenceUntrustedfirst line, UTF-8 round trip equal,senderNameclean) and the room-share suite (aparseRoomShareed notice with a lone surrogate → well-formed, no U+FFFD on the wire path). - Mirrors under
patches/ds-bot/files/regenerated; changelog fragment.
Out of scope: making the transport reject such a payload (another team's side of the wire), and healing values that never leave the process.
Dedupe
Open issues matching surrogate / well-formed / toWellFormed / U+FFFD: 0. The closed ones (#1907, #1912, #1916, #1925, #1928, #1930, #1931) all fixed cut points; this is the input side of the same class and none of them normalized an already-broken input. #1930 touched the fence's name cut, not the name's well-formedness.
Activity
peer names: make a peer-supplied name well formed where it enters (Th…
peer names: make a peer-supplied name well formed where it enters (Th…
host: an outside string that arrives ill-formed reaches visible text …