fix(gateway): defer http auth imports

This commit is contained in:
Vincent Koc
2026-04-27 00:22:20 -07:00
parent 735890d955
commit 1ee893bc5f
2 changed files with 44 additions and 9 deletions

View File

@@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai
- Gateway/startup: read embedded-run activity from a lightweight shared state module so restart deferral no longer imports the embedded runner during Gateway boot. Thanks @vincentkoc.
- Gateway/startup: defer MCP loopback server imports until Gateway shutdown so normal boot no longer loads the loopback HTTP/tool schema stack just to register close handlers. Thanks @vincentkoc.
- Gateway/startup: resolve channel runtime helpers asynchronously only when an enabled/configured channel starts, so no-channel Gateway boot skips auto-reply, media, pairing, and outbound channel helper imports. Thanks @vincentkoc.
- Gateway/startup: lazy-load HTTP auth, canvas auth, and plugin route scope helpers from their request paths so Gateway bind no longer pays those utility graphs during boot. Thanks @vincentkoc.
- CLI/Gateway: use a parse-only config snapshot for plain `gateway status` reads and reuse same-path service config context so status no longer spends tens of seconds in full config validation before printing. Thanks @vincentkoc.
- Lobster/Gateway: memoize repeated Ajv schema compilation before loading the embedded Lobster runtime so scheduled workflows and `llm.invoke` loops stop growing gateway heap on content-identical schemas. Fixes #71148. Thanks @cmi525, @vsolaz, and @vincentkoc.
- Codex harness: normalize cached input tokens before session/context accounting so prompt cache reads are not double-counted in `/status`, `session_status`, or persisted `sessionEntry.totalTokens`. Fixes #69298. Thanks @richardmqq.

View File

@@ -8,7 +8,12 @@ import {
import { createServer as createHttpsServer } from "node:https";
import type { TlsOptions } from "node:tls";
import type { WebSocketServer } from "ws";
import { A2UI_PATH, CANVAS_WS_PATH, handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
import {
A2UI_PATH,
CANVAS_HOST_PATH,
CANVAS_WS_PATH,
handleA2uiHttpRequest,
} from "../canvas-host/a2ui.js";
import type { CanvasHostHandler } from "../canvas-host/server.js";
import { resolveBundledChannelGatewayAuthBypassPaths } from "../channels/plugins/gateway-auth-bypass.js";
import { loadConfig } from "../config/config.js";
@@ -56,17 +61,10 @@ import {
resolveHookChannel,
resolveHookDeliver,
} from "./hooks.js";
import {
type AuthorizedGatewayHttpRequest,
authorizeGatewayHttpRequestOrReply,
getBearerToken,
resolveHttpBrowserOriginPolicy,
} from "./http-auth-utils.js";
import type { AuthorizedGatewayHttpRequest } from "./http-auth-utils.js";
import { sendGatewayAuthFailure, setDefaultSecurityHeaders } from "./http-common.js";
import { resolveRequestClientIp } from "./net.js";
import { DEDUPE_MAX, DEDUPE_TTL_MS } from "./server-constants.js";
import { authorizeCanvasRequest, isCanvasPath } from "./server/http-auth.js";
import { resolvePluginRouteRuntimeOperatorScopes } from "./server/plugin-route-runtime-scopes.js";
import {
isProtectedPluginRoutePathFromContext,
resolvePluginRoutePathContext,
@@ -109,6 +107,11 @@ let toolsInvokeHttpModulePromise: Promise<typeof import("./tools-invoke-http.js"
let voiceClawRealtimeUpgradeModulePromise:
| Promise<typeof import("./voiceclaw-realtime/upgrade.js")>
| undefined;
let canvasAuthModulePromise: Promise<typeof import("./server/http-auth.js")> | undefined;
let httpAuthUtilsModulePromise: Promise<typeof import("./http-auth-utils.js")> | undefined;
let pluginRouteRuntimeScopesModulePromise:
| Promise<typeof import("./server/plugin-route-runtime-scopes.js")>
| undefined;
function getIdentityAvatarModule() {
identityAvatarModulePromise ??= import("../agents/identity-avatar.js");
@@ -165,6 +168,21 @@ function getVoiceClawRealtimeUpgradeModule() {
return voiceClawRealtimeUpgradeModulePromise;
}
function getCanvasAuthModule() {
canvasAuthModulePromise ??= import("./server/http-auth.js");
return canvasAuthModulePromise;
}
function getHttpAuthUtilsModule() {
httpAuthUtilsModulePromise ??= import("./http-auth-utils.js");
return httpAuthUtilsModulePromise;
}
function getPluginRouteRuntimeScopesModule() {
pluginRouteRuntimeScopesModulePromise ??= import("./server/plugin-route-runtime-scopes.js");
return pluginRouteRuntimeScopesModulePromise;
}
type HookDispatchers = {
dispatchWakeHook: (value: { text: string; mode: "now" | "next-heartbeat" }) => void;
dispatchAgentHook: (value: HookAgentDispatchPayload) => string;
@@ -283,6 +301,16 @@ function isA2uiPath(pathname: string): boolean {
return pathname === A2UI_PATH || pathname.startsWith(`${A2UI_PATH}/`);
}
function isCanvasPath(pathname: string): boolean {
return (
pathname === A2UI_PATH ||
pathname.startsWith(`${A2UI_PATH}/`) ||
pathname === CANVAS_HOST_PATH ||
pathname.startsWith(`${CANVAS_HOST_PATH}/`) ||
pathname === CANVAS_WS_PATH
);
}
function shouldEnforceDefaultPluginGatewayAuth(pathContext: PluginRoutePathContext): boolean {
return (
pathContext.malformedEncoding ||
@@ -304,6 +332,7 @@ async function canRevealReadinessDetails(params: {
return false;
}
const { getBearerToken, resolveHttpBrowserOriginPolicy } = await getHttpAuthUtilsModule();
const bearerToken = getBearerToken(params.req);
const authResult = await authorizeHttpGatewayConnect({
auth: params.resolvedAuth,
@@ -474,6 +503,7 @@ function buildPluginRequestStages(params: {
if ((await params.getGatewayAuthBypassPaths()).has(params.requestPath)) {
return false;
}
const { authorizeGatewayHttpRequestOrReply } = await getHttpAuthUtilsModule();
const requestAuth = await authorizeGatewayHttpRequestOrReply({
req: params.req,
res: params.res,
@@ -487,6 +517,8 @@ function buildPluginRequestStages(params: {
}
pluginGatewayAuthSatisfied = true;
pluginGatewayRequestAuth = requestAuth;
const { resolvePluginRouteRuntimeOperatorScopes } =
await getPluginRouteRuntimeScopesModule();
pluginRequestOperatorScopes = resolvePluginRouteRuntimeOperatorScopes(
params.req,
requestAuth,
@@ -1092,6 +1124,7 @@ export function createGatewayHttpServer(opts: {
if (!isCanvasPath(scopedRequestPath)) {
return false;
}
const { authorizeCanvasRequest } = await getCanvasAuthModule();
const ok = await authorizeCanvasRequest({
req,
auth: resolvedAuth,
@@ -1258,6 +1291,7 @@ export function attachGatewayUpgradeHandler(opts: {
const url = new URL(req.url ?? "/", "http://localhost");
if (canvasHost) {
if (url.pathname === CANVAS_WS_PATH) {
const { authorizeCanvasRequest } = await getCanvasAuthModule();
const ok = await authorizeCanvasRequest({
req,
auth: resolvedAuth,