diff --git a/src/media/read-capability.test.ts b/src/media/read-capability.test.ts index a6f5a20ed61..f2000ac4e90 100644 --- a/src/media/read-capability.test.ts +++ b/src/media/read-capability.test.ts @@ -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", () => { diff --git a/src/media/read-capability.ts b/src/media/read-capability.ts index 3c3b3506d37..6e5b45be0b6 100644 --- a/src/media/read-capability.ts +++ b/src/media/read-capability.ts @@ -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 } : {}),