mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 08:27:39 +00:00
fix(security): fail closed on gateway bind fallback and tighten canvas IP fallback
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import type { TlsOptions } from "node:tls";
|
||||
import type { WebSocketServer } from "ws";
|
||||
import {
|
||||
createServer as createHttpServer,
|
||||
type Server as HttpServer,
|
||||
@@ -5,8 +7,10 @@ import {
|
||||
type ServerResponse,
|
||||
} from "node:http";
|
||||
import { createServer as createHttpsServer } from "node:https";
|
||||
import type { TlsOptions } from "node:tls";
|
||||
import type { WebSocketServer } from "ws";
|
||||
import type { CanvasHostHandler } from "../canvas-host/server.js";
|
||||
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { AuthRateLimiter } from "./auth-rate-limit.js";
|
||||
import type { GatewayWsClient } from "./server/ws-types.js";
|
||||
import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
||||
import {
|
||||
A2UI_PATH,
|
||||
@@ -14,12 +18,9 @@ import {
|
||||
CANVAS_WS_PATH,
|
||||
handleA2uiHttpRequest,
|
||||
} from "../canvas-host/a2ui.js";
|
||||
import type { CanvasHostHandler } from "../canvas-host/server.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { safeEqualSecret } from "../security/secret-equal.js";
|
||||
import { handleSlackHttpRequest } from "../slack/http/index.js";
|
||||
import type { AuthRateLimiter } from "./auth-rate-limit.js";
|
||||
import {
|
||||
authorizeGatewayConnect,
|
||||
isLocalDirectRequest,
|
||||
@@ -50,10 +51,14 @@ import {
|
||||
} from "./hooks.js";
|
||||
import { sendGatewayAuthFailure, setDefaultSecurityHeaders } from "./http-common.js";
|
||||
import { getBearerToken, getHeader } from "./http-utils.js";
|
||||
import { isPrivateOrLoopbackAddress, resolveGatewayClientIp } from "./net.js";
|
||||
import {
|
||||
isPrivateOrLoopbackAddress,
|
||||
isTrustedProxyAddress,
|
||||
resolveGatewayClientIp,
|
||||
} from "./net.js";
|
||||
import { handleOpenAiHttpRequest } from "./openai-http.js";
|
||||
import { handleOpenResponsesHttpRequest } from "./openresponses-http.js";
|
||||
import type { GatewayWsClient } from "./server/ws-types.js";
|
||||
import { GATEWAY_CLIENT_MODES, normalizeGatewayClientMode } from "./protocol/client-info.js";
|
||||
import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
|
||||
|
||||
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
|
||||
@@ -97,9 +102,16 @@ function isCanvasPath(pathname: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function hasAuthorizedWsClientForIp(clients: Set<GatewayWsClient>, clientIp: string): boolean {
|
||||
function isNodeWsClient(client: GatewayWsClient): boolean {
|
||||
if (client.connect.role === "node") {
|
||||
return true;
|
||||
}
|
||||
return normalizeGatewayClientMode(client.connect.client.mode) === GATEWAY_CLIENT_MODES.NODE;
|
||||
}
|
||||
|
||||
function hasAuthorizedNodeWsClientForIp(clients: Set<GatewayWsClient>, clientIp: string): boolean {
|
||||
for (const client of clients) {
|
||||
if (client.clientIp && client.clientIp === clientIp) {
|
||||
if (client.clientIp && client.clientIp === clientIp && isNodeWsClient(client)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -118,6 +130,9 @@ async function authorizeCanvasRequest(params: {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
const hasProxyHeaders = Boolean(getHeader(req, "x-forwarded-for") || getHeader(req, "x-real-ip"));
|
||||
const remoteIsTrustedProxy = isTrustedProxyAddress(req.socket?.remoteAddress, trustedProxies);
|
||||
|
||||
let lastAuthFailure: GatewayAuthResult | null = null;
|
||||
const token = getBearerToken(req);
|
||||
if (token) {
|
||||
@@ -150,7 +165,11 @@ async function authorizeCanvasRequest(params: {
|
||||
if (!isPrivateOrLoopbackAddress(clientIp)) {
|
||||
return lastAuthFailure ?? { ok: false, reason: "unauthorized" };
|
||||
}
|
||||
if (hasAuthorizedWsClientForIp(clients, clientIp)) {
|
||||
// Ignore IP fallback when proxy headers come from an untrusted source.
|
||||
if (hasProxyHeaders && !remoteIsTrustedProxy) {
|
||||
return lastAuthFailure ?? { ok: false, reason: "unauthorized" };
|
||||
}
|
||||
if (hasAuthorizedNodeWsClientForIp(clients, clientIp)) {
|
||||
return { ok: true };
|
||||
}
|
||||
return lastAuthFailure ?? { ok: false, reason: "unauthorized" };
|
||||
|
||||
Reference in New Issue
Block a user