mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
fix(api,auth)!: bind loopback by default, and let a container ask for more
Both servers bound `0.0.0.0`. That was harmless while the only deployment was docker-compose.yml, which publishes these ports on 127.0.0.1 and makes the container's own bind irrelevant. As ordinary processes on a rented machine there is no such wrapper, and `0.0.0.0` is a listener on the internet — in front of an issuer that sets cookies without `Secure` and mints sign-in codes, because it expects something else to be terminating TLS. So the default is `127.0.0.1` and `HOST` is there for the deployment that genuinely needs every interface. compose now sets `HOST: 0.0.0.0` explicitly, which is not a workaround: inside a container, binding loopback is what would make the published port unreachable. The right answer differs by deployment, which is why it is a variable rather than a constant. Both now log the address they bound, not the one they hoped for. BREAKING CHANGE: api and auth no longer listen on every interface by default. A deployment that relied on that must set HOST=0.0.0.0.
This commit is contained in:
@@ -14,9 +14,23 @@ import handler, { type ApiEnv } from './index.js';
|
|||||||
|
|
||||||
const port = Number(process.env.PORT ?? 3000);
|
const port = Number(process.env.PORT ?? 3000);
|
||||||
|
|
||||||
|
// Loopback by default, and that is a security setting rather than a
|
||||||
|
// convenience. Under a Cloudflare Tunnel nothing reaches this process except
|
||||||
|
// `cloudflared` on the same machine, and this address is half of what makes
|
||||||
|
// that true -- the machine's firewall is the other half.
|
||||||
|
//
|
||||||
|
// It was `0.0.0.0` until 2026-09-16, which was harmless while the only
|
||||||
|
// deployment was `docker-compose.yml`, because that publishes these ports on
|
||||||
|
// `127.0.0.1` and the container's own bind never mattered. As an ordinary
|
||||||
|
// process there is no such wrapper: `0.0.0.0` is a public listener.
|
||||||
|
//
|
||||||
|
// `HOST` exists for the deployment that genuinely wants one -- a container,
|
||||||
|
// where binding loopback would make the port unpublishable.
|
||||||
|
const hostname = process.env.HOST ?? '127.0.0.1';
|
||||||
|
|
||||||
Bun.serve({
|
Bun.serve({
|
||||||
port,
|
port,
|
||||||
hostname: '0.0.0.0',
|
hostname,
|
||||||
// A Worker runtime hands the handler a context whose `waitUntil` keeps the
|
// A Worker runtime hands the handler a context whose `waitUntil` keeps the
|
||||||
// invocation alive past the response. A process does not need convincing to
|
// invocation alive past the response. A process does not need convincing to
|
||||||
// stay alive, so the equivalent is to let the promise run — with a catch,
|
// stay alive, so the equivalent is to let the promise run — with a catch,
|
||||||
@@ -37,4 +51,4 @@ Bun.serve({
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[api] listening on http://0.0.0.0:${port}`);
|
console.log(`[api] listening on http://${hostname}:${port}`);
|
||||||
|
|||||||
@@ -16,9 +16,25 @@ import handler from './index.js';
|
|||||||
|
|
||||||
const port = Number(process.env.PORT ?? 1337);
|
const port = Number(process.env.PORT ?? 1337);
|
||||||
|
|
||||||
|
// Loopback by default, and that is a security setting rather than a
|
||||||
|
// convenience. Under a Cloudflare Tunnel nothing reaches this process except
|
||||||
|
// `cloudflared` on the same machine, and this address is half of what makes
|
||||||
|
// that true -- the machine's firewall is the other half.
|
||||||
|
//
|
||||||
|
// It was `0.0.0.0` until 2026-09-16, which was harmless while the only
|
||||||
|
// deployment was `docker-compose.yml`, because that publishes these ports on
|
||||||
|
// `127.0.0.1` and the container's own bind never mattered. As an ordinary
|
||||||
|
// process there is no such wrapper: `0.0.0.0` is a public listener, and this one
|
||||||
|
// issues sign-in codes and sets cookies without `Secure`, because it expects
|
||||||
|
// something in front of it to be the thing terminating TLS.
|
||||||
|
//
|
||||||
|
// `HOST` exists for the deployment that genuinely wants one -- a container,
|
||||||
|
// where binding loopback would make the port unpublishable.
|
||||||
|
const hostname = process.env.HOST ?? '127.0.0.1';
|
||||||
|
|
||||||
Bun.serve({
|
Bun.serve({
|
||||||
port,
|
port,
|
||||||
hostname: '0.0.0.0',
|
hostname,
|
||||||
// A Worker runtime hands the handler a context whose `waitUntil` keeps the
|
// A Worker runtime hands the handler a context whose `waitUntil` keeps the
|
||||||
// invocation alive past the response. A process does not need convincing to
|
// invocation alive past the response. A process does not need convincing to
|
||||||
// stay alive, so the equivalent is to let the promise run — with a catch,
|
// stay alive, so the equivalent is to let the promise run — with a catch,
|
||||||
@@ -39,4 +55,4 @@ Bun.serve({
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[auth] listening on http://0.0.0.0:${port}`);
|
console.log(`[auth] listening on http://${hostname}:${port}`);
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ services:
|
|||||||
# unconfigured and this unset, the issuer refuses to send rather than
|
# unconfigured and this unset, the issuer refuses to send rather than
|
||||||
# logging codes, which is the failure a self-hoster should get.
|
# logging codes, which is the failure a self-hoster should get.
|
||||||
EMAIL_DEV_LOG: ${EMAIL_DEV_LOG:-}
|
EMAIL_DEV_LOG: ${EMAIL_DEV_LOG:-}
|
||||||
|
# Every interface *inside the container*, which is what makes the
|
||||||
|
# loopback publication below reachable. The processes default to
|
||||||
|
# 127.0.0.1 because a bare process on a host has no such wrapper and a
|
||||||
|
# public bind there is a listener on the internet; a container's own
|
||||||
|
# loopback is not, and binding it would make the port unpublishable.
|
||||||
|
HOST: 0.0.0.0
|
||||||
# Loopback. This listener speaks plain HTTP and sets no `Secure` on the
|
# Loopback. This listener speaks plain HTTP and sets no `Secure` on the
|
||||||
# cookies it issues, because it expects to be behind something that
|
# cookies it issues, because it expects to be behind something that
|
||||||
# terminates TLS. Published on every interface it would be a way to reach
|
# terminates TLS. Published on every interface it would be a way to reach
|
||||||
@@ -101,6 +107,12 @@ services:
|
|||||||
# A shared secret that turns any request carrying it into an operator.
|
# A shared secret that turns any request carrying it into an operator.
|
||||||
# Required, with no default, for that reason.
|
# Required, with no default, for that reason.
|
||||||
ADMIN_SHARED_SECRET: ${ADMIN_SHARED_SECRET:?set ADMIN_SHARED_SECRET in .env to a value you generated}
|
ADMIN_SHARED_SECRET: ${ADMIN_SHARED_SECRET:?set ADMIN_SHARED_SECRET in .env to a value you generated}
|
||||||
|
# Every interface *inside the container*, which is what makes the
|
||||||
|
# loopback publication below reachable. The processes default to
|
||||||
|
# 127.0.0.1 because a bare process on a host has no such wrapper and a
|
||||||
|
# public bind there is a listener on the internet; a container's own
|
||||||
|
# loopback is not, and binding it would make the port unpublishable.
|
||||||
|
HOST: 0.0.0.0
|
||||||
# Loopback, for the same reason as the issuer above.
|
# Loopback, for the same reason as the issuer above.
|
||||||
ports:
|
ports:
|
||||||
- '127.0.0.1:3000:3000'
|
- '127.0.0.1:3000:3000'
|
||||||
|
|||||||
Reference in New Issue
Block a user