Skip to content

fix(cli): print titles and names without what terminals act on - #358

Merged
kalaspuff merged 4 commits into
fctl/573-linear-linksfrom
fctl/573-plain-titles
Oct 30, 2026
Merged

kalaspuff merged 4 commits into
fctl/573-linear-linksfrom
fctl/573-plain-titles

Conversation

@fctl-sh

@fctl-sh fctl-sh Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Session titles reached people's terminals as the server had them. fctl sessions, fctl show, the header of fctl attach and fctl run, and the host commands' lists printed them raw, and the titles the control plane makes itself skipped the control-character check that titles people set get: one made from a prompt's first line, from a Linear ticket's title, a schedule's prompt or a reviewed pull request's title. So a project editor, the author of a pull request a review agent reviews, or whoever writes a Linear ticket that someone delegates could put escape sequences in a title that, when another member listed sessions, set their clipboard (in terminals that allow OSC 52), rewrote their screen, set the window's title or planted links.

  • Titles fctl makes leave out control characters and those that turn text around (fctl_domain::naming::plain), as names already did.
  • The CLI prints titles, descriptions, branches, names, models and what fctl login shows (the instance's name, the code, the link, who signed in) through style::plain, one line without any control characters, and error messages through terminal_safe, as transcripts already were (fix(cli): open only the server's sign-in links and print sessions safely #174). Titles stored before this print clean too.

Verified against cargo xtask dev: a session started with a prompt that begins with an OSC 52 sequence gets a title without it, and with a raw escape sequence put into a stored title, fctl sessions and fctl show print it without any escape or bell character. New unit tests cover the domain's titles, review titles and the CLI's plain; cargo xtask check --quick passes.

Stacked pull requests

Based on the one before it in this security pass's stack:

fctl session


Note

Low risk. It only changes how titles and names are cleaned or printed, in the CLI and in the titles the control plane derives, with no stored-data or protocol change.

What it does. Titles the control plane derives from prompts, Linear tickets, schedules and reviewed pull requests now drop control characters and characters that reverse text, so escape sequences in them can no longer act on a terminal. The CLI also prints titles, descriptions, branches, names, models, login details and error messages through style::plain or terminal_safe, now including the fctl run confirmation line and fctl edit output, so titles stored before this print clean too.

fctl review's first look at 10587ed · 8 files, +114 −25 · Review agent settings

Session titles reached the terminal as the server had them: `fctl
sessions`, `fctl show`, `fctl attach` and the host commands printed them
raw, and titles the control plane makes itself (from a prompt, a Linear
ticket's title, a schedule's prompt or a reviewed pull request's title)
skipped the control-character check that titles people set get. A project
editor, the author of a reviewed pull request or whoever writes a Linear
ticket could put escape sequences in a title that set another member's
clipboard, rewrite their screen or plant links when they listed sessions.

Titles fctl makes now leave out control characters and those that turn
text around, and the CLI prints titles, descriptions, branches, names and
what `fctl login` shows through `style::plain`, and error messages through
`terminal_safe`.
@fctl-review

fctl-review Bot commented Oct 30, 2026

Copy link
Copy Markdown

fctl reviewed f591f07 and found 2 issues.

The domain side (naming::plain, title_from_prompt, reviews::title) looks right and its tests pass. The CLI side leaves a few title sinks unsanitized, including the fctl run header the description says is covered.

On lines the diff doesn't show:

  • fctl run still prints the title raw · Medium severity · crates/fctl/src/cmd/sessions.rs line 280: The confirmation line built in run formats session.title into style::bold(...) without style::plain. The description says the fctl run header is covered, but this title can be one the control plane derived from a prompt or ticket, so escape sequences in it still reach the terminal.
  • print_edited prints title and description raw · Low severity · crates/fctl/src/cmd/sessions.rs lines 691–693: print_edited (used by fctl edit) prints title and description as the server returns them, while fctl show now prints the same values through style::plain. A title or description another member set reaches the terminal unchanged.

Reviewed by fctl · Review agent settings

fctl-sh Bot added 2 commits October 30, 2026 21:57
The line `fctl run` prints for the session it started, and what `fctl
edit` prints of a session's new title and description, still went to the
terminal as the server had them; both go through `style::plain` now, as
in `fctl show`.
@fctl-sh

fctl-sh Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

On the review's two summary findings, both fixed in 10587ed: the line fctl run prints for the session it started now shows the title through style::plain, and print_edited (fctl edit) prints the title and description through it too. The other places that print names (people's, workspaces', projects', runners', devices') take names the control plane cleans as it stores them since #239, so I left them as they are.

@kalaspuff
kalaspuff added this pull request to stack #361 October 30, 2026 21:59
@kalaspuff
kalaspuff merged commit f87bc65 into main Oct 30, 2026
11 checks passed
@kalaspuff
kalaspuff deleted the fctl/573-plain-titles branch October 30, 2026 22:14
kalaspuff pushed a commit that referenced this pull request Oct 30, 2026
## Summary

What a linked Linear account delegates to fctl, or mentions fctl in,
starts sessions as its fctl person, on their Claude subscription, and
its replies reach their sessions' agents. With a stolen browser session
or token, someone could link their own Linear account to the victim's
fctl account, with nothing to confirm and nothing said to the person,
and go on starting the victim's sessions from Linear, reading what they
do there, long after the stolen token was revoked or the password
changed.

Linking a Linear account is now a sensitive action
(`Sensitive::LinkLinear`), as connecting a Claude subscription became in
#326: where the primary instance owner turned on confirming it's you, it
takes a passkey or a code. The app's "Link your Linear account" buttons
(Settings and a project's Tickets tab) confirm before they go on to
Linear; a link opened elsewhere (Linear's own button in an agent
session, `fctl account linear connect`) goes back to Settings with
"Linking a Linear account takes confirming it's you", where linking
again confirms first. A new link also emails the person when email is
set up, naming the Linear account and saying what it does.

Verified in `cargo xtask dev`: with confirming turned on and an
authenticator app on the account, an unconfirmed session's `GET
/api/v1/linear/link` goes back to `/settings?linear=confirm#linear`, and
after `POST /api/v1/auth/verify` it goes on to Linear's sign-in. The web
tests for the Tickets tab and the outcomes pass, and `cargo xtask check
--quick` passes. The email needs an instance with email, which the dev
stack isn't.

The privacy policy lists the new email since:

- hyperstructure/fctl-site#97

## Worth confirming

- **Decision:** like every sensitive action, this asks only where
confirming it's you is turned on, which is off by default; elsewhere the
email is what tells the person.
- **Decision:** a link from Linear's own button that needs confirming
lands on Settings rather than taking up the agent session that asked for
it; after linking from Settings, fctl still takes up the delegations
that waited for that Linear account.

## Stacked pull requests

The fixes before this one in this security pass are on main; the next
ones build on this one:

- #358
- #359
- #360

[fctl session](https://app.fctl.sh/s/ses_01M677K9YMM5WPZBTAR2KKXMSZ)

<!-- FCTL_REVIEW_SUMMARY -->
---

> [!NOTE]
> **Medium risk.** It adds a confirm-it's-you check to linking a Linear
account, which changes an authentication-adjacent flow for everyone who
links one; a mistake could block linking or let a stolen session link an
account.
>
> **What it does.** Linking a Linear account lets Linear delegations
start sessions as the person, so `GET /api/v1/linear/link` now requires
the same sensitive-action confirmation as connecting a Claude
subscription, where the instance asks for it. The web app confirms
before sending people there, and Linear's own button is redirected back
to Settings to confirm. A notice email is also sent when an account is
linked.
>
> **New outcome and email.** Linking now has a `confirm` outcome shown
in Settings and sends a best-effort email to the person;
docs/security.md and docs/architecture.md are updated.
>
> <sub>[fctl
review](https://app.fctl.sh/s/ses_01M67D37HF57SP912A3YYJK0WN)'s first
look at
[`393e78c`](393e78c)
· 10 files, +173 −11 · [Review agent
settings](https://app.fctl.sh/p/fctl?tab=reviews)</sub>

<!-- /FCTL_REVIEW_SUMMARY -->

---------

Co-authored-by: fctl-sh[bot] <343971779+fctl-sh[bot]@users.noreply.github.com>
kalaspuff pushed a commit that referenced this pull request Oct 30, 2026
…359)

## Summary

The device page warns against approving a CLI sign-in someone else
started, but its other use had no such warning: a "confirm it's you"
request, which any CLI or unlimited access token that is signed in
already can start (`POST /api/v1/auth/verify/device`) and send as a
link. With a leaked CLI or access token, on an instance that asks to
confirm sensitive actions, someone could send its owner that link: one
approval gave the token 15 minutes of sensitive actions, such as making
an access token that outlives revoking the leaked one, which the
confirmation is there to stop. The page now says what approving allows
and to deny a request you didn't make yourself, in a terminal, just now.

Verified in `cargo xtask dev` with a confirmation code started from the
dev token: the screenshots below are of `/device?code=…`, main against
this branch. `npm run check` in `web/` passes.

## Screenshots

At e6ecbc0, the device page for a confirmation request, 900 px wide.

| Before | After |
|---|---|
| ![Before: the confirmation request,
light](https://app.fctl.sh/api/v1/published/6729xHwXvRde4zGOEGsxl4aLwJ8nPVtgvOThbxWoMz8/before-device-light.png)
| ![After: the confirmation request with the warning,
light](https://app.fctl.sh/api/v1/published/jB4jF1WsHgdIYDihu2AGc4v6o4FX5fnD18yVADfHSFn/after-device-light.png)
|
| ![Before: the confirmation request,
dark](https://app.fctl.sh/api/v1/published/74oJo7ENHWPcd61iKIzTH42y2tJoofRWLnYfXp51kUa/before-device-dark.png)
| ![After: the confirmation request with the warning,
dark](https://app.fctl.sh/api/v1/published/Qv8nPaqT8RylCp9ejl1OiPnz0FX4SQD1cvdqsALC7SC/after-device-dark.png)
|

At a phone's width, dark: ![After: the confirmation request at 390 px,
dark](https://app.fctl.sh/api/v1/published/GcIVYAGVS6QgcOzOQYvwrO327YmknrnCs1v2fBgaMkQ/after-device-dark-390.png)

## Stacked pull requests

Based on the one before it in this security pass's stack:

- #358

[fctl session](https://app.fctl.sh/s/ses_01M677K9YMM5WPZBTAR2KKXMSZ)

<!-- FCTL_REVIEW_SUMMARY -->
---

> [!NOTE]
> **Low risk.** It only changes the wording of a warning on the device
page; no logic, API or stored data is touched.
>
> **What it does.** The device page already warned against approving a
CLI sign-in that someone else started, but not against approving a
confirm-it's-you request, which a signed-in CLI or token can start and
send as a link. The confirmation text now says what approving allows (15
minutes of sensitive actions, such as making access tokens and exporting
data) and tells people to approve only what they started themselves in a
terminal, just now.
>
> <sub>[fctl
review](https://app.fctl.sh/s/ses_01M67D4QBTA7H37DJ9VA0BHJ7K)'s first
look at
[`bb36116`](bb36116)
· 1 file, +3 −1 · [Review agent
settings](https://app.fctl.sh/p/fctl?tab=reviews)</sub>

<!-- /FCTL_REVIEW_SUMMARY -->

Co-authored-by: fctl-sh[bot] <343971779+fctl-sh[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant