mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-24 10:02:17 +01:00
fix(gateway): require admin for agent session reset
This commit is contained in:
@@ -803,7 +803,10 @@ describe("gateway agent handler", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
idempotencyKey: "test-idem-new",
|
||||
},
|
||||
{ reqId: "4" },
|
||||
{
|
||||
reqId: "4",
|
||||
client: { connect: { scopes: ["operator.admin"] } } as AgentHandlerArgs["client"],
|
||||
},
|
||||
);
|
||||
|
||||
await waitForAssertion(() => expect(mocks.agentCommand).toHaveBeenCalled());
|
||||
@@ -831,7 +834,10 @@ describe("gateway agent handler", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
idempotencyKey: "test-idem-reset-suffix",
|
||||
},
|
||||
{ reqId: "4b" },
|
||||
{
|
||||
reqId: "4b",
|
||||
client: { connect: { scopes: ["operator.admin"] } } as AgentHandlerArgs["client"],
|
||||
},
|
||||
);
|
||||
|
||||
const call = await expectResetCall("[Wed 2026-01-28 20:30 EST] check status");
|
||||
@@ -861,6 +867,34 @@ describe("gateway agent handler", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects /reset for write-scoped gateway callers", async () => {
|
||||
mockMainSessionEntry({ sessionId: "existing-session-id" });
|
||||
mocks.performGatewaySessionReset.mockClear();
|
||||
mocks.agentCommand.mockClear();
|
||||
|
||||
const respond = await invokeAgent(
|
||||
{
|
||||
message: "/reset",
|
||||
sessionKey: "agent:main:main",
|
||||
idempotencyKey: "test-reset-write-scope",
|
||||
},
|
||||
{
|
||||
reqId: "4c",
|
||||
client: { connect: { scopes: ["operator.write"] } } as AgentHandlerArgs["client"],
|
||||
},
|
||||
);
|
||||
|
||||
expect(mocks.performGatewaySessionReset).not.toHaveBeenCalled();
|
||||
expect(mocks.agentCommand).not.toHaveBeenCalled();
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({
|
||||
message: "missing scope: operator.admin",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects malformed session keys in agent.identity.get", async () => {
|
||||
const respond = await invokeAgentIdentityGet(
|
||||
{
|
||||
|
||||
@@ -79,6 +79,10 @@ function resolveAllowModelOverrideFromClient(
|
||||
return resolveSenderIsOwnerFromClient(client) || client?.internal?.allowModelOverride === true;
|
||||
}
|
||||
|
||||
function resolveCanResetSessionFromClient(client: GatewayRequestHandlerOptions["client"]): boolean {
|
||||
return resolveSenderIsOwnerFromClient(client);
|
||||
}
|
||||
|
||||
async function runSessionResetFromAgent(params: {
|
||||
key: string;
|
||||
reason: "new" | "reset";
|
||||
@@ -240,6 +244,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
};
|
||||
const senderIsOwner = resolveSenderIsOwnerFromClient(client);
|
||||
const allowModelOverride = resolveAllowModelOverrideFromClient(client);
|
||||
const canResetSession = resolveCanResetSessionFromClient(client);
|
||||
const requestedModelOverride = Boolean(request.provider || request.model);
|
||||
if (requestedModelOverride && !allowModelOverride) {
|
||||
respond(
|
||||
@@ -378,6 +383,14 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
|
||||
const resetCommandMatch = message.match(RESET_COMMAND_RE);
|
||||
if (resetCommandMatch && requestedSessionKey) {
|
||||
if (!canResetSession) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, `missing scope: ${ADMIN_SCOPE}`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const resetReason = resetCommandMatch[1]?.toLowerCase() === "new" ? "new" : "reset";
|
||||
const resetResult = await runSessionResetFromAgent({
|
||||
key: requestedSessionKey,
|
||||
|
||||
@@ -326,7 +326,7 @@ describe("gateway server agent", () => {
|
||||
expect(call.sessionId).not.toBe("sess-main-before-reset");
|
||||
});
|
||||
|
||||
test("write-scoped callers cannot use sessions.reset directly but can still reset conversations via agent", async () => {
|
||||
test("write-scoped callers cannot reset conversations via agent", async () => {
|
||||
await withGatewayServer(async ({ port }) => {
|
||||
await useTempSessionStorePath();
|
||||
const storePath = testState.sessionStorePath;
|
||||
@@ -358,19 +358,16 @@ describe("gateway server agent", () => {
|
||||
sessionKey: "main",
|
||||
idempotencyKey: "idem-agent-write-reset",
|
||||
});
|
||||
expect(viaAgent.ok).toBe(true);
|
||||
expect(viaAgent.ok).toBe(false);
|
||||
expect(viaAgent.error?.message).toContain("missing scope: operator.admin");
|
||||
|
||||
const store = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
|
||||
string,
|
||||
{ sessionId?: string }
|
||||
>;
|
||||
expect(store["agent:main:main"]?.sessionId).toBeDefined();
|
||||
expect(store["agent:main:main"]?.sessionId).not.toBe("sess-main-before-write-reset");
|
||||
|
||||
await vi.waitFor(() => expect(vi.mocked(agentCommand)).toHaveBeenCalled());
|
||||
const call = readAgentCommandCall();
|
||||
expect(typeof call.sessionId).toBe("string");
|
||||
expect(call.sessionId).not.toBe("sess-main-before-write-reset");
|
||||
expect(store["agent:main:main"]?.sessionId).toBe("sess-main-before-write-reset");
|
||||
expect(vi.mocked(agentCommand)).not.toHaveBeenCalled();
|
||||
|
||||
writeWs.close();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user