fix: honor trusted proxy client IPs (PR #1654)
Thanks @ndbroadbent. Co-authored-by: Nathan Broadbent <git@ndbroadbent.com>
This commit is contained in:
@@ -28,6 +28,7 @@ Docs: https://docs.clawd.bot
|
||||
- Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.
|
||||
- Agents: use the active auth profile for auto-compaction recovery.
|
||||
- Models: default missing custom provider fields so minimal configs are accepted.
|
||||
- Gateway: honor trusted proxy client IPs for local pairing + HTTP checks. (#1654) Thanks @ndbroadbent.
|
||||
- Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b.
|
||||
- macOS: default direct-transport `ws://` URLs to port 18789; document `gateway.remote.transport`. (#1603) Thanks @ngutman.
|
||||
- Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634)
|
||||
|
||||
@@ -2846,6 +2846,11 @@ Related docs:
|
||||
- [Tailscale](/gateway/tailscale)
|
||||
- [Remote access](/gateway/remote)
|
||||
|
||||
Trusted proxies:
|
||||
- `gateway.trustedProxies`: list of reverse proxy IPs that terminate TLS in front of the Gateway.
|
||||
- When a connection comes from one of these IPs, Clawdbot uses `x-forwarded-for` (or `x-real-ip`) to determine the client IP for local pairing checks and HTTP auth/local checks.
|
||||
- Only list proxies you fully control, and ensure they **overwrite** incoming `x-forwarded-for`.
|
||||
|
||||
Notes:
|
||||
- `clawdbot gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag).
|
||||
- `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).
|
||||
|
||||
@@ -322,6 +322,11 @@ Tailscale.
|
||||
you terminate TLS or proxy in front of the gateway, disable
|
||||
`gateway.auth.allowTailscale` and use token/password auth instead.
|
||||
|
||||
Trusted proxies:
|
||||
- If you terminate TLS in front of the Gateway, set `gateway.trustedProxies` to your proxy IPs.
|
||||
- Clawdbot will trust `x-forwarded-for` (or `x-real-ip`) from those IPs to determine the client IP for local pairing checks and HTTP auth/local checks.
|
||||
- Ensure your proxy **overwrites** `x-forwarded-for` and blocks direct access to the Gateway port.
|
||||
|
||||
See [Tailscale](/gateway/tailscale) and [Web overview](/web).
|
||||
|
||||
### 0.6.1) Browser control server over Tailscale (recommended)
|
||||
|
||||
@@ -218,4 +218,10 @@ export type GatewayConfig = {
|
||||
tls?: GatewayTlsConfig;
|
||||
http?: GatewayHttpConfig;
|
||||
nodes?: GatewayNodesConfig;
|
||||
/**
|
||||
* IPs of trusted reverse proxies (e.g. Traefik, nginx). When a connection
|
||||
* arrives from one of these IPs, the Gateway trusts `x-forwarded-for` (or
|
||||
* `x-real-ip`) to determine the client IP for local pairing and HTTP checks.
|
||||
*/
|
||||
trustedProxies?: string[];
|
||||
};
|
||||
|
||||
@@ -324,6 +324,7 @@ export const ClawdbotSchema = z
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
trustedProxies: z.array(z.string()).optional(),
|
||||
tailscale: z
|
||||
.object({
|
||||
mode: z.union([z.literal("off"), z.literal("serve"), z.literal("funnel")]).optional(),
|
||||
|
||||
@@ -142,4 +142,19 @@ describe("gateway auth", () => {
|
||||
expect(res.method).toBe("tailscale");
|
||||
expect(res.user).toBe("peter");
|
||||
});
|
||||
|
||||
it("treats trusted proxy loopback clients as direct", async () => {
|
||||
const res = await authorizeGatewayConnect({
|
||||
auth: { mode: "none", allowTailscale: true },
|
||||
connectAuth: null,
|
||||
trustedProxies: ["10.0.0.2"],
|
||||
req: {
|
||||
socket: { remoteAddress: "10.0.0.2" },
|
||||
headers: { host: "localhost", "x-forwarded-for": "127.0.0.1" },
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(res.ok).toBe(true);
|
||||
expect(res.method).toBe("none");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
import type { IncomingMessage } from "node:http";
|
||||
import type { GatewayAuthConfig, GatewayTailscaleMode } from "../config/config.js";
|
||||
import { isTrustedProxyAddress, resolveGatewayClientIp } from "./net.js";
|
||||
export type ResolvedGatewayAuthMode = "none" | "token" | "password";
|
||||
|
||||
export type ResolvedGatewayAuth = {
|
||||
@@ -53,9 +54,26 @@ function getHostName(hostHeader?: string): string {
|
||||
return name ?? "";
|
||||
}
|
||||
|
||||
function isLocalDirectRequest(req?: IncomingMessage): boolean {
|
||||
function headerValue(value: string | string[] | undefined): string | undefined {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
|
||||
function resolveRequestClientIp(
|
||||
req?: IncomingMessage,
|
||||
trustedProxies?: string[],
|
||||
): string | undefined {
|
||||
if (!req) return undefined;
|
||||
return resolveGatewayClientIp({
|
||||
remoteAddr: req.socket?.remoteAddress ?? "",
|
||||
forwardedFor: headerValue(req.headers?.["x-forwarded-for"]),
|
||||
realIp: headerValue(req.headers?.["x-real-ip"]),
|
||||
trustedProxies,
|
||||
});
|
||||
}
|
||||
|
||||
function isLocalDirectRequest(req?: IncomingMessage, trustedProxies?: string[]): boolean {
|
||||
if (!req) return false;
|
||||
const clientIp = req.socket?.remoteAddress ?? "";
|
||||
const clientIp = resolveRequestClientIp(req, trustedProxies) ?? "";
|
||||
if (!isLoopbackAddress(clientIp)) return false;
|
||||
|
||||
const host = getHostName(req.headers?.host);
|
||||
@@ -68,7 +86,8 @@ function isLocalDirectRequest(req?: IncomingMessage): boolean {
|
||||
req.headers?.["x-forwarded-host"],
|
||||
);
|
||||
|
||||
return (hostIsLocal || hostIsTailscaleServe) && !hasForwarded;
|
||||
const remoteIsTrustedProxy = isTrustedProxyAddress(req.socket?.remoteAddress, trustedProxies);
|
||||
return (hostIsLocal || hostIsTailscaleServe) && (!hasForwarded || remoteIsTrustedProxy);
|
||||
}
|
||||
|
||||
function getTailscaleUser(req?: IncomingMessage): TailscaleUser | null {
|
||||
@@ -135,9 +154,10 @@ export async function authorizeGatewayConnect(params: {
|
||||
auth: ResolvedGatewayAuth;
|
||||
connectAuth?: ConnectAuth | null;
|
||||
req?: IncomingMessage;
|
||||
trustedProxies?: string[];
|
||||
}): Promise<GatewayAuthResult> {
|
||||
const { auth, connectAuth, req } = params;
|
||||
const localDirect = isLocalDirectRequest(req);
|
||||
const { auth, connectAuth, req, trustedProxies } = params;
|
||||
const localDirect = isLocalDirectRequest(req, trustedProxies);
|
||||
|
||||
if (auth.allowTailscale && !localDirect) {
|
||||
const tailscaleUser = getTailscaleUser(req);
|
||||
|
||||
@@ -8,7 +8,7 @@ vi.mock("../infra/tailnet.js", () => ({
|
||||
pickPrimaryTailnetIPv6: () => testTailnetIPv6.value,
|
||||
}));
|
||||
|
||||
import { isLocalGatewayAddress } from "./net.js";
|
||||
import { isLocalGatewayAddress, resolveGatewayClientIp } from "./net.js";
|
||||
|
||||
describe("gateway net", () => {
|
||||
beforeEach(() => {
|
||||
@@ -38,4 +38,40 @@ describe("gateway net", () => {
|
||||
testTailnetIPv6.value = "fd7a:115c:a1e0::123";
|
||||
expect(isLocalGatewayAddress("fd7a:115c:a1e0::123")).toBe(true);
|
||||
});
|
||||
|
||||
test("uses forwarded-for when remote is a trusted proxy", () => {
|
||||
const clientIp = resolveGatewayClientIp({
|
||||
remoteAddr: "10.0.0.2",
|
||||
forwardedFor: "203.0.113.9, 10.0.0.2",
|
||||
trustedProxies: ["10.0.0.2"],
|
||||
});
|
||||
expect(clientIp).toBe("203.0.113.9");
|
||||
});
|
||||
|
||||
test("ignores forwarded-for from untrusted proxies", () => {
|
||||
const clientIp = resolveGatewayClientIp({
|
||||
remoteAddr: "10.0.0.3",
|
||||
forwardedFor: "203.0.113.9",
|
||||
trustedProxies: ["10.0.0.2"],
|
||||
});
|
||||
expect(clientIp).toBe("10.0.0.3");
|
||||
});
|
||||
|
||||
test("normalizes trusted proxy IPs and strips forwarded ports", () => {
|
||||
const clientIp = resolveGatewayClientIp({
|
||||
remoteAddr: "::ffff:10.0.0.2",
|
||||
forwardedFor: "203.0.113.9:1234",
|
||||
trustedProxies: ["10.0.0.2"],
|
||||
});
|
||||
expect(clientIp).toBe("203.0.113.9");
|
||||
});
|
||||
|
||||
test("falls back to x-real-ip when forwarded-for is missing", () => {
|
||||
const clientIp = resolveGatewayClientIp({
|
||||
remoteAddr: "10.0.0.2",
|
||||
realIp: "203.0.113.10",
|
||||
trustedProxies: ["10.0.0.2"],
|
||||
});
|
||||
expect(clientIp).toBe("203.0.113.10");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,56 @@ function normalizeIPv4MappedAddress(ip: string): string {
|
||||
return ip;
|
||||
}
|
||||
|
||||
function normalizeIp(ip: string | undefined): string | undefined {
|
||||
const trimmed = ip?.trim();
|
||||
if (!trimmed) return undefined;
|
||||
return normalizeIPv4MappedAddress(trimmed.toLowerCase());
|
||||
}
|
||||
|
||||
function stripOptionalPort(ip: string): string {
|
||||
if (ip.startsWith("[")) {
|
||||
const end = ip.indexOf("]");
|
||||
if (end !== -1) return ip.slice(1, end);
|
||||
}
|
||||
if (net.isIP(ip)) return ip;
|
||||
const lastColon = ip.lastIndexOf(":");
|
||||
if (lastColon > -1 && ip.includes(".") && ip.indexOf(":") === lastColon) {
|
||||
const candidate = ip.slice(0, lastColon);
|
||||
if (net.isIP(candidate) === 4) return candidate;
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
function parseForwardedForClientIp(forwardedFor?: string): string | undefined {
|
||||
const raw = forwardedFor?.split(",")[0]?.trim();
|
||||
if (!raw) return undefined;
|
||||
return normalizeIp(stripOptionalPort(raw));
|
||||
}
|
||||
|
||||
function parseRealIp(realIp?: string): string | undefined {
|
||||
const raw = realIp?.trim();
|
||||
if (!raw) return undefined;
|
||||
return normalizeIp(stripOptionalPort(raw));
|
||||
}
|
||||
|
||||
export function isTrustedProxyAddress(ip: string | undefined, trustedProxies?: string[]): boolean {
|
||||
const normalized = normalizeIp(ip);
|
||||
if (!normalized || !trustedProxies || trustedProxies.length === 0) return false;
|
||||
return trustedProxies.some((proxy) => normalizeIp(proxy) === normalized);
|
||||
}
|
||||
|
||||
export function resolveGatewayClientIp(params: {
|
||||
remoteAddr?: string;
|
||||
forwardedFor?: string;
|
||||
realIp?: string;
|
||||
trustedProxies?: string[];
|
||||
}): string | undefined {
|
||||
const remote = normalizeIp(params.remoteAddr);
|
||||
if (!remote) return undefined;
|
||||
if (!isTrustedProxyAddress(remote, params.trustedProxies)) return remote;
|
||||
return parseForwardedForClientIp(params.forwardedFor) ?? parseRealIp(params.realIp) ?? remote;
|
||||
}
|
||||
|
||||
export function isLocalGatewayAddress(ip: string | undefined): boolean {
|
||||
if (isLoopbackAddress(ip)) return true;
|
||||
if (!ip) return false;
|
||||
|
||||
@@ -20,6 +20,7 @@ import { getBearerToken, resolveAgentIdForRequest, resolveSessionKey } from "./h
|
||||
type OpenAiHttpOptions = {
|
||||
auth: ResolvedGatewayAuth;
|
||||
maxBodyBytes?: number;
|
||||
trustedProxies?: string[];
|
||||
};
|
||||
|
||||
type OpenAiChatMessage = {
|
||||
@@ -168,6 +169,7 @@ export async function handleOpenAiHttpRequest(
|
||||
auth: opts.auth,
|
||||
connectAuth: { token, password: token },
|
||||
req,
|
||||
trustedProxies: opts.trustedProxies,
|
||||
});
|
||||
if (!authResult.ok) {
|
||||
sendUnauthorized(res);
|
||||
|
||||
@@ -60,6 +60,7 @@ type OpenResponsesHttpOptions = {
|
||||
auth: ResolvedGatewayAuth;
|
||||
maxBodyBytes?: number;
|
||||
config?: GatewayHttpResponsesConfig;
|
||||
trustedProxies?: string[];
|
||||
};
|
||||
|
||||
const DEFAULT_BODY_BYTES = 20 * 1024 * 1024;
|
||||
@@ -331,6 +332,7 @@ export async function handleOpenResponsesHttpRequest(
|
||||
auth: opts.auth,
|
||||
connectAuth: { token, password: token },
|
||||
req,
|
||||
trustedProxies: opts.trustedProxies,
|
||||
});
|
||||
if (!authResult.ok) {
|
||||
sendUnauthorized(res);
|
||||
|
||||
@@ -227,21 +227,36 @@ export function createGatewayHttpServer(opts: {
|
||||
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
||||
|
||||
try {
|
||||
const configSnapshot = loadConfig();
|
||||
const trustedProxies = configSnapshot.gateway?.trustedProxies ?? [];
|
||||
if (await handleHooksRequest(req, res)) return;
|
||||
if (await handleSlackHttpRequest(req, res)) return;
|
||||
if (handlePluginRequest && (await handlePluginRequest(req, res))) return;
|
||||
if (await handleToolsInvokeHttpRequest(req, res, { auth: resolvedAuth })) return;
|
||||
if (
|
||||
await handleToolsInvokeHttpRequest(req, res, {
|
||||
auth: resolvedAuth,
|
||||
trustedProxies,
|
||||
})
|
||||
)
|
||||
return;
|
||||
if (openResponsesEnabled) {
|
||||
if (
|
||||
await handleOpenResponsesHttpRequest(req, res, {
|
||||
auth: resolvedAuth,
|
||||
config: openResponsesConfig,
|
||||
trustedProxies,
|
||||
})
|
||||
)
|
||||
return;
|
||||
}
|
||||
if (openAiChatCompletionsEnabled) {
|
||||
if (await handleOpenAiHttpRequest(req, res, { auth: resolvedAuth })) return;
|
||||
if (
|
||||
await handleOpenAiHttpRequest(req, res, {
|
||||
auth: resolvedAuth,
|
||||
trustedProxies,
|
||||
})
|
||||
)
|
||||
return;
|
||||
}
|
||||
if (canvasHost) {
|
||||
if (await handleA2uiHttpRequest(req, res)) return;
|
||||
@@ -251,14 +266,14 @@ export function createGatewayHttpServer(opts: {
|
||||
if (
|
||||
handleControlUiAvatarRequest(req, res, {
|
||||
basePath: controlUiBasePath,
|
||||
resolveAvatar: (agentId) => resolveAgentAvatar(loadConfig(), agentId),
|
||||
resolveAvatar: (agentId) => resolveAgentAvatar(configSnapshot, agentId),
|
||||
})
|
||||
)
|
||||
return;
|
||||
if (
|
||||
handleControlUiHttpRequest(req, res, {
|
||||
basePath: controlUiBasePath,
|
||||
config: loadConfig(),
|
||||
config: configSnapshot,
|
||||
})
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -73,6 +73,7 @@ export function attachGatewayWsConnectionHandler(params: {
|
||||
const requestOrigin = headerValue(upgradeReq.headers.origin);
|
||||
const requestUserAgent = headerValue(upgradeReq.headers["user-agent"]);
|
||||
const forwardedFor = headerValue(upgradeReq.headers["x-forwarded-for"]);
|
||||
const realIp = headerValue(upgradeReq.headers["x-real-ip"]);
|
||||
|
||||
const canvasHostPortForWs = canvasHostServerPort ?? (canvasHostEnabled ? port : undefined);
|
||||
const canvasHostOverride =
|
||||
@@ -228,6 +229,7 @@ export function attachGatewayWsConnectionHandler(params: {
|
||||
connId,
|
||||
remoteAddr,
|
||||
forwardedFor,
|
||||
realIp,
|
||||
requestHost,
|
||||
requestOrigin,
|
||||
requestUserAgent,
|
||||
|
||||
@@ -26,7 +26,7 @@ import type { ResolvedGatewayAuth } from "../../auth.js";
|
||||
import { authorizeGatewayConnect } from "../../auth.js";
|
||||
import { loadConfig } from "../../../config/config.js";
|
||||
import { buildDeviceAuthPayload } from "../../device-auth.js";
|
||||
import { isLocalGatewayAddress } from "../../net.js";
|
||||
import { isLocalGatewayAddress, resolveGatewayClientIp } from "../../net.js";
|
||||
import { resolveNodeCommandAllowlist } from "../../node-command-policy.js";
|
||||
import {
|
||||
type ConnectParams,
|
||||
@@ -104,6 +104,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
connId: string;
|
||||
remoteAddr?: string;
|
||||
forwardedFor?: string;
|
||||
realIp?: string;
|
||||
requestHost?: string;
|
||||
requestOrigin?: string;
|
||||
requestUserAgent?: string;
|
||||
@@ -133,6 +134,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
connId,
|
||||
remoteAddr,
|
||||
forwardedFor,
|
||||
realIp,
|
||||
requestHost,
|
||||
requestOrigin,
|
||||
requestUserAgent,
|
||||
@@ -157,6 +159,11 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
logWsControl,
|
||||
} = params;
|
||||
|
||||
const configSnapshot = loadConfig();
|
||||
const trustedProxies = configSnapshot.gateway?.trustedProxies ?? [];
|
||||
const clientIp = resolveGatewayClientIp({ remoteAddr, forwardedFor, realIp, trustedProxies });
|
||||
const isLocalClient = isLocalGatewayAddress(clientIp);
|
||||
|
||||
const isWebchatConnect = (p: ConnectParams | null | undefined) => isWebchatClient(p?.client);
|
||||
|
||||
socket.on("message", async (data) => {
|
||||
@@ -300,7 +307,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
|
||||
if (!device) {
|
||||
const allowInsecureControlUi =
|
||||
isControlUi && loadConfig().gateway?.controlUi?.allowInsecureAuth === true;
|
||||
isControlUi && configSnapshot.gateway?.controlUi?.allowInsecureAuth === true;
|
||||
const canSkipDevice =
|
||||
isControlUi && allowInsecureControlUi ? hasTokenAuth || hasPasswordAuth : hasTokenAuth;
|
||||
|
||||
@@ -380,7 +387,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
close(1008, "device signature expired");
|
||||
return;
|
||||
}
|
||||
const nonceRequired = !isLocalGatewayAddress(remoteAddr);
|
||||
const nonceRequired = !isLocalClient;
|
||||
const providedNonce = typeof device.nonce === "string" ? device.nonce.trim() : "";
|
||||
if (nonceRequired && !providedNonce) {
|
||||
setHandshakeState("failed");
|
||||
@@ -495,6 +502,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
auth: resolvedAuth,
|
||||
connectAuth: connectParams.auth,
|
||||
req: upgradeReq,
|
||||
trustedProxies,
|
||||
});
|
||||
let authOk = authResult.ok;
|
||||
let authMethod = authResult.method ?? "none";
|
||||
@@ -556,8 +564,8 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
clientMode: connectParams.client.mode,
|
||||
role,
|
||||
scopes,
|
||||
remoteIp: remoteAddr,
|
||||
silent: isLocalGatewayAddress(remoteAddr),
|
||||
remoteIp: clientIp,
|
||||
silent: isLocalClient,
|
||||
});
|
||||
const context = buildRequestContext();
|
||||
if (pairing.request.silent === true) {
|
||||
@@ -640,7 +648,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
clientMode: connectParams.client.mode,
|
||||
role,
|
||||
scopes,
|
||||
remoteIp: remoteAddr,
|
||||
remoteIp: clientIp,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -689,7 +697,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
if (presenceKey) {
|
||||
upsertPresence(presenceKey, {
|
||||
host: connectParams.client.displayName ?? connectParams.client.id ?? os.hostname(),
|
||||
ip: isLocalGatewayAddress(remoteAddr) ? undefined : remoteAddr,
|
||||
ip: isLocalClient ? undefined : clientIp,
|
||||
version: connectParams.client.version,
|
||||
platform: connectParams.client.platform,
|
||||
deviceFamily: connectParams.client.deviceFamily,
|
||||
@@ -748,7 +756,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
setHandshakeState("connected");
|
||||
if (role === "node") {
|
||||
const context = buildRequestContext();
|
||||
const nodeSession = context.nodeRegistry.register(nextClient, { remoteIp: remoteAddr });
|
||||
const nodeSession = context.nodeRegistry.register(nextClient, { remoteIp: clientIp });
|
||||
const instanceIdRaw = connectParams.client.instanceId;
|
||||
const instanceId = typeof instanceIdRaw === "string" ? instanceIdRaw.trim() : "";
|
||||
const nodeIdsForPairing = new Set<string>([nodeSession.nodeId]);
|
||||
|
||||
@@ -70,7 +70,7 @@ function mergeActionIntoArgsIfSupported(params: {
|
||||
export async function handleToolsInvokeHttpRequest(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
opts: { auth: ResolvedGatewayAuth; maxBodyBytes?: number },
|
||||
opts: { auth: ResolvedGatewayAuth; maxBodyBytes?: number; trustedProxies?: string[] },
|
||||
): Promise<boolean> {
|
||||
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
||||
if (url.pathname !== "/tools/invoke") return false;
|
||||
@@ -80,11 +80,13 @@ export async function handleToolsInvokeHttpRequest(
|
||||
return true;
|
||||
}
|
||||
|
||||
const cfg = loadConfig();
|
||||
const token = getBearerToken(req);
|
||||
const authResult = await authorizeGatewayConnect({
|
||||
auth: opts.auth,
|
||||
connectAuth: token ? { token, password: token } : null,
|
||||
req,
|
||||
trustedProxies: opts.trustedProxies ?? cfg.gateway?.trustedProxies,
|
||||
});
|
||||
if (!authResult.ok) {
|
||||
sendUnauthorized(res);
|
||||
@@ -110,7 +112,6 @@ export async function handleToolsInvokeHttpRequest(
|
||||
: {}
|
||||
) as Record<string, unknown>;
|
||||
|
||||
const cfg = loadConfig();
|
||||
const rawSessionKey = resolveSessionKeyFromBody(body);
|
||||
const sessionKey =
|
||||
!rawSessionKey || rawSessionKey === "main" ? resolveMainSessionKey(cfg) : rawSessionKey;
|
||||
|
||||
Reference in New Issue
Block a user