feat(gateway): add trusted-proxy auth mode (#15940)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 279d4b304f
Co-authored-by: nickytonline <833231+nickytonline@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
This commit is contained in:
Nick Taylor
2026-02-14 06:32:17 -05:00
committed by GitHub
parent 3a330e681b
commit 1fb52b4d7b
28 changed files with 1867 additions and 92 deletions

View File

@@ -85,6 +85,8 @@ export async function resolveGatewayRuntimeConfig(params: {
const canvasHostEnabled =
process.env.OPENCLAW_SKIP_CANVAS_HOST !== "1" && params.cfg.canvasHost?.enabled !== false;
const trustedProxies = params.cfg.gateway?.trustedProxies ?? [];
assertGatewayAuthConfigured(resolvedAuth);
if (tailscaleMode === "funnel" && authMode !== "password") {
throw new Error(
@@ -94,12 +96,25 @@ export async function resolveGatewayRuntimeConfig(params: {
if (tailscaleMode !== "off" && !isLoopbackHost(bindHost)) {
throw new Error("tailscale serve/funnel requires gateway bind=loopback (127.0.0.1)");
}
if (!isLoopbackHost(bindHost) && !hasSharedSecret) {
if (!isLoopbackHost(bindHost) && !hasSharedSecret && authMode !== "trusted-proxy") {
throw new Error(
`refusing to bind gateway to ${bindHost}:${params.port} without auth (set gateway.auth.token/password, or set OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD)`,
);
}
if (authMode === "trusted-proxy") {
if (isLoopbackHost(bindHost)) {
throw new Error(
"gateway auth mode=trusted-proxy makes no sense with bind=loopback; use bind=lan or bind=custom with gateway.trustedProxies configured",
);
}
if (trustedProxies.length === 0) {
throw new Error(
"gateway auth mode=trusted-proxy requires gateway.trustedProxies to be configured with at least one proxy IP",
);
}
}
return {
bindHost,
controlUiEnabled,