Bind the bb server to loopback by default - #1125
Conversation
| if (value.length === 0) { | ||
| throw new Error("Env value must not be empty. Use unset to remove it."); | ||
| } | ||
| if (key === "BB_SERVER_BIND_HOST") { |
There was a problem hiding this comment.
BB_SERVER_BIND_HOST is startup-only, but this calls the generic reload endpoint and prints Reloaded running bb server config. If a wildcard-bound server is running and the user unsets this value, the existing listener remains bound to 0.0.0.0. Even a supervised server-child restart reuses the launcher’s captured serverEnv, so only restarting the whole bb-app launcher applies the safer binding.
This can leave users believing external access has been closed while the unauthenticated API remains exposed.
There was a problem hiding this comment.
Good catch — addressed in 1fe5550f6.
You're right that the "Reloaded running bb server config." message implied the bind host took effect when the running listener keeps its previous bind until a full launcher restart. While digging I found this wasn't new: the same misleading confirmation already applied to every startup-only key accepted by bb-app env/bb-app config — BB_SERVER_PORT, BB_HOST_DAEMON_PORT, BB_LOG_LEVEL, BB_TELEMETRY, BB_DATA_DIR, feature flags, and others have never been re-appliable through the reload endpoint (it only applies BB_APP_URL, BB_INFERENCE, BB_TRANSCRIPTION, and OPENAI_API_KEY).
So rather than special-case the bind host, this now covers the complete set. bb-app env set/unset and bb-app config set/unset on a startup-only key still run the reload (for any other pending changes) but print an explicit notice that the running process keeps its current value and a full bb-app stop && bb-app start (or desktop app restart) is required. For BB_SERVER_BIND_HOST specifically it also warns that a previous 0.0.0.0 listener remains exposed until that restart. bb-app config refresh now notes any startup-only keys present in the config files, and the docs, CLI help text, and guide template describe the behavior.
I went back and forth on the scope broadening — it's a pre-existing flaw and arguably a separate PR — but in the context of this change, where the whole point is letting users close network exposure, a success message that says the opposite of reality seemed worth fixing completely rather than for one key. Happy to split if you'd prefer.
One residual I left alone: daemon-consumed env keys (BB_HOST_NAME, BB_HOST_ID, etc.) still print the generic reload message even though the daemon process never sees the change without a restart. The help text scopes the list to "server and launcher keys" to stay honest; flagging it here as a possible follow-up rather than growing this PR further.
`bb-app env set/unset` and `bb-app config set/unset` always print "Reloaded running bb server config." after POSTing to the reload endpoint, but the reload only applies a small allowlist of runtime keys. Startup-only keys (ports, data dir, telemetry, feature flags, and the new BB_SERVER_BIND_HOST) keep their previous values until a full launcher restart, so the success message is misleading — and for BB_SERVER_BIND_HOST it can leave a user believing a 0.0.0.0 listener was closed when it is still exposed. Print an explicit restart notice for startup-only keys instead, keep the reload for other pending changes, note configured startup-only keys after `config refresh`, and cover the behavior with tests and doc/help updates. Addresses review feedback on get-bb#1125. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@SawyerHood @ymichael Sorry for the direct ping, just bringing this to your attention since this is a security issue where bb is unexpectedly exposed through your whole LAN (or more). |
|
🚨 SLOP COP 🚨 · I am the SlopCop. I am reviewing this pull request for security, code quality, architecture, duplication, performance, and end-to-end behavior. |
| } | ||
|
|
||
| return "0.0.0.0"; | ||
| return BB_LOOPBACK_HOST; |
There was a problem hiding this comment.
🚨 slopcop/review — Use the exact IPv4 loopback address for internal targets
This change makes the server listen only on 127.0.0.1, but serverHttpOrigin below and resolveDevInstanceConfig() still create localhost URLs. On a host where localhost resolves only to ::1, Vite proxy requests and the source host daemon cannot reach the IPv4-only server. Please build both internal URLs from BB_LOOPBACK_HOST and update their tests.
| "BB_APP_SURFACE", | ||
| "BB_APP_URL", | ||
| "BB_DATA_DIR", | ||
| "BB_DEV_APP_HOST", |
There was a problem hiding this comment.
🚨 slopcop/review — Do not describe the Vite host as managed launcher state
bb-app env set writes this value to ~/.bb/env.json, but bb-app starts only the server and host daemon. Source Vite reads the repository dotenv cascade or its process environment. A bb-app restart cannot apply this saved value to Vite. Please remove BB_DEV_APP_HOST from this set and the new managed-env help, then keep it documented only as a source-development environment variable.
|
@charpeni ty for the ping going to merge this and do a release ASAP. |
SawyerHood
left a comment
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
ELI5
bb is like a house with a network door. Before this PR, the door opened to the local network. Now it opens only inside the computer by default. This change makes bb safer.
Findings
-
Medium — Internal source URLs can select IPv6. The server now listens only on
127.0.0.1. Two source paths still createlocalhostserver URLs. A host can maplocalhostonly to::1. Vite and the host daemon then cannot reach the server. UseBB_LOOPBACK_HOSTinvite-dev.tsandruntime.ts. -
Low — The launcher gives false Vite restart advice.
bb-app envstoresBB_DEV_APP_HOSToutside the source repository. Abb-apprestart does not start Vite. Thus, it cannot apply that stored value. Remove this key from the managed startup set and help text.
Review result
I found no direct security defect in the new bind logic. The explicit wildcard option remains dangerous, and the new warning explains that risk.
I found no performance regression. The project already has BB_LOOPBACK_HOST, but two stale localhost values split the loopback policy.
The Turbo type checks passed for @bb/config, bb-app, and @bb/server. The tests passed for all 96 config tests and all 61 launcher tests.
The server run passed 1,337 of 1,338 tests. The last test passed all 10 cases after I removed a global bb-app path from the review shell.
The full source stack started on IPv4 loopback. The browser loaded the app without console errors. The health route returned 200.
Requests through the machine LAN address failed for both Vite and the server. This confirms the new default network boundary.
Please address the two inline findings. The main safety change works as intended.
- add `BB_SERVER_BIND_HOST` (default `127.0.0.1`, accepts `127.0.0.1` or
`0.0.0.0`) and pass it as the `serve()` hostname
- add the matching `--server-bind-host` launcher flag, validated before
the server child is spawned
- let an explicit flag win over a persisted `env.json` value, and reject
an invalid value at `bb-app env set` time so it cannot be persisted
- log a security warning at startup when the bind host is not loopback
- default the source-dev Vite listener to loopback and allow `.ts.net`
hosts so the Tailscale Serve dev flow works
`apps/server/src/start-server.ts` called `serve({ port, fetch })` with no
hostname, so Node bound `::`/`0.0.0.0` and the server was reachable on
every network interface the machine joined.
Authentication middleware covers only `/internal/*`; `/api/v1` has none.
`browserRequestProblem` is an Origin/CSRF control that deliberately
passes requests without an `Origin` header, so it does not constrain
non-browser clients.
Together these meant any peer on an untrusted LAN could reach
`POST /api/v1/terminals` with `start:{mode:"command"}`, which the daemon
executes through the user's shell, and `GET /api/v1/files/read`, which
accepts an unrestricted absolute path.
The host daemon local API and the machine-auth proxy already bound
loopback deliberately; only the main server lacked a bind host.
A laptop that joins untrusted networks no longer exposes the bb API.
The desktop app, plugin SDK, bb CLI, agents, and the colocated host
daemon all reach the server over loopback and are unaffected.
Remote host daemons and browsers that targeted a direct LAN or tailnet
`http://<host>:38886` URL must migrate to `tailscale serve` in front of
loopback, to the authenticated bb connect route, or explicitly opt back
in with `--server-bind-host 0.0.0.0`. Migration guidance is documented in
`docs/multiple-devices.md`; establish the new route before restarting an
upgraded server, since an unreachable daemon cannot self-update.
`HOST_DAEMON_PROTOCOL_VERSION` is unchanged: no wire format changed, and
a version bump cannot help a daemon that can no longer reach the server.
- `pnpm exec turbo run typecheck --filter=@bb/config --filter=@bb/server
--filter=bb-app --filter=@bb/app --filter=@bb/templates`
- `pnpm exec turbo run test --filter=@bb/config --filter=bb-app
--filter=@bb/server --force`
- verified on a running instance: `lsof` reports `TCP 127.0.0.1:38886
(LISTEN)`, loopback `/health` returns `{"ok":true}`, and the same
request to the host's LAN address is refused
Fixes get-bb#1124
`bb-app env set/unset` and `bb-app config set/unset` always print "Reloaded running bb server config." after POSTing to the reload endpoint, but the reload only applies a small allowlist of runtime keys. Startup-only keys (ports, data dir, telemetry, feature flags, and the new BB_SERVER_BIND_HOST) keep their previous values until a full launcher restart, so the success message is misleading — and for BB_SERVER_BIND_HOST it can leave a user believing a 0.0.0.0 listener was closed when it is still exposed. Print an explicit restart notice for startup-only keys instead, keep the reload for other pending changes, note configured startup-only keys after `config refresh`, and cover the behavior with tests and doc/help updates. Addresses review feedback on get-bb#1125. Co-authored-by: Cursor <cursoragent@cursor.com>
7fb0648 to
9c5a9e6
Compare
## Summary Prepares the 0.36.0 release: version lockstep bump and release notes. - bump `bb-app` and `@bb/desktop` to `0.36.0` via `scripts/bump-version.mjs` - add the `0.36.0` section to `CHANGELOG.md` - add the `0.36.0` entry to `RELEASE_META` in `apps/web/src/landing/changelog.ts` ## Release notes Headline is "Fixes and improvements". The notes lead with the loopback bind default (#1125), because it is the one change that needs user action before the upgrade. That section links to `docs/multiple-devices.md` for the bb connect and Tailscale Serve setup steps rather than repeating them. Two risks are called out explicitly in the notes: - The host daemon protocol moved 69 → 82 in this range. A remote daemon that reaches the server at a direct `host:38886` address loses its route and cannot self-update, so the route must move first. - A thread that names no model now resolves one from the provider catalog on the target host (#1002), and fails to start when that host cannot list models. A Thanks section credits the external contributors in this range: @ben-vargas, @Diffuzmetall, @kschrader, and @toasterman234. Commands stay as inline code spans throughout. The `/changelog` parser handles only paragraphs and bullets, and its inline renderer supports only `code` and `strong`, so a fenced block or a Markdown link would render as literal text on the marketing site. ## Validation - `node .github/workflows/check-version-lockstep.mjs` — bb-app=0.36.0 @bb/desktop=0.36.0 - `pnpm exec turbo run typecheck test --filter=@bb/config --filter=@bb/server --filter=bb-app --force` — 1367 tests passed - `pnpm exec turbo run smoke:tarball --filter=bb-app --force` — passed - `git diff --check` — clean Unrelated pre-existing issue found while validating: `apps/server/test/internal/internal-skill-trees.test.ts` asserts a file mode of `0o644`, which depends on the process umask. It fails under `umask 002` and passes under `umask 022`. CI is unaffected. ## Follow-ups - Redeploy `@bb/web` after this lands so `/changelog` shows 0.36.0. - `publish-bb-app.yml` and `build-desktop.yml` still need to run from `main` at this commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
BB_SERVER_BIND_HOST(default127.0.0.1, accepts127.0.0.1or0.0.0.0) and pass it as theserve()hostname--server-bind-hostlauncher flag, validated before the server child is spawnedenv.jsonvalue, and reject an invalid value atbb-app env settime so it cannot be persisted.ts.nethosts so the Tailscale Serve dev flow worksRoot cause
apps/server/src/start-server.tscalledserve({ port, fetch })with no hostname, so Node bound::/0.0.0.0and the server was reachable on every network interface the machine joined.Authentication middleware covers only
/internal/*;/api/v1has none.browserRequestProblemis an Origin/CSRF control that deliberately passes requests without anOriginheader, so it does not constrain non-browser clients.Together these meant any host that could route packets to the machine could reach
POST /api/v1/terminalswithstart:{mode:"command"}, which the daemon executes through the user's shell, andGET /api/v1/files/read, which accepts an unrestricted absolute path. That includes LAN, VPN, and tailnet peers — and, because the::bind also covers public IPv4 and globally routable IPv6, the open internet wherever no firewall happens to stand in front.The host daemon local API and the machine-auth proxy already bound loopback deliberately; only the main server lacked a bind host.
User impact
This isn't a LAN-only concern: deployments with a routable address and no host firewall (cloud VMs, VPS hosts, machines with global IPv6) were exposed to the internet. That, plus the public PoC in #1124, is why this should ship as a security release rather than waiting for a normal cycle.
A laptop that joins untrusted networks no longer exposes the bb API. The desktop app, plugin SDK, bb CLI, agents, and the colocated host daemon all reach the server over loopback and are unaffected.
Remote host daemons and browsers that targeted a direct LAN or tailnet
http://<host>:38886URL must migrate totailscale servein front of loopback, to the authenticated bb connect route, or explicitly opt back in with--server-bind-host 0.0.0.0. Migration guidance is documented indocs/multiple-devices.md; establish the new route before restarting an upgraded server, since an unreachable daemon cannot self-update.HOST_DAEMON_PROTOCOL_VERSIONis unchanged: no wire format changed, and a version bump cannot help a daemon that can no longer reach the server.Validation
pnpm exec turbo run typecheck --filter=@bb/config --filter=@bb/server --filter=bb-app --filter=@bb/app --filter=@bb/templatespnpm exec turbo run test --filter=@bb/config --filter=bb-app --filter=@bb/server --forcelsofreportsTCP 127.0.0.1:38886 (LISTEN), loopback/healthreturns{"ok":true}, and the same request to the host's LAN address is refusedFixes #1124