Skip to content

feat(auth): connect an assistant without a terminal - #1832

Merged
frahlg merged 3 commits into
masterfrom
claude/simple-assistant-connect
Oct 25, 2026
Merged

frahlg merged 3 commits into
masterfrom
claude/simple-assistant-connect

Conversation

@frahlg

@frahlg frahlg commented Oct 25, 2026

Copy link
Copy Markdown
Member

Problem

On 25 October 2026 a beta user tried to connect ChatGPT and gave up. Settings → Access asked for a house password first and sent him to System → LAN password. There, pressing Enable from his browser returned:

enable the house password from loopback inside the process. A published Docker port is not loopback — on Docker Desktop use docker compose -f docker-compose.macos.yml exec ftw, then curl http://127.0.0.1:8080

He runs FTW on Docker Desktop, so his own browser never counts as "loopback". He could not connect an assistant without a terminal.

What Fredrik decided (25 October 2026)

  • The address a request comes from proves nothing. The house password, the Home Assistant login or a paired phone prove the owner.
  • Any browser that reaches the box may set the first house password. While the lock is off that browser is already an owner: it may change any setting and pair a phone as an owner (operator decision 29 August 2026). Setting the password gives it nothing new and closes the door behind it.
  • New boxes ask for a password during setup. Existing boxes are asked, not forced.
  • Any password will do. A short one gets a warning, not a refusal.
  • A forgotten password, or one someone else set first, is reset on the box: ftw password reset.

What changes

Core

  • POST /api/auth/password no longer refuses the first enable from a LAN or Docker address. The app session still cannot turn it on. Choosing the password signs that browser in (signed_in in the answer), so the page goes straight on.
  • The house password now counts on the box itself too (resolveLANSecret reads it on loopback). Loopback is still an owner for settings without it, so ftw commands keep working. Letting an assistant in needs a signed-in browser from every address. Drivers cannot reach the API (drivers/selfguard.go), and app-session requests never present the house secret.
  • A request let in while the lock was still off can no longer replace a password set meanwhile; every change writes an audit line.
  • No minimum length; 512 characters at most.
  • The first-run wizard's settings save takes house_password and starts the box locked. Home Assistant ingress ignores it. A script that sends none leaves the lock off, as before.
  • ftw password reset turns the lock off through the local API and signs every browser out. On Docker the box's own binary runs it: docker compose exec ftw /app/ftw password reset.

Box page

  • Settings → Access asks for the password right above the assistant's code. A box with none gets "choose a house password" (twice). A box with one gets "sign in". No detour through System.
  • Settings → System calls it the house password and says what it does in plain words. The loopback, Bearer, curl and Docker text is gone; expert details sit behind a docs link.
  • The wizard's last step asks for the house password on a new box (not under Home Assistant, not on a re-run).
  • The sign-in dialog says "That is not the house password" rather than invalid password.

Docs: docs/operations.md has a House password section with the reset command; docs/agents.md states the new rule.

Security, in plain words

Before After
With the lock off, any device on the Wi-Fi could pair a phone as owner and let an assistant in from it, but the honest owner's browser could not Any browser that reaches the box may set the first password; the first one wins and then everyone else needs it
A visitor could not set the password first A visitor could set it first; the owner sees Settings locked at once and runs ftw password reset on the box
The password never counted on the box itself, so a browser there could not let an assistant in It counts everywhere; a browser on the box signs in like any other. Loopback stays an owner for settings, as before
At least 10 characters Any length; a short one is the owner's choice and gets a warning

Nothing changes for Home Assistant (its login already counts) or for the FTW app (approves with its own signed key).

Evidence

Browser test against a local Core built from this branch, with a local stand-in for FTW Cloud's code lookup that signs real offers with a key the build pins (-ldflags -X cloudlink.OfferKeys, cloudlink.Endpoint; nothing reached production FTW Cloud).

  • Docker-port case: the browser on localhost, the box seeing a private LAN address (as Docker Desktop's bridge does). Before (master): the Access block and the Docker error above, reproduced word for word. After: choose a password → signed in → type the code → review → "ChatGPT" can reach this home.
  • On the box itself (127.0.0.1, a native Mac install): "Sign in" → signed in → code → access given. Before, this case told the owner to use another device.
  • Short password: 1234 shows "Short passwords are easy to guess. You can still use this one." and is accepted.
  • Reset: /app/ftw password reset --url http://127.0.0.1:… turns the lock off; against the LAN address with the lock on it refuses with "run this on the box itself".
  • New box: the wizard's last step asks for the password; after Start FTW the box answers lan_auth: true, configured: true, and config.yaml holds no password.
  • Home Assistant: not run in a browser (ingress only admits 172.30.32.2). Covered by TestBootstrapWizardSavesTheHousePassword/through_Home_Assistant, TestHomeAssistantBootstrapNeedsNoSecondPassword and the readiness tests.

Screenshots, on branch claude/simple-assistant-connect-shots (not for merging).

Before (master) After
Access asks for the password and sends you to System Access asks for a password right above the code, with a short-password warning
Enable from a Docker port refused with a docker/curl message Signed in, the code field is open
The offer from the stand-in, reviewed on the box
ChatGPT can reach this home
On the box itself: sign in
On the box itself: signed in, assistant listed
System: House password in plain words
Wizard last step asks for the house password

Tests: make verify passed on the first commit. After the review fixes and a rebase on 8e33174e, go test for internal/api, cmd/ftw, internal/ftwcli and internal/state passes, and npm test passes 884/884. New tests cover: the first password from a LAN, Docker-bridge, IPv4 and IPv6 loopback address; a second device refused; a short password accepted; the race between two first enables; an unproved change on the box earning no sign-in; a cookie on the box widening; an app session never presenting the house password; the wizard saving a verifiable hash (and Home Assistant ignoring it); and ftw password reset.

Owner approval

The changeset is minor (a visible change to how the box is claimed and how assistants are let in). Fredrik approved the direction in the session; please confirm the minor here.

Independent review

A separate review (Sonnet) read the diff. It found, and this PR now fixes:

  • Race on the first enable: a request Authenticate let in while the lock was off could replace the password the first browser had just chosen. The handler now refuses it with 401 once the lock is on.
  • Sign-in without proof: the box itself could replace the password and get a cookie. Now only a direct local browser that chose the first password, or proved the current one, is signed in. The box may still replace an unproved password (it may change any setting), and the docs now say plainly that loopback is not a wall against programs on the box: drivers cannot reach the API (drivers/selfguard.go), and anything else there can change everything already.
  • "Choose" on unknown status: when the box's status could not be read, Access offered "choose a password", which a signed-in owner could use to overwrite a real one. It now says it could not check.
  • Native boxes with an old ftw command: ftw moves with install.sh --refresh, not ftw update. The docs and changeset give Core's own binary as the fallback.
  • An audit line for every password change, a character (not byte) limit, plain fallback text in the sign-in dialog, and a stale comment in docker-compose.macos.yml.

Left as is, on purpose: a wrong Bearer on loopback now counts toward the shared five-guess lockout, as it does from the LAN.

Not covered

  • Home Assistant ingress in a real browser (see above).
  • A real Docker Desktop container; OrbStack was not running. The Docker case was exercised as the address it produces (a private non-loopback peer), in the browser and in TestTheFirstHousePasswordFromAnyAddress/172.17.0.1.
  • A real assistant through production FTW Cloud.

🤖 Generated with Claude Code

frahlg and others added 2 commits October 25, 2026 10:34
The house password, not the address, proves the owner. Any browser that
reaches the box may set the first one, also through a Docker port or on
the box itself, and choosing it signs that browser in. Settings → Access
asks for it right above the assistant's code, and the setup wizard asks
for it on a new box. Any non-empty password is accepted; a short one gets
a warning. A forgotten password is turned off on the box with
`ftw password reset` (`/app/ftw password reset` in Docker).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From an independent review of the previous commit:

- A request Authenticate let in while the lock was still off can no
  longer replace the password the first browser just chose.
- The sign-in cookie goes only to a direct local browser that chose the
  first password or proved the current one. The box itself may still
  replace a password it never proved, as it may change any setting, but
  that earns no sign-in.
- Every change of the house password writes an audit line with the
  address.
- Access no longer offers "choose a password" when the box's status
  could not be read, and a choose that meets a password set meanwhile
  turns into a sign-in.
- The password limit counts characters, not bytes.
- The docs say how to reset on a native box whose ftw command predates
  this release, and say plainly that loopback is not a wall against
  programs on the box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-25T09:39:01.263338Z 76a5662 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@frahlg
frahlg merged commit c56fccf into master Oct 25, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant