beeperbox 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/package.json +1 -1
  3. package/server.js +20 -6
package/README.md CHANGED
@@ -35,10 +35,11 @@ BEEPER_TOKEN=your-token-here npx beeperbox --stdio
35
35
  | `MCP_PORT` | MCP HTTP port | `23375` |
36
36
  | `MCP_AUTH_TOKEN` | Optional bearer guard on the MCP endpoint | unset (open on loopback) |
37
37
  | `MCP_ALLOWED_HOSTS` | Host/Origin allowlist | `localhost,127.0.0.1,::1` |
38
+ | `MCP_BIND_ADDR` | Interface the MCP server binds | `127.0.0.1` (loopback) |
38
39
 
39
40
  ## Security
40
41
 
41
- The server binds `0.0.0.0` but is meant to stay loopback-only: it's safe on `127.0.0.1` with no auth. To expose it beyond your machine, set `MCP_AUTH_TOKEN` **and** `MCP_ALLOWED_HOSTS`, and put it behind a tunnel (SSH / Tailscale / TLS reverse proxy) — never raw on a public interface.
42
+ The server binds **loopback only** (`127.0.0.1`) by default, so it's safe with no auth — only processes on your own machine can reach it. Don't just set it to `0.0.0.0`: a same-network attacker can spoof the `Host` header past the allowlist and reach the full tool surface (read every message, send across every network) unauthenticated. To expose it deliberately, set `MCP_BIND_ADDR=0.0.0.0` **and** `MCP_AUTH_TOKEN`, and put it behind a tunnel (SSH / Tailscale / TLS reverse proxy) — never raw on a public interface.
42
43
 
43
44
  ## Supervision
44
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeperbox",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Lite mode for beeperbox — the opinionated MCP verb server for Beeper Desktop, run standalone against a Beeper you already have open (no Docker, no Electron). The full headless-Beeper-in-Docker build lives at github.com/hamr0/beeperbox.",
5
5
  "bin": {
6
6
  "beeperbox": "server.js"
package/server.js CHANGED
@@ -22,6 +22,18 @@ const PORT = parseInt(process.env.MCP_PORT || '23375', 10);
22
22
  const BEEPER_API = process.env.BEEPER_API || 'http://127.0.0.1:23373';
23
23
  const BEEPER_TOKEN = process.env.BEEPER_TOKEN || '';
24
24
 
25
+ // Bind address. Defaults to LOOPBACK (127.0.0.1) — the safe default for lite
26
+ // mode (`npx beeperbox` on a laptop), where the process listens directly on the
27
+ // host with no Docker loopback publish in front of it. Binding 0.0.0.0 there
28
+ // would put the full tool surface (read every message, send across every
29
+ // network) on the LAN, reachable unauthenticated by anyone who can spoof the
30
+ // `Host` header — a non-browser attacker trivially can. The container needs
31
+ // 0.0.0.0 (a Docker published port can't reach a loopback-bound process) and
32
+ // sets MCP_BIND_ADDR=0.0.0.0 via the image ENV; there the loopback *publish*
33
+ // (`127.0.0.1:23375:23375`), not the bind, is the boundary. To expose lite mode
34
+ // deliberately, set MCP_BIND_ADDR=0.0.0.0 AND MCP_AUTH_TOKEN AND a tunnel.
35
+ const BIND_ADDR = process.env.MCP_BIND_ADDR || '127.0.0.1';
36
+
25
37
  // ─── http transport hardening ─────────────────────────────────────
26
38
  // The HTTP transport is the network-exposed surface (stdio is local-only).
27
39
  // Three guards, all configurable so they don't break the documented
@@ -34,10 +46,11 @@ const BEEPER_TOKEN = process.env.BEEPER_TOKEN || '';
34
46
  // Defaults to loopback; set it for reverse proxies.
35
47
  // MCP_MAX_BODY — max request body bytes (default 1 MiB) so a large
36
48
  // POST can't grow the in-memory buffer unbounded.
37
- // The listener stays bound to 0.0.0.0 ON PURPOSE: a Docker published port
38
- // is unreachable if the in-container process binds 127.0.0.1, so loopback
39
- // binding here would break `127.0.0.1:23375:23375`. Auth + Host/Origin
40
- // checks are the defense, not the bind address.
49
+ // The bind address is MCP_BIND_ADDR (see above): loopback by default (safe for
50
+ // lite mode), 0.0.0.0 in the container (where the loopback PUBLISH is the
51
+ // boundary). In the container, Auth + Host/Origin are the in-container defense;
52
+ // in lite mode the loopback BIND is, because a non-browser client can spoof the
53
+ // Host header past the allowlist.
41
54
  const MCP_AUTH_TOKEN = process.env.MCP_AUTH_TOKEN || '';
42
55
  const MCP_ALLOWED_HOSTS = new Set(
43
56
  (process.env.MCP_ALLOWED_HOSTS || 'localhost,127.0.0.1,::1,[::1]')
@@ -1217,8 +1230,8 @@ function startHttpTransport() {
1217
1230
  });
1218
1231
  });
1219
1232
 
1220
- server.listen(PORT, '0.0.0.0', () => {
1221
- console.log(`[beeperbox-mcp] listening on http://0.0.0.0:${PORT}`);
1233
+ server.listen(PORT, BIND_ADDR, () => {
1234
+ console.log(`[beeperbox-mcp] listening on http://${BIND_ADDR}:${PORT}${BIND_ADDR === '0.0.0.0' ? ' (all interfaces — rely on a loopback publish or set MCP_AUTH_TOKEN)' : ' (loopback only)'}`);
1222
1235
  console.log(`[beeperbox-mcp] beeper api: ${BEEPER_API}`);
1223
1236
  console.log(`[beeperbox-mcp] beeper token: ${BEEPER_TOKEN ? 'set' : 'NOT SET (set BEEPER_TOKEN env var)'}`);
1224
1237
  console.log(`[beeperbox-mcp] http auth: ${MCP_AUTH_TOKEN ? 'required (MCP_AUTH_TOKEN set)' : 'OPEN — set MCP_AUTH_TOKEN to require a bearer token'}`);
@@ -1288,6 +1301,7 @@ module.exports = {
1288
1301
  // version + tool names here is what guarantees the two builds can't drift.
1289
1302
  VERSION,
1290
1303
  TOOL_NAMES: TOOLS.map((t) => t.name),
1304
+ BIND_ADDR,
1291
1305
  ledgerPath,
1292
1306
  encodeCursor,
1293
1307
  decodeCursor,