From 42e9c9ae1249b080cb7ba33f81dd240a3031bf97 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 6 Mar 2026 01:14:12 -0500 Subject: [PATCH] gateway: reject writes blocked by operator policy --- src/gateway/server-methods/config.ts | 40 +++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/gateway/server-methods/config.ts b/src/gateway/server-methods/config.ts index a0c9cad1955..b70e592126e 100644 --- a/src/gateway/server-methods/config.ts +++ b/src/gateway/server-methods/config.ts @@ -47,6 +47,19 @@ import { parseRestartRequestParams } from "./restart-request.js"; import type { GatewayRequestHandlers, RespondFn } from "./types.js"; import { assertValidParams } from "./validation.js"; +function respondConfigWriteError(respond: RespondFn, error: unknown): boolean { + const message = error instanceof Error ? error.message : String(error); + const code = + error && typeof error === "object" && "code" in error && typeof error.code === "string" + ? error.code + : undefined; + if (code === "OPERATOR_POLICY_LOCKED" || message.startsWith("Invalid operator policy at ")) { + respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, message)); + return true; + } + return false; +} + function requireConfigBaseHash( params: unknown, snapshot: Awaited>, @@ -270,7 +283,14 @@ export const configHandlers: GatewayRequestHandlers = { if (!parsed) { return; } - await writeConfigFile(parsed.config, writeOptions); + try { + await writeConfigFile(parsed.config, writeOptions); + } catch (error) { + if (respondConfigWriteError(respond, error)) { + return; + } + throw error; + } respond( true, { @@ -360,7 +380,14 @@ export const configHandlers: GatewayRequestHandlers = { context?.logGateway?.info( `config.patch write ${formatControlPlaneActor(actor)} changedPaths=${summarizeChangedPaths(changedPaths)} restartReason=config.patch`, ); - await writeConfigFile(validated.config, writeOptions); + try { + await writeConfigFile(validated.config, writeOptions); + } catch (error) { + if (respondConfigWriteError(respond, error)) { + return; + } + throw error; + } const { sessionKey, note, restartDelayMs, deliveryContext, threadId } = resolveConfigRestartRequest(params); @@ -420,7 +447,14 @@ export const configHandlers: GatewayRequestHandlers = { context?.logGateway?.info( `config.apply write ${formatControlPlaneActor(actor)} changedPaths=${summarizeChangedPaths(changedPaths)} restartReason=config.apply`, ); - await writeConfigFile(parsed.config, writeOptions); + try { + await writeConfigFile(parsed.config, writeOptions); + } catch (error) { + if (respondConfigWriteError(respond, error)) { + return; + } + throw error; + } const { sessionKey, note, restartDelayMs, deliveryContext, threadId } = resolveConfigRestartRequest(params);