Skip to content

Bind the bb server to loopback by default - #1125

Merged
SawyerHood merged 3 commits into
get-bb:mainfrom
ben-vargas:feat/loopback-bind-host
Aug 8, 2026
Merged

Bind the bb server to loopback by default#1125
SawyerHood merged 3 commits into
get-bb:mainfrom
ben-vargas:feat/loopback-bind-host

Conversation

@ben-vargas

@ben-vargas ben-vargas commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 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

Root cause

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 host that could route packets to the machine 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. 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>: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.

Validation

  • 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 #1124

if (value.length === 0) {
throw new Error("Env value must not be empty. Use unset to remove it.");
}
if (key === "BB_SERVER_BIND_HOST") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 configBB_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.

ben-vargas added a commit to ben-vargas/ai-bb that referenced this pull request Aug 7, 2026
`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>
@charpeni

charpeni commented Aug 7, 2026

Copy link
Copy Markdown

@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).

@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 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.

Comment thread packages/bb-app/src/launcher.ts Outdated
"BB_APP_SURFACE",
"BB_APP_URL",
"BB_DATA_DIR",
"BB_DEV_APP_HOST",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 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.

@SawyerHood

Copy link
Copy Markdown
Collaborator

@charpeni ty for the ping going to merge this and do a release ASAP.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 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

  1. Medium — Internal source URLs can select IPv6. The server now listens only on 127.0.0.1. Two source paths still create localhost server URLs. A host can map localhost only to ::1. Vite and the host daemon then cannot reach the server. Use BB_LOOPBACK_HOST in vite-dev.ts and runtime.ts.

  2. Low — The launcher gives false Vite restart advice. bb-app env stores BB_DEV_APP_HOST outside the source repository. A bb-app restart 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.

ben-vargas and others added 3 commits August 8, 2026 00:28
- 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>
@SawyerHood
SawyerHood force-pushed the feat/loopback-bind-host branch from 7fb0648 to 9c5a9e6 Compare August 8, 2026 00:31
@SawyerHood
SawyerHood merged commit 7881e63 into get-bb:main Aug 8, 2026
9 checks passed
@SawyerHood SawyerHood mentioned this pull request Aug 8, 2026
SawyerHood added a commit that referenced this pull request Aug 8, 2026
## 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>
@ben-vargas
ben-vargas deleted the feat/loopback-bind-host branch August 8, 2026 02:05
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.

Unauthenticated RCE: server binds all interfaces and /api/v1 has no auth

3 participants