fix: honor group read deny for reply media

This commit is contained in:
Gustavo Madeira Santana
2026-04-14 16:35:11 -04:00
parent f8bc1b77c2
commit 4641d79f49
2 changed files with 38 additions and 22 deletions

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.js";
import { resolveAgentScopedOutboundMediaAccess } from "./read-capability.js";
@@ -7,6 +7,10 @@ vi.mock("../channels/plugins/index.js", () => ({
}));
describe("resolveAgentScopedOutboundMediaAccess", () => {
afterEach(() => {
vi.unstubAllEnvs();
});
it("preserves caller-provided workspaceDir from mediaAccess", () => {
const result = resolveAgentScopedOutboundMediaAccess({
cfg: {} as OpenClawConfig,
@@ -49,12 +53,14 @@ describe("resolveAgentScopedOutboundMediaAccess", () => {
const result = resolveAgentScopedOutboundMediaAccess({
cfg,
sessionKey: "agent:main:whatsapp:group:ops",
mediaSources: ["/Users/peter/Pictures/photo.png"],
// Production call sites set messageProvider: undefined when sessionKey is present;
// resolveGroupToolPolicy derives channel from the session key instead.
requesterSenderId: "attacker",
});
expect(result.readFile).toBeUndefined();
expect(result.localRoots).not.toContain("/Users/peter/Pictures");
});
it("keeps host reads enabled when sender group policy allows read", () => {
@@ -80,10 +86,12 @@ describe("resolveAgentScopedOutboundMediaAccess", () => {
const result = resolveAgentScopedOutboundMediaAccess({
cfg,
sessionKey: "agent:main:whatsapp:group:ops",
mediaSources: ["/Users/peter/Pictures/photo.png"],
requesterSenderId: "trusted-user",
});
expect(result.readFile).toBeTypeOf("function");
expect(result.localRoots).toContain("/Users/peter/Pictures");
});
it("keeps host reads enabled when no group policy applies", () => {

View File

@@ -8,7 +8,10 @@ import type { OpenClawConfig } from "../config/types.js";
import { readLocalFileSafely } from "../infra/fs-safe.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { OutboundMediaAccess, OutboundMediaReadFile } from "./load-options.js";
import { getAgentScopedMediaLocalRootsForSources } from "./local-roots.js";
import {
getAgentScopedMediaLocalRoots,
getAgentScopedMediaLocalRootsForSources,
} from "./local-roots.js";
type OutboundHostMediaPolicyContext = {
sessionKey?: string;
@@ -87,13 +90,16 @@ export function resolveAgentScopedOutboundMediaAccess(
mediaReadFile?: OutboundMediaReadFile;
} & OutboundHostMediaPolicyContext,
): OutboundMediaAccess {
const hostMediaReadAllowed = isAgentScopedHostMediaReadAllowed(params);
const localRoots =
params.mediaAccess?.localRoots ??
getAgentScopedMediaLocalRootsForSources({
cfg: params.cfg,
agentId: params.agentId,
mediaSources: params.mediaSources,
});
(hostMediaReadAllowed
? getAgentScopedMediaLocalRootsForSources({
cfg: params.cfg,
agentId: params.agentId,
mediaSources: params.mediaSources,
})
: getAgentScopedMediaLocalRoots(params.cfg, params.agentId));
const resolvedWorkspaceDir =
params.workspaceDir ??
params.mediaAccess?.workspaceDir ??
@@ -101,21 +107,23 @@ export function resolveAgentScopedOutboundMediaAccess(
const readFile =
params.mediaAccess?.readFile ??
params.mediaReadFile ??
createAgentScopedHostMediaReadFile({
cfg: params.cfg,
agentId: params.agentId,
workspaceDir: resolvedWorkspaceDir,
sessionKey: params.sessionKey,
messageProvider: params.messageProvider,
groupId: params.groupId,
groupChannel: params.groupChannel,
groupSpace: params.groupSpace,
accountId: params.accountId,
requesterSenderId: params.requesterSenderId,
requesterSenderName: params.requesterSenderName,
requesterSenderUsername: params.requesterSenderUsername,
requesterSenderE164: params.requesterSenderE164,
});
(hostMediaReadAllowed
? createAgentScopedHostMediaReadFile({
cfg: params.cfg,
agentId: params.agentId,
workspaceDir: resolvedWorkspaceDir,
sessionKey: params.sessionKey,
messageProvider: params.messageProvider,
groupId: params.groupId,
groupChannel: params.groupChannel,
groupSpace: params.groupSpace,
accountId: params.accountId,
requesterSenderId: params.requesterSenderId,
requesterSenderName: params.requesterSenderName,
requesterSenderUsername: params.requesterSenderUsername,
requesterSenderE164: params.requesterSenderE164,
})
: undefined);
return {
...(localRoots?.length ? { localRoots } : {}),
...(readFile ? { readFile } : {}),