Skip to content

Commit 738b2be

Browse files
authored
fix: gate active memory global toggles (#97841)
1 parent 3d4b7ca commit 738b2be

2 files changed

Lines changed: 46 additions & 5 deletions

File tree

extensions/active-memory/index.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ describe("active-memory plugin", () => {
471471
};
472472
expect(command.name).toBe("active-memory");
473473
expect(command.acceptsArgs).toBe(true);
474+
expect(command.exposeSenderIsOwner).toBe(true);
474475

475476
const offResult = await command.handler({
476477
channel: "webchat",
@@ -568,6 +569,7 @@ describe("active-memory plugin", () => {
568569
const offResult = await command.handler({
569570
channel: "webchat",
570571
isAuthorizedSender: true,
572+
senderIsOwner: true,
571573
args: "off --global",
572574
commandBody: "/active-memory off --global",
573575
config: {},
@@ -617,6 +619,7 @@ describe("active-memory plugin", () => {
617619
const onResult = await command.handler({
618620
channel: "webchat",
619621
isAuthorizedSender: true,
622+
senderIsOwner: true,
620623
args: "on --global",
621624
commandBody: "/active-memory on --global",
622625
config: {},
@@ -650,6 +653,30 @@ describe("active-memory plugin", () => {
650653
expect(runEmbeddedAgent).toHaveBeenCalledTimes(1);
651654
});
652655

656+
it("blocks external non-owner callers from changing global active-memory config", async () => {
657+
const command = registeredCommands["active-memory"];
658+
659+
for (const args of ["off --global", "on --global"]) {
660+
const result = await command.handler({
661+
channel: "telegram",
662+
isAuthorizedSender: true,
663+
senderIsOwner: false,
664+
args,
665+
commandBody: `/active-memory ${args}`,
666+
config: {},
667+
requestConversationBinding: async () => ({ status: "error", message: "unsupported" }),
668+
detachConversationBinding: async () => ({ removed: false }),
669+
getCurrentConversationBinding: async () => null,
670+
});
671+
672+
expect(result.text).toContain(
673+
"global enable/disable changes require owner or operator.admin",
674+
);
675+
}
676+
677+
expect(api.runtime.config.mutateConfigFile).not.toHaveBeenCalled();
678+
});
679+
653680
it("blocks gateway callers without admin scope from changing global active-memory config", async () => {
654681
const command = registeredCommands["active-memory"];
655682

@@ -674,7 +701,9 @@ describe("active-memory plugin", () => {
674701
getCurrentConversationBinding: async () => null,
675702
});
676703

677-
expect(result.text).toContain("global enable/disable changes require operator.admin");
704+
expect(result.text).toContain(
705+
"global enable/disable changes require owner or operator.admin",
706+
);
678707
}
679708

680709
expect(api.runtime.config.mutateConfigFile).not.toHaveBeenCalled();

extensions/active-memory/index.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -827,12 +827,18 @@ function updateActiveMemoryGlobalEnabledInConfig(
827827
};
828828
}
829829

830-
function requiresAdminToMutateActiveMemoryGlobal(gatewayClientScopes?: readonly string[]): boolean {
831-
return Array.isArray(gatewayClientScopes) && !gatewayClientScopes.includes("operator.admin");
830+
function lacksAdminToMutateActiveMemoryGlobal(params: {
831+
senderIsOwner?: boolean;
832+
gatewayClientScopes?: readonly string[];
833+
}): boolean {
834+
if (Array.isArray(params.gatewayClientScopes)) {
835+
return !params.gatewayClientScopes.includes("operator.admin");
836+
}
837+
return params.senderIsOwner !== true;
832838
}
833839

834840
const ACTIVE_MEMORY_GLOBAL_MUTATION_ADMIN_REQUIRED_TEXT =
835-
"⚠️ /active-memory global enable/disable changes require operator.admin for gateway clients.";
841+
"⚠️ /active-memory global enable/disable changes require owner or operator.admin.";
836842

837843
function normalizePluginConfig(
838844
pluginConfig: unknown,
@@ -3487,6 +3493,7 @@ export default definePluginEntry({
34873493
name: "active-memory",
34883494
description: "Enable, disable, or inspect Active Memory for this session.",
34893495
acceptsArgs: true,
3496+
exposeSenderIsOwner: true,
34903497
handler: async (ctx) => {
34913498
const tokens = ctx.args?.trim().split(/\s+/).filter(Boolean) ?? [];
34923499
const isGlobal = tokens.includes("--global");
@@ -3501,7 +3508,12 @@ export default definePluginEntry({
35013508
text: `Active Memory: ${isActiveMemoryGloballyEnabled(currentConfig) ? "on" : "off"} globally.`,
35023509
};
35033510
}
3504-
if (requiresAdminToMutateActiveMemoryGlobal(ctx.gatewayClientScopes)) {
3511+
if (
3512+
lacksAdminToMutateActiveMemoryGlobal({
3513+
senderIsOwner: ctx.senderIsOwner,
3514+
gatewayClientScopes: ctx.gatewayClientScopes,
3515+
})
3516+
) {
35053517
return {
35063518
text: ACTIVE_MEMORY_GLOBAL_MUTATION_ADMIN_REQUIRED_TEXT,
35073519
};

0 commit comments

Comments
 (0)