mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 08:41:23 +00:00
refactor(security): unify gateway scope authorization flows
This commit is contained in:
@@ -19,6 +19,8 @@ export type ActionGate<T extends Record<string, boolean | undefined>> = (
|
||||
defaultValue?: boolean,
|
||||
) => boolean;
|
||||
|
||||
export const OWNER_ONLY_TOOL_ERROR = "Tool restricted to owner senders.";
|
||||
|
||||
export class ToolInputError extends Error {
|
||||
readonly status = 400;
|
||||
|
||||
@@ -208,6 +210,12 @@ export function jsonResult(payload: unknown): AgentToolResult<unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
export function assertOwnerSender(senderIsOwner?: boolean): void {
|
||||
if (senderIsOwner === false) {
|
||||
throw new Error(OWNER_ONLY_TOOL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
export async function imageResult(params: {
|
||||
label: string;
|
||||
path: string;
|
||||
|
||||
@@ -8,8 +8,8 @@ import { extractTextFromChatContent } from "../../shared/chat-content.js";
|
||||
import { isRecord, truncateUtf16Safe } from "../../utils.js";
|
||||
import { resolveSessionAgentId } from "../agent-scope.js";
|
||||
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
||||
import { assertOwnerSender, type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||
import { callGatewayTool, readGatewayCallOptions, type GatewayCallOptions } from "./gateway.js";
|
||||
import { resolveInternalSessionKey, resolveMainSessionAlias } from "./sessions-helpers.js";
|
||||
|
||||
// NOTE: We use Type.Object({}, { additionalProperties: true }) for job/patch
|
||||
@@ -260,15 +260,15 @@ WAKE MODES (for wake action):
|
||||
Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.`,
|
||||
parameters: CronToolSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
if (opts?.senderIsOwner === false) {
|
||||
throw new Error("Tool restricted to owner senders.");
|
||||
}
|
||||
assertOwnerSender(opts?.senderIsOwner);
|
||||
const params = args as Record<string, unknown>;
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const gatewayOpts: GatewayCallOptions = {
|
||||
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
|
||||
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
|
||||
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : 60_000,
|
||||
...readGatewayCallOptions(params),
|
||||
timeoutMs:
|
||||
typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
|
||||
? params.timeoutMs
|
||||
: 60_000,
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from "../../infra/restart-sentinel.js";
|
||||
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
|
||||
import { stringEnum } from "../schema/typebox.js";
|
||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||
import { callGatewayTool } from "./gateway.js";
|
||||
import { assertOwnerSender, type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||
import { callGatewayTool, readGatewayCallOptions } from "./gateway.js";
|
||||
|
||||
const DEFAULT_UPDATE_TIMEOUT_MS = 20 * 60_000;
|
||||
|
||||
@@ -74,9 +74,7 @@ export function createGatewayTool(opts?: {
|
||||
"Restart, apply config, or update the gateway in-place (SIGUSR1). Use config.patch for safe partial config updates (merges with existing). Use config.apply only when replacing entire config. Both trigger restart after writing. Always pass a human-readable completion message via the `note` parameter so the system can deliver it to the user after restart.",
|
||||
parameters: GatewayToolSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
if (opts?.senderIsOwner === false) {
|
||||
throw new Error("Tool restricted to owner senders.");
|
||||
}
|
||||
assertOwnerSender(opts?.senderIsOwner);
|
||||
const params = args as Record<string, unknown>;
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
if (action === "restart") {
|
||||
@@ -129,19 +127,7 @@ export function createGatewayTool(opts?: {
|
||||
return jsonResult(scheduled);
|
||||
}
|
||||
|
||||
const gatewayUrl =
|
||||
typeof params.gatewayUrl === "string" && params.gatewayUrl.trim()
|
||||
? params.gatewayUrl.trim()
|
||||
: undefined;
|
||||
const gatewayToken =
|
||||
typeof params.gatewayToken === "string" && params.gatewayToken.trim()
|
||||
? params.gatewayToken.trim()
|
||||
: undefined;
|
||||
const timeoutMs =
|
||||
typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
|
||||
? Math.max(1, Math.floor(params.timeoutMs))
|
||||
: undefined;
|
||||
const gatewayOpts = { gatewayUrl, gatewayToken, timeoutMs };
|
||||
const gatewayOpts = readGatewayCallOptions(params);
|
||||
|
||||
const resolveGatewayWriteMeta = (): {
|
||||
sessionKey: string | undefined;
|
||||
@@ -214,15 +200,16 @@ export function createGatewayTool(opts?: {
|
||||
}
|
||||
if (action === "update.run") {
|
||||
const { sessionKey, note, restartDelayMs } = resolveGatewayWriteMeta();
|
||||
const updateTimeoutMs = gatewayOpts.timeoutMs ?? DEFAULT_UPDATE_TIMEOUT_MS;
|
||||
const updateGatewayOpts = {
|
||||
...gatewayOpts,
|
||||
timeoutMs: timeoutMs ?? DEFAULT_UPDATE_TIMEOUT_MS,
|
||||
timeoutMs: updateTimeoutMs,
|
||||
};
|
||||
const result = await callGatewayTool("update.run", updateGatewayOpts, {
|
||||
sessionKey,
|
||||
note,
|
||||
restartDelayMs,
|
||||
timeoutMs: timeoutMs ?? DEFAULT_UPDATE_TIMEOUT_MS,
|
||||
timeoutMs: updateTimeoutMs,
|
||||
});
|
||||
return jsonResult({ ok: true, result });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user