Skip to content

fix(web): warn against approving a confirmation someone else started - #359

Merged
kalaspuff merged 3 commits into
fctl/573-plain-titlesfrom
fctl/573-device-warning
Oct 30, 2026
Merged

kalaspuff merged 3 commits into
fctl/573-plain-titlesfrom
fctl/573-device-warning

Conversation

@fctl-sh

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

Copy link
Copy Markdown
Contributor

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 After: the confirmation request with the warning, light
Before: the confirmation request, dark After: the confirmation request with the warning, dark

At a phone's width, dark: After: the confirmation request at 390 px, dark

Stacked pull requests

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

fctl session


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.

fctl review's first look at bb36116 · 1 file, +3 −1 · Review agent settings

The device page warned against approving a CLI sign-in someone else
started, but not a "confirm it's you" request from a CLI or token that is
signed in already, which any of them can start and send as a link. With a
leaked CLI or access token, 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 it. The page now says what
approving allows and to deny what you didn't ask for yourself.
@fctl-sh
fctl-sh Bot requested a review from kalaspuff as a code owner October 30, 2026 21:46
@kalaspuff
kalaspuff added this pull request to stack #361 October 30, 2026 21:59
@kalaspuff
kalaspuff merged commit 15b174c into main Oct 30, 2026
11 checks passed
@kalaspuff
kalaspuff deleted the fctl/573-device-warning 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
…360)

## Summary

JSON bodies, GitHub's and Linear's webhook deliveries, avatars, and the
files people attach to sessions or agents publish were read whole before
anything checked their size, and refused early only when the client said
how large the body was (`Content-Length`). A client that doesn't,
sending `Transfer-Encoding: chunked`, could send a body without end,
without signing in: on the standalone server, whose proxy passes bodies
on chunked and caps none, a `curl -T /dev/zero` to the sign-in endpoint
would have the Worker keep it all in memory until the instance fell
over, with every session socket on it. On Cloudflare the platform's own
limits bound it. Bodies are now read as they come (`http::read_upto`)
and refused once they pass their limit, with the same messages as
before.

Verified in `cargo xtask dev`: 200 MB of zeros sent chunked to `POST
/api/v1/auth/login` is refused with "The request body is too large"
after about 9 MB had gone out, within a second, and signing in,
uploading an avatar and an attachment, an agent's published screenshot
and a Linear delegation through the stand-in's webhook all work as
before. `cargo xtask check --quick` passes.

## Stacked pull requests

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

- #359

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

<!-- FCTL_REVIEW_SUMMARY -->
---

> [!NOTE]
> **Medium risk.** It changes how every JSON body, webhook delivery,
avatar and file upload is read, which all requests go through, though
only the reading changes and not what is accepted.
>
> **What it does.** Adds `http::read_upto`, which reads a request body
chunk by chunk and stops once it holds more than the limit, and uses it
for JSON bodies, GitHub and Linear webhooks, avatars, and the file
uploads and published screenshots in the files object. Before, a body
was read whole before its size was checked, so a chunked request without
`Content-Length` could use up the Worker's memory on the standalone
server. The callers' own size checks and messages stay as they were, and
`docs/security.md` describes the behavior.
>
> <sub>[fctl
review](https://app.fctl.sh/s/ses_01M67D4Q8YHM4BW3MWK538PWGS)'s first
look at
[`11b23e0`](11b23e0)
· 6 files, +36 −8 · [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