feat: HOST validated at the adapter boundary and controls the actual bind interface (E00-S04-T04)

The environment adapter now resolves HOST through resolveHost, validating it
at the adapter boundary as a hostname (RFC 1123) or IP address (IPv4/IPv6,
node:net isIP); an invalid HOST throws a field-specific ConfigStartupError
naming host, so arbitrary env content is never used for binding or echoed
verbatim into the startup log (issue acceptance criterion, resolving security
review finding SEC-3).

The server passes config.host to server.listen(config.port, config.host, ...),
so a configured HOST binds exactly that interface and the startup log never
claims a bind the process does not enforce (resolving SEC-2).
This commit is contained in:
implementer
2026-08-30 04:41:53 +00:00
parent 73a1ae88cd
commit 345ceccfad
5 changed files with 65 additions and 9 deletions
+10 -2
View File
@@ -33,7 +33,11 @@
* (`HOST`/`PORT`/`DATABASE_URL`/`EPPP_SESSION_SECRET`) onto the validated
* config shape and validates it with `assertValidConfig` (E00-S04-T02) before
* the server binds, so a missing required setting is still a startup error
* naming the missing field.
* naming the missing field. `HOST` is validated at the adapter boundary as a
* hostname or IP address and the server passes `config.host` to
* `server.listen`, so a configured `HOST` binds exactly that interface and
* the startup log reflects the actual bind — it never claims a bind the
* process does not enforce, and never echoes unvalidated env content.
*
* [E00-S04-T03] secret redaction: ALL log output goes through the redacting
* logger (`createLogger`, defined below — every line is scrubbed of the
@@ -199,7 +203,11 @@ if (databaseUrl === undefined) {
});
}
server.listen(config.port, () => {
// The server binds the validated bind interface: `config.host` (default
// `0.0.0.0`, validated as a hostname/IP by the adapter) is passed to
// `server.listen`, so a configured `HOST` binds exactly that interface and
// the startup log reflects the actual bind.
server.listen(config.port, config.host, () => {
logger.log(`@personal-blog/server listening on http://${config.host}:${config.port} (health: GET /health)`);
});