Public: anyone can read this; the crew can change it.
A card title cannot repaint someone's terminal: strip control characters at the boundary and when drawing #34
Sign in to join this conversation.
Public: anyone can read this; the crew can change it.
Found while reviewing #29 and confirmed on that pull's preview: a card title carrying a literal ESC byte round-trips through the app and is written to a terminal unescaped.
cleanTextinsrc/routes.ts:67isString(value ?? "").trim().slice(0, max).trim()removes whitespace, not the C0 range, so\x1b,\x07and the rest survive into SQLite.src/mcp.ts:64does the same by hand fortitle. On the web page this is harmless —src/pages/*escape their output and a browser prints ESC as nothing — butdraw()insrc/tty.tswritesc.title,col.nameandboard.namestraight into an ANSI stream onprocess.stdout.So anyone holding a board's share link can post a card whose title moves the cursor, clears the screen, or — on a terminal honouring OSC — sets the window title of whoever has that board open over
ssh. It is a modest injection, not a foothold: the terminal never runs what it prints, andsrc/tty.tsshells out to nothing. But it is a person's screen being written to by another person's input, and the app's own bar says every input is validated at the boundary and every output escaped. Neither happens here.#22 forbade behaviour changes in #29, so it was left alone and written up instead.
Done looks like
cleanTextstrips the C0 range (\x00-\x1f, keeping nothing — a title is one line) and C1 (\x7f-\x9f) before it trims and slices, so nothing stored from the web can address a terminal.src/mcp.tsgoes through the same function rather than its owntrim().slice(), so the agent seat cannot post what the web seat cannot.draw()guards what it prints anyway, because the database already holds whatever was written before this change: oneprintable()insrc/tty.tsapplied to every title and name it interpolates. Both halves, since neither alone is enough — the boundary for what arrives next, the draw for what is already there./b/:id/cardswith\x1b[2Jin the title stores a title with no ESC in it;src/tty.test.tsor a small draw test asserts a card title already in the database is printed without its control bytes.Files I expect to change
src/routes.ts—cleanText.src/mcp.ts— use it.src/tty.ts—printable()around whatdraw()interpolates.src/routes.test.ts/src/server.test.tsandsrc/tty.test.ts— the two tests above.Risks
src/tty.tsbuilds its own ANSI;printable()must apply to the data it interpolates and not to the codes it writes itself.visibleLength()insrc/tty.tscounts ANSI as zero width for padding. Once titles cannot contain ANSI, the padding is right for the first time; check a column still lines up.