diff --git a/src/gateway/server-methods/chat.send-deleted-agent.test.ts b/src/gateway/server-methods/chat.send-deleted-agent.test.ts new file mode 100644 index 00000000000..21e51466f27 --- /dev/null +++ b/src/gateway/server-methods/chat.send-deleted-agent.test.ts @@ -0,0 +1,52 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { ErrorCodes } from "../protocol/index.js"; +import type { RespondFn } from "./types.js"; + +const loadSessionEntryMock = vi.fn(); +const resolveDeletedAgentIdFromSessionKeyMock = vi.fn(); + +vi.mock("../session-utils.js", async () => { + const actual = await vi.importActual("../session-utils.js"); + return { + ...actual, + loadSessionEntry: (...args: unknown[]) => loadSessionEntryMock(...args), + resolveDeletedAgentIdFromSessionKey: (...args: unknown[]) => + resolveDeletedAgentIdFromSessionKeyMock(...args), + }; +}); + +import { chatHandlers } from "./chat.js"; + +describe("chat.send deleted-agent guard", () => { + beforeEach(() => { + loadSessionEntryMock.mockReset(); + resolveDeletedAgentIdFromSessionKeyMock.mockReset(); + }); + + it("rejects keys belonging to a deleted agent", async () => { + const orphanKey = "agent:deleted-agent:main"; + loadSessionEntryMock.mockReturnValue({ + cfg: {}, + canonicalKey: orphanKey, + storePath: "/tmp/sessions.json", + entry: { sessionId: "sess-orphan" }, + }); + resolveDeletedAgentIdFromSessionKeyMock.mockReturnValue("deleted-agent"); + + const respond = vi.fn() as unknown as RespondFn; + + await chatHandlers["chat.send"]({ + req: { id: "req-1" } as never, + params: { sessionKey: orphanKey, message: "hi", idempotencyKey: "run-1" }, + respond, + context: {} as never, + client: null, + isWebchatConnect: () => false, + }); + + expect(respond).toHaveBeenCalledWith(false, undefined, { + code: ErrorCodes.INVALID_REQUEST, + message: 'Agent "deleted-agent" no longer exists in configuration', + }); + }); +}); diff --git a/src/gateway/server-methods/chat.ts b/src/gateway/server-methods/chat.ts index 3c351398a2a..dc4e6a2319d 100644 --- a/src/gateway/server-methods/chat.ts +++ b/src/gateway/server-methods/chat.ts @@ -74,6 +74,7 @@ import { capArrayByJsonBytes, loadSessionEntry, resolveGatewayModelSupportsImages, + resolveDeletedAgentIdFromSessionKey, readSessionMessages, resolveSessionModelRef, } from "../session-utils.js"; @@ -1846,6 +1847,18 @@ export const chatHandlers: GatewayRequestHandlers = { } const rawSessionKey = p.sessionKey; const { cfg, entry, canonicalKey: sessionKey } = loadSessionEntry(rawSessionKey); + const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, sessionKey); + if (deletedAgentId !== null) { + respond( + false, + undefined, + errorShape( + ErrorCodes.INVALID_REQUEST, + `Agent "${deletedAgentId}" no longer exists in configuration`, + ), + ); + return; + } const agentId = resolveSessionAgentId({ sessionKey, config: cfg,