openclaw/extensions/matrix/src/exec-approvals.test.ts

458 lines
12 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { afterEach, describe, expect, it } from "vitest";
import {
getMatrixExecApprovalApprovers,
isMatrixExecApprovalApprover,
isMatrixExecApprovalAuthorizedSender,
isMatrixExecApprovalClientEnabled,
isMatrixExecApprovalTargetRecipient,
normalizeMatrixApproverId,
resolveMatrixExecApprovalTarget,
shouldHandleMatrixExecApprovalRequest,
shouldSuppressLocalMatrixExecApprovalPrompt,
} from "./exec-approvals.js";
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
function createTempDir(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-exec-approvals-"));
tempDirs.push(dir);
return dir;
}
function buildConfig(
execApprovals?: NonNullable<NonNullable<OpenClawConfig["channels"]>["matrix"]>["execApprovals"],
channelOverrides?: Partial<NonNullable<NonNullable<OpenClawConfig["channels"]>["matrix"]>>,
): OpenClawConfig {
return {
channels: {
matrix: {
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok",
...channelOverrides,
execApprovals,
},
},
} as OpenClawConfig;
}
describe("matrix exec approvals", () => {
it("requires enablement and an explicit or inferred approver", () => {
expect(isMatrixExecApprovalClientEnabled({ cfg: buildConfig() })).toBe(false);
expect(
isMatrixExecApprovalClientEnabled({
cfg: buildConfig(undefined, { dm: { allowFrom: ["@owner:example.org"] } }),
}),
).toBe(false);
expect(isMatrixExecApprovalClientEnabled({ cfg: buildConfig({ enabled: true }) })).toBe(false);
expect(
isMatrixExecApprovalClientEnabled({
cfg: buildConfig({ enabled: true }, { dm: { allowFrom: ["@owner:example.org"] } }),
}),
).toBe(true);
expect(
isMatrixExecApprovalClientEnabled({
cfg: buildConfig({ enabled: true, approvers: ["@owner:example.org"] }),
}),
).toBe(true);
});
it("prefers explicit approvers when configured", () => {
const cfg = buildConfig(
{ enabled: true, approvers: ["user:@override:example.org"] },
{ dm: { allowFrom: ["@owner:example.org"] } },
);
expect(getMatrixExecApprovalApprovers({ cfg })).toEqual(["@override:example.org"]);
expect(isMatrixExecApprovalApprover({ cfg, senderId: "@override:example.org" })).toBe(true);
expect(isMatrixExecApprovalApprover({ cfg, senderId: "@owner:example.org" })).toBe(false);
});
it("ignores wildcard allowlist entries when inferring exec approvers", () => {
const cfg = buildConfig({ enabled: true }, { dm: { allowFrom: ["*"] } });
expect(getMatrixExecApprovalApprovers({ cfg })).toEqual([]);
expect(isMatrixExecApprovalClientEnabled({ cfg })).toBe(false);
});
it("defaults target to dm", () => {
expect(
resolveMatrixExecApprovalTarget({
cfg: buildConfig({ enabled: true, approvers: ["@owner:example.org"] }),
}),
).toBe("dm");
});
it("matches matrix target recipients from generic approval forwarding targets", () => {
const cfg = {
channels: {
matrix: {
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok",
},
},
approvals: {
exec: {
enabled: true,
mode: "targets",
targets: [
{ channel: "matrix", to: "user:@target:example.org" },
{ channel: "matrix", to: "room:!ops:example.org" },
],
},
},
} as OpenClawConfig;
expect(isMatrixExecApprovalTargetRecipient({ cfg, senderId: "@target:example.org" })).toBe(
true,
);
expect(isMatrixExecApprovalTargetRecipient({ cfg, senderId: "@other:example.org" })).toBe(
false,
);
expect(isMatrixExecApprovalAuthorizedSender({ cfg, senderId: "@target:example.org" })).toBe(
true,
);
});
it("suppresses local prompts only when the native client is enabled", () => {
const payload = {
channelData: {
execApproval: {
approvalId: "req-1",
approvalSlug: "req-1",
agentId: "ops-agent",
sessionKey: "agent:ops-agent:matrix:channel:!ops:example.org",
},
},
};
expect(
shouldSuppressLocalMatrixExecApprovalPrompt({
cfg: buildConfig({ enabled: true, approvers: ["@owner:example.org"] }),
payload,
}),
).toBe(true);
expect(
shouldSuppressLocalMatrixExecApprovalPrompt({
cfg: buildConfig(),
payload,
}),
).toBe(false);
});
it("keeps local prompts when filters exclude the request", () => {
const payload = {
channelData: {
execApproval: {
approvalId: "req-1",
approvalSlug: "req-1",
agentId: "other-agent",
sessionKey: "agent:other-agent:matrix:channel:!ops:example.org",
},
},
};
expect(
shouldSuppressLocalMatrixExecApprovalPrompt({
cfg: buildConfig({
enabled: true,
approvers: ["@owner:example.org"],
agentFilter: ["ops-agent"],
}),
payload,
}),
).toBe(false);
});
it("suppresses local prompts for generic exec payloads when metadata matches filters", () => {
const payload = {
channelData: {
execApproval: {
approvalId: "req-1",
approvalSlug: "req-1",
approvalKind: "exec",
agentId: "ops-agent",
sessionKey: "agent:ops-agent:matrix:channel:!ops:example.org",
},
},
};
expect(
shouldSuppressLocalMatrixExecApprovalPrompt({
cfg: buildConfig({
enabled: true,
approvers: ["@owner:example.org"],
agentFilter: ["ops-agent"],
sessionFilter: ["matrix:channel:"],
}),
payload,
}),
).toBe(true);
});
it("does not suppress local prompts for plugin approval payloads", () => {
const payload = {
channelData: {
execApproval: {
approvalId: "plugin:req-1",
approvalSlug: "plugin:r",
approvalKind: "plugin",
},
},
};
expect(
shouldSuppressLocalMatrixExecApprovalPrompt({
cfg: buildConfig({ enabled: true, approvers: ["@owner:example.org"] }),
payload,
}),
).toBe(false);
});
it("normalizes prefixed approver ids", () => {
expect(normalizeMatrixApproverId("matrix:@owner:example.org")).toBe("@owner:example.org");
expect(normalizeMatrixApproverId("user:@owner:example.org")).toBe("@owner:example.org");
});
it("applies agent and session filters to request handling", () => {
const cfg = buildConfig({
enabled: true,
approvers: ["@owner:example.org"],
agentFilter: ["ops-agent"],
sessionFilter: ["matrix:channel:", "ops$"],
});
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
request: {
id: "req-1",
request: {
command: "echo hi",
agentId: "ops-agent",
sessionKey: "agent:ops-agent:matrix:channel:!room:example.org:ops",
},
createdAtMs: 0,
expiresAtMs: 1000,
},
}),
).toBe(true);
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
request: {
id: "req-2",
request: {
command: "echo hi",
agentId: "other-agent",
sessionKey: "agent:other-agent:matrix:channel:!room:example.org:ops",
},
createdAtMs: 0,
expiresAtMs: 1000,
},
}),
).toBe(false);
});
it("scopes non-matrix turn sources to the stored matrix account", () => {
const tmpDir = createTempDir();
const storePath = path.join(tmpDir, "sessions.json");
fs.writeFileSync(
storePath,
JSON.stringify({
"agent:ops-agent:matrix:channel:!room:example.org": {
sessionId: "main",
updatedAt: 1,
origin: {
provider: "matrix",
accountId: "ops",
},
lastChannel: "slack",
lastTo: "channel:C999",
lastAccountId: "work",
},
}),
"utf-8",
);
const cfg = {
session: { store: storePath },
channels: {
matrix: {
accounts: {
default: {
homeserver: "https://matrix.example.org",
userId: "@bot-default:example.org",
accessToken: "tok-default",
execApprovals: {
enabled: true,
approvers: ["@owner:example.org"],
},
},
ops: {
homeserver: "https://matrix.example.org",
userId: "@bot-ops:example.org",
accessToken: "tok-ops",
execApprovals: {
enabled: true,
approvers: ["@owner:example.org"],
},
},
},
},
},
} as OpenClawConfig;
const request = {
id: "req-3",
request: {
command: "echo hi",
agentId: "ops-agent",
sessionKey: "agent:ops-agent:matrix:channel:!room:example.org",
turnSourceChannel: "slack",
turnSourceTo: "channel:C123",
},
createdAtMs: 0,
expiresAtMs: 1000,
};
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
accountId: "default",
request,
}),
).toBe(false);
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
accountId: "ops",
request,
}),
).toBe(true);
});
it("rejects unbound foreign-channel approvals in multi-account matrix configs", () => {
const cfg = {
channels: {
matrix: {
accounts: {
default: {
homeserver: "https://matrix.example.org",
userId: "@bot-default:example.org",
accessToken: "tok-default",
execApprovals: {
enabled: true,
approvers: ["@owner:example.org"],
},
},
ops: {
homeserver: "https://matrix.example.org",
userId: "@bot-ops:example.org",
accessToken: "tok-ops",
execApprovals: {
enabled: true,
approvers: ["@owner:example.org"],
},
},
},
},
},
} as OpenClawConfig;
const request = {
id: "req-4",
request: {
command: "echo hi",
agentId: "ops-agent",
sessionKey: "agent:ops-agent:missing",
turnSourceChannel: "slack",
turnSourceTo: "channel:C123",
},
createdAtMs: 0,
expiresAtMs: 1000,
};
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
accountId: "default",
request,
}),
).toBe(false);
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
accountId: "ops",
request,
}),
).toBe(false);
});
it("allows unbound foreign-channel approvals when only one matrix account can handle them", () => {
const cfg = {
channels: {
matrix: {
accounts: {
default: {
homeserver: "https://matrix.example.org",
userId: "@bot-default:example.org",
accessToken: "tok-default",
execApprovals: {
enabled: true,
approvers: ["@owner:example.org"],
},
},
ops: {
homeserver: "https://matrix.example.org",
userId: "@bot-ops:example.org",
accessToken: "tok-ops",
execApprovals: {
enabled: false,
approvers: ["@owner:example.org"],
},
},
},
},
},
} as OpenClawConfig;
const request = {
id: "req-5",
request: {
command: "echo hi",
agentId: "ops-agent",
sessionKey: "agent:ops-agent:missing",
turnSourceChannel: "slack",
turnSourceTo: "channel:C123",
},
createdAtMs: 0,
expiresAtMs: 1000,
};
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
accountId: "default",
request,
}),
).toBe(true);
expect(
shouldHandleMatrixExecApprovalRequest({
cfg,
accountId: "ops",
request,
}),
).toBe(false);
});
});