fix: harden gateway control-plane restart protections

This commit is contained in:
Peter Steinberger
2026-02-19 14:29:44 +01:00
parent 14b4c7fd56
commit ff74d89e86
11 changed files with 523 additions and 16 deletions

View File

@@ -1,3 +1,5 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { GatewayRequestHandlers, RespondFn } from "./types.js";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
import { listChannelPlugins } from "../../channels/plugins/index.js";
import {
@@ -19,7 +21,6 @@ import {
} from "../../config/redact-snapshot.js";
import { buildConfigSchema, type ConfigSchemaResponse } from "../../config/schema.js";
import { extractDeliveryInfo } from "../../config/sessions.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import {
formatDoctorNonInteractiveHint,
type RestartSentinelPayload,
@@ -27,6 +28,12 @@ import {
} from "../../infra/restart-sentinel.js";
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
import { loadOpenClawPlugins } from "../../plugins/loader.js";
import { diffConfigPaths } from "../config-reload.js";
import {
formatControlPlaneActor,
resolveControlPlaneActor,
summarizeChangedPaths,
} from "../control-plane-audit.js";
import {
ErrorCodes,
errorShape,
@@ -38,7 +45,6 @@ import {
} from "../protocol/index.js";
import { resolveBaseHashParam } from "./base-hash.js";
import { parseRestartRequestParams } from "./restart-request.js";
import type { GatewayRequestHandlers, RespondFn } from "./types.js";
import { assertValidParams } from "./validation.js";
function requireConfigBaseHash(
@@ -275,7 +281,7 @@ export const configHandlers: GatewayRequestHandlers = {
undefined,
);
},
"config.patch": async ({ params, respond }) => {
"config.patch": async ({ params, respond, client, context }) => {
if (!assertValidParams(params, validateConfigPatchParams, "config.patch", respond)) {
return;
}
@@ -349,6 +355,11 @@ export const configHandlers: GatewayRequestHandlers = {
);
return;
}
const changedPaths = diffConfigPaths(snapshot.config, validated.config);
const actor = resolveControlPlaneActor(client);
context?.logGateway?.info(
`config.patch write ${formatControlPlaneActor(actor)} changedPaths=${summarizeChangedPaths(changedPaths)} restartReason=config.patch`,
);
await writeConfigFile(validated.config, writeOptions);
const { sessionKey, note, restartDelayMs, deliveryContext, threadId } =
@@ -365,7 +376,18 @@ export const configHandlers: GatewayRequestHandlers = {
const restart = scheduleGatewaySigusr1Restart({
delayMs: restartDelayMs,
reason: "config.patch",
audit: {
actor: actor.actor,
deviceId: actor.deviceId,
clientIp: actor.clientIp,
changedPaths,
},
});
if (restart.coalesced) {
context?.logGateway?.warn(
`config.patch restart coalesced ${formatControlPlaneActor(actor)} delayMs=${restart.delayMs}`,
);
}
respond(
true,
{
@@ -381,7 +403,7 @@ export const configHandlers: GatewayRequestHandlers = {
undefined,
);
},
"config.apply": async ({ params, respond }) => {
"config.apply": async ({ params, respond, client, context }) => {
if (!assertValidParams(params, validateConfigApplyParams, "config.apply", respond)) {
return;
}
@@ -393,6 +415,11 @@ export const configHandlers: GatewayRequestHandlers = {
if (!parsed) {
return;
}
const changedPaths = diffConfigPaths(snapshot.config, parsed.config);
const actor = resolveControlPlaneActor(client);
context?.logGateway?.info(
`config.apply write ${formatControlPlaneActor(actor)} changedPaths=${summarizeChangedPaths(changedPaths)} restartReason=config.apply`,
);
await writeConfigFile(parsed.config, writeOptions);
const { sessionKey, note, restartDelayMs, deliveryContext, threadId } =
@@ -409,7 +436,18 @@ export const configHandlers: GatewayRequestHandlers = {
const restart = scheduleGatewaySigusr1Restart({
delayMs: restartDelayMs,
reason: "config.apply",
audit: {
actor: actor.actor,
deviceId: actor.deviceId,
clientIp: actor.clientIp,
changedPaths,
},
});
if (restart.coalesced) {
context?.logGateway?.warn(
`config.apply restart coalesced ${formatControlPlaneActor(actor)} delayMs=${restart.delayMs}`,
);
}
respond(
true,
{