feat(server): validate OPENCHAMBER_OPENCODE_HOSTNAME bind hostname

The env var was already read and passed to the managed OpenCode server
spawn, but any non-empty string was accepted. Reject values that are
not a valid IP (IPv4/IPv6, brackets allowed) or DNS-style hostname with
a clear [config] error and fall back to the secure loopback default so
a typo can never silently bind a non-loopback address.

Refs OPE-231
This commit is contained in:
Serhii Dziupin
2026-08-05 11:24:14 +03:00
parent 34c221b07f
commit ddae6f2545
4 changed files with 152 additions and 2 deletions
@@ -1,3 +1,26 @@
import { isIP } from 'node:net';
const MAX_HOSTNAME_LENGTH = 253;
const HOSTNAME_LABEL_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
// All-numeric dotted values must be a real IPv4 address; otherwise typo'd IPs
// like "0.0.0.0.0" would slip through as (technically valid) hostnames.
const ALL_NUMERIC_DOTTED_RE = /^\d+(?:\.\d+)*$/;
// Valid bind hostnames for the managed OpenCode server: IPv4, IPv6 (with or
// without brackets), or a DNS-style hostname. Everything else (URLs, ports,
// paths, whitespace, underscores) is rejected.
export const isValidOpenCodeHostname = (value) => {
if (typeof value !== 'string') return false;
const trimmed = value.trim();
if (!trimmed || trimmed.length > MAX_HOSTNAME_LENGTH) return false;
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
return isIP(trimmed.slice(1, -1)) === 6;
}
if (isIP(trimmed) !== 0) return true;
if (ALL_NUMERIC_DOTTED_RE.test(trimmed)) return false;
return trimmed.split('.').every((label) => HOSTNAME_LABEL_RE.test(label));
};
export const resolveOpenCodeEnvConfig = (options = {}) => {
const env = options.env && typeof options.env === 'object' ? options.env : {};
const logger = options.logger ?? console;
@@ -60,6 +83,14 @@ export const resolveOpenCodeEnvConfig = (options = {}) => {
);
return '127.0.0.1';
}
if (!isValidOpenCodeHostname(trimmed)) {
logger.error(
`[config] Rejecting OPENCHAMBER_OPENCODE_HOSTNAME=${JSON.stringify(raw)}: `
+ 'must be a valid hostname or IP address (for example 127.0.0.1, 0.0.0.0, localhost, [::1]); '
+ 'falling back to 127.0.0.1 (loopback only)',
);
return '127.0.0.1';
}
return trimmed;
})();