Harden remote API security boundaries

This commit is contained in:
Bohdan Triapitsyn
2026-06-12 18:24:07 +03:00
parent c281937406
commit 106b31a407
52 changed files with 1582 additions and 579 deletions
+25 -6
View File
@@ -11,6 +11,11 @@ import { fileURLToPath, pathToFileURL } from 'url';
import { isModuleCliExecution } from './cli-entry.js';
import { cloudflareTunnelProviderCapabilities } from '../server/lib/tunnels/providers/cloudflare.js';
import { createRemoteClientAuthRuntime } from '../server/lib/client-auth/remote-clients.js';
import {
getUnauthenticatedLanErrorMessage,
isNetworkExposedBindHost,
isUnsafeUnauthenticatedLanAllowed,
} from '../server/lib/security/bind-host.js';
import {
intro as clackIntro, outro as clackOutro, log as clackLog,
box as clackBox, confirm as clackConfirm,
@@ -147,10 +152,6 @@ function resolveConfiguredBindHost(hostOverride) {
return configured || '127.0.0.1';
}
function isWildcardBindHost(host) {
return host === '0.0.0.0' || host === '::' || host === '[::]';
}
function resolveApiHost(hostOverride) {
const configured = resolveConfiguredBindHost(hostOverride);
@@ -637,6 +638,20 @@ function hasUiPasswordConfigured(password) {
return typeof password === 'string' && password.trim().length > 0;
}
function assertAuthenticatedNetworkExposure({ host, uiPassword }) {
const bindHost = resolveConfiguredBindHost(host);
if (hasUiPasswordConfigured(uiPassword)) {
return;
}
if (!isNetworkExposedBindHost(bindHost)) {
return;
}
if (isUnsafeUnauthenticatedLanAllowed(process.env)) {
return;
}
throw new TunnelCliError(getUnauthenticatedLanErrorMessage(bindHost), EXIT_CODE.AUTH_CONFIG_ERROR);
}
const BUN_BIN = getBunBinary();
function isBunRuntime() {
@@ -3445,10 +3460,13 @@ const commands = {
const logFd = fs.openSync(initialLogPath, 'a');
const effectiveUiPassword = hasUiPasswordConfigured(options.uiPassword) ? options.uiPassword : undefined;
assertAuthenticatedNetworkExposure({
host: options.host,
uiPassword: effectiveUiPassword,
});
if (!effectiveUiPassword && !options.suppressUiPasswordWarning) {
const bindHost = resolveConfiguredBindHost(options.host);
const loopbackHosts = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
const networkExposed = isWildcardBindHost(bindHost) || !loopbackHosts.has(bindHost);
const networkExposed = isNetworkExposedBindHost(bindHost);
const warningLine = 'OPENCHAMBER_UI_PASSWORD is not set';
const warningDetail = networkExposed
? `server is bound to ${bindHost} and reachable on your network with no UI auth. `
@@ -5710,6 +5728,7 @@ if (isCliExecution) {
export {
commands,
parseArgs,
assertAuthenticatedNetworkExposure,
hasUiPasswordConfigured,
shouldDisplayTunnelQr,
isValidTunnelDoctorResponse,