


























@@ -9,24 +9,21 @@ import { setMediaStoreNetworkDepsForTest } from "../media/store.js";
991010const authorizeGatewayHttpRequestOrReplyMock = vi.fn();
1111const resolveOpenAiCompatibleHttpOperatorScopesMock = vi.fn();
12-const getLatestSubagentRunByChildSessionKeyMock = vi.fn();
12+const resolveOpenAiCompatibleHttpSenderIsOwnerMock = vi.fn();
1313const loadSessionEntryMock = vi.fn();
1414const readSessionMessagesMock = vi.fn();
15151616vi.mock("./http-utils.js", () => ({
1717authorizeGatewayHttpRequestOrReply: authorizeGatewayHttpRequestOrReplyMock,
1818resolveOpenAiCompatibleHttpOperatorScopes: resolveOpenAiCompatibleHttpOperatorScopesMock,
19+resolveOpenAiCompatibleHttpSenderIsOwner: resolveOpenAiCompatibleHttpSenderIsOwnerMock,
1920}));
20212122vi.mock("./session-utils.js", () => ({
2223loadSessionEntry: loadSessionEntryMock,
2324readSessionMessagesAsync: readSessionMessagesMock,
2425}));
252626-vi.mock("../agents/subagent-registry.js", () => ({
27-getLatestSubagentRunByChildSessionKey: getLatestSubagentRunByChildSessionKeyMock,
28-}));
29-3027const {
3128DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS,
3229 attachManagedOutgoingImagesToMessage,
@@ -128,7 +125,6 @@ async function requestManagedImage(params: {
128125authResponse?: Record<string, unknown>;
129126headers?: Record<string, string>;
130127transcriptMessages?: Record<string, unknown>[];
131-subagentRun?: Record<string, unknown> | null;
132128sessionEntry?: { sessionId: string; sessionFile?: string };
133129}) {
134130authorizeGatewayHttpRequestOrReplyMock.mockImplementation(async ({ res }) => {
@@ -140,7 +136,15 @@ async function requestManagedImage(params: {
140136return { ok: true, ...params.authResponse };
141137});
142138resolveOpenAiCompatibleHttpOperatorScopesMock.mockReturnValue(params.scopes ?? ["operator.read"]);
143-getLatestSubagentRunByChildSessionKeyMock.mockReturnValue(params.subagentRun ?? null);
139+resolveOpenAiCompatibleHttpSenderIsOwnerMock.mockImplementation((_req, requestAuth) => {
140+if (requestAuth.authMethod === "token" || requestAuth.authMethod === "password") {
141+return true;
142+}
143+return (
144+requestAuth.trustDeclaredOperatorScopes === true &&
145+(params.scopes ?? ["operator.read"]).includes("operator.admin")
146+);
147+});
144148loadSessionEntryMock.mockReturnValue({
145149storePath: path.join(params.stateDir, "gateway-sessions.json"),
146150entry: params.sessionEntry ?? { sessionId: "sess-1", sessionFile: "session.jsonl" },
@@ -237,7 +241,7 @@ describe("handleManagedOutgoingImageHttpRequest", () => {
237241const { result } = await requestManagedImage({
238242 stateDir,
239243pathName: `/api/chat/media/outgoing/${encodeURIComponent(sessionKey)}/${attachmentId}/full`,
240-headers: { "x-openclaw-requester-session-key": sessionKey },
244+authResponse: { authMethod: "token" },
241245});
242246243247expect(result.statusCode).toBe(200);
@@ -259,25 +263,40 @@ describe("handleManagedOutgoingImageHttpRequest", () => {
259263expect(result.body.byteLength).toBe(0);
260264});
261265262-it("rejects requests from unrelated sessions", async () => {
266+it("rejects non-owner trusted-proxy requests with self-declared session ownership", async () => {
263267const { attachmentId, sessionKey } = await createFixture(stateDir);
264268265269const { result } = await requestManagedImage({
266270 stateDir,
267271pathName: `/api/chat/media/outgoing/${encodeURIComponent(sessionKey)}/${attachmentId}/full`,
268-headers: { "x-openclaw-requester-session-key": "agent:main:other" },
272+authResponse: { authMethod: "trusted-proxy", trustDeclaredOperatorScopes: true },
273+headers: { "x-openclaw-requester-session-key": sessionKey },
269274});
270275271276expect(result.statusCode).toBe(403);
272277});
273278274-it("allows device-token access without requester session ownership", async () => {
279+it("rejects device-token access with self-declared session ownership", async () => {
275280const { attachmentId, sessionKey } = await createFixture(stateDir);
276281277282const { result } = await requestManagedImage({
278283 stateDir,
279284pathName: `/api/chat/media/outgoing/${encodeURIComponent(sessionKey)}/${attachmentId}/full`,
280285authResponse: { authMethod: "device-token" },
286+headers: { "x-openclaw-requester-session-key": sessionKey },
287+});
288+289+expect(result.statusCode).toBe(403);
290+});
291+292+it("serves owner trusted-proxy requests with admin scope", async () => {
293+const { attachmentId, sessionKey } = await createFixture(stateDir);
294+295+const { result } = await requestManagedImage({
296+ stateDir,
297+pathName: `/api/chat/media/outgoing/${encodeURIComponent(sessionKey)}/${attachmentId}/full`,
298+authResponse: { authMethod: "trusted-proxy", trustDeclaredOperatorScopes: true },
299+scopes: ["operator.admin"],
281300});
282301283302expect(result.statusCode).toBe(200);
@@ -332,14 +351,14 @@ describe("handleManagedOutgoingImageHttpRequest", () => {
332351const first = await requestManagedImage({
333352 stateDir,
334353 pathName,
335-headers: { "x-openclaw-requester-session-key": sessionKey },
354+authResponse: { authMethod: "token" },
336355sessionEntry: { sessionId: "sess-main", sessionFile },
337356 transcriptMessages,
338357});
339358const second = await requestManagedImage({
340359 stateDir,
341360 pathName,
342-headers: { "x-openclaw-requester-session-key": sessionKey },
361+authResponse: { authMethod: "token" },
343362sessionEntry: { sessionId: "sess-main", sessionFile },
344363 transcriptMessages,
345364});
@@ -354,7 +373,7 @@ describe("handleManagedOutgoingImageHttpRequest", () => {
354373const third = await requestManagedImage({
355374 stateDir,
356375 pathName,
357-headers: { "x-openclaw-requester-session-key": sessionKey },
376+authResponse: { authMethod: "token" },
358377sessionEntry: { sessionId: "sess-main", sessionFile },
359378 transcriptMessages,
360379});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。