From 6cb82eaab8650bcd0fb9cb4d8ae0d60fcf341b3c Mon Sep 17 00:00:00 2001 From: Pavan Kumar Gondhi Date: Tue, 30 Jun 2026 16:59:50 +0530 Subject: [PATCH] fix: require owner for trajectory export (#97840) --- src/auto-reply/reply/commands-info.test.ts | 14 +++++++++++++- src/auto-reply/reply/commands-info.ts | 6 +++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/reply/commands-info.test.ts b/src/auto-reply/reply/commands-info.test.ts index c1f1c66b3dc..a99d26739a3 100644 --- a/src/auto-reply/reply/commands-info.test.ts +++ b/src/auto-reply/reply/commands-info.test.ts @@ -133,7 +133,7 @@ describe("info command handlers", () => { }); }); - it("only lets owners export trajectory bundles", async () => { + it("ignores trajectory export requests from unauthorized senders", async () => { const params = buildInfoParams("/export-trajectory", { commands: { text: true }, } as OpenClawConfig); @@ -145,6 +145,18 @@ describe("info command handlers", () => { expect(buildExportTrajectoryCommandReplyMock).not.toHaveBeenCalled(); }); + it("blocks authorized non-owners from exporting trajectory bundles", async () => { + const params = buildInfoParams("/export-trajectory", { + commands: { text: true }, + } as OpenClawConfig); + params.command.senderIsOwner = false; + + const result = await handleExportTrajectoryCommand(params, true); + + expect(result).toEqual({ shouldContinue: false }); + expect(buildExportTrajectoryCommandReplyMock).not.toHaveBeenCalled(); + }); + it("returns sender details for /whoami", async () => { const result = await handleWhoamiCommand( buildInfoParams( diff --git a/src/auto-reply/reply/commands-info.ts b/src/auto-reply/reply/commands-info.ts index 77b218d2406..2369b133ad1 100644 --- a/src/auto-reply/reply/commands-info.ts +++ b/src/auto-reply/reply/commands-info.ts @@ -15,7 +15,7 @@ import { } from "../status.js"; import { buildThreadingToolContext } from "./agent-runner-utils.js"; import { resolveChannelAccountId } from "./channel-context.js"; -import { rejectUnauthorizedCommand } from "./command-gates.js"; +import { rejectNonOwnerCommand, rejectUnauthorizedCommand } from "./command-gates.js"; import { buildExportSessionReply } from "./commands-export-session.js"; import { buildExportTrajectoryCommandReply } from "./commands-export-trajectory.js"; import { buildStatusPluginsReply, buildStatusReply } from "./commands-status.js"; @@ -348,5 +348,9 @@ export const handleExportTrajectoryCommand: CommandHandler = async (params, allo if (unauthorized) { return unauthorized; } + const nonOwner = rejectNonOwnerCommand(params, "/export-trajectory"); + if (nonOwner) { + return nonOwner; + } return { shouldContinue: false, reply: await buildExportTrajectoryCommandReply(params) }; };