open-design/apps/daemon/tests/media-policy-routes.test.ts
Denis Redozubov f4c5d22f22
fix(daemon): confine sandbox project roots and host discovery (#3243)
* fix(daemon): confine sandbox project and host discovery

* fix(daemon): resolve sandbox data dir for toolchain discovery

* fix(daemon): resolve sandbox data dir for agent env

* fix(daemon): fail fast for sandbox imported folders

* test(daemon): assert sandbox imported folder rejection

* fix(daemon): keep sandbox import guard at run start

* fix(daemon): reject sandbox imported project file roots

* fix(daemon): preserve imported project detail roots

* test(daemon): expect sandbox profiles to stay scoped

* fix(daemon): bypass proxies for agent tool callbacks

* test(daemon): isolate media policy route memory extraction

* fix(daemon): keep loopback no-proxy scoped to sandbox
2026-05-30 16:57:04 +00:00

590 lines
20 KiB
TypeScript

import type http from 'node:http';
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { startServer } from '../src/server.js';
import { memoryDir, writeMemoryConfig } from '../src/memory.js';
type FakeMediaEndpoint = 'tool' | 'legacy';
interface FakeMediaAgentOptions {
endpoint?: FakeMediaEndpoint;
attachToken?: boolean;
}
describe('run-scoped media policy routes', () => {
let tempDir: string;
let binDir: string;
let oldPath: string | undefined;
let oldCapture: string | undefined;
let oldMemoryConfigRaw: string | null = null;
let server: http.Server | null = null;
let shutdown: (() => Promise<void> | void) | undefined;
beforeEach(async () => {
tempDir = await mkdtemp(path.join(os.tmpdir(), 'od-media-policy-route-'));
binDir = await mkdtemp(path.join(os.tmpdir(), 'od-media-policy-bin-'));
oldPath = process.env.PATH;
oldCapture = process.env.OD_CAPTURE_MEDIA_RESPONSE;
process.env.PATH = `${binDir}${path.delimiter}${oldPath ?? ''}`;
const memoryConfig = memoryConfigPath();
oldMemoryConfigRaw = await readFile(memoryConfig, 'utf8').catch(() => null);
await writeMemoryConfig(process.env.OD_DATA_DIR!, {
chatExtractionEnabled: false,
extraction: null,
});
});
afterEach(async () => {
await Promise.resolve(shutdown?.());
shutdown = undefined;
if (server) {
await new Promise<void>((resolve) => server?.close(() => resolve()));
server = null;
}
if (oldPath === undefined) delete process.env.PATH;
else process.env.PATH = oldPath;
if (oldCapture === undefined) delete process.env.OD_CAPTURE_MEDIA_RESPONSE;
else process.env.OD_CAPTURE_MEDIA_RESPONSE = oldCapture;
const memoryConfig = memoryConfigPath();
if (oldMemoryConfigRaw === null) {
await rm(memoryConfig, { force: true });
} else {
await mkdir(path.dirname(memoryConfig), { recursive: true });
await writeFile(memoryConfig, oldMemoryConfigRaw);
}
oldMemoryConfigRaw = null;
await rm(tempDir, { recursive: true, force: true });
await rm(binDir, { recursive: true, force: true });
});
it('rejects in-run media generation when media execution is disabled', async () => {
const capturePath = path.join(tempDir, 'media-disabled-response.json');
await writeFakeAgent(capturePath, {
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
output: 'poster.png',
});
const { url, projectId, conversationId } = await startProjectServer('Disabled media project');
const createResponse = await fetch(`${url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId,
conversationId,
message: 'Try to create a poster image.',
mediaExecution: { mode: 'disabled' },
}),
});
expect(createResponse.status).toBe(202);
const captured = await waitForCapturedMediaResponse(capturePath);
expect(captured.status).toBe(403);
expect(captured.tokenAvailable).toBe(true);
expect(captured.body.error).toMatchObject({
code: 'MEDIA_EXECUTION_DISABLED',
});
});
it('preserves no-token legacy media generation when run media execution is disabled', async () => {
const capturePath = path.join(tempDir, 'legacy-no-token-disabled-response.json');
await writeFakeAgent(
capturePath,
{
surface: 'image',
model: 'test-image-model',
prompt: 'Create a launch poster',
output: 'poster.png',
},
{ endpoint: 'legacy', attachToken: false },
);
const { url, projectId, conversationId } = await startProjectServer(
'Legacy no-token media project',
);
const createResponse = await fetch(`${url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId,
conversationId,
message: 'Try to create a poster image through the legacy path.',
mediaExecution: { mode: 'disabled' },
}),
});
expect(createResponse.status).toBe(202);
const captured = await waitForCapturedMediaResponse(capturePath);
expect(captured.status).toBe(202);
expect(captured.tokenAvailable).toBe(true);
expect(captured.tokenAttached).toBe(false);
});
it('allows token-bearing legacy media generation when enabled policy permits it', async () => {
const capturePath = path.join(tempDir, 'legacy-token-enabled-response.json');
await writeFakeAgent(
capturePath,
{
surface: 'image',
model: 'test-image-model',
prompt: 'Create a launch poster',
output: 'poster.png',
},
{ endpoint: 'legacy' },
);
const { url, projectId, conversationId } = await startProjectServer(
'Legacy token-enabled media project',
);
const createResponse = await fetch(`${url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId,
conversationId,
message: 'Try to create a poster image through the legacy path.',
mediaExecution: {
mode: 'enabled',
allowedSurfaces: ['image'],
allowedModels: ['test-image-model'],
},
}),
});
expect(createResponse.status).toBe(202);
const captured = await waitForCapturedMediaResponse(capturePath);
expect(captured.status).toBe(202);
expect(captured.tokenAvailable).toBe(true);
expect(captured.tokenAttached).toBe(true);
});
it('rejects token-bearing legacy media generation when media execution is disabled', async () => {
const capturePath = path.join(tempDir, 'legacy-token-disabled-response.json');
await writeFakeAgent(
capturePath,
{
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
output: 'poster.png',
},
{ endpoint: 'legacy' },
);
const { url, projectId, conversationId } = await startProjectServer(
'Legacy token-disabled media project',
);
const createResponse = await fetch(`${url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId,
conversationId,
message: 'Try to create a poster image through the legacy path.',
mediaExecution: { mode: 'disabled' },
}),
});
expect(createResponse.status).toBe(202);
const captured = await waitForCapturedMediaResponse(capturePath);
expect(captured.status).toBe(403);
expect(captured.tokenAvailable).toBe(true);
expect(captured.tokenAttached).toBe(true);
expect(captured.body.error).toMatchObject({
code: 'MEDIA_EXECUTION_DISABLED',
});
});
it('rejects disallowed surfaces and models on token-bearing legacy media generation', async () => {
const surfaceCapturePath = path.join(tempDir, 'legacy-surface-denied.json');
await writeFakeAgent(
surfaceCapturePath,
{
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
output: 'poster.png',
},
{ endpoint: 'legacy' },
);
const surfaceProject = await startProjectServer('Legacy surface denied media project');
const surfaceResponse = await fetch(`${surfaceProject.url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId: surfaceProject.projectId,
conversationId: surfaceProject.conversationId,
message: 'Try to create a poster image through the legacy path.',
mediaExecution: {
mode: 'enabled',
allowedSurfaces: ['video'],
},
}),
});
expect(surfaceResponse.status).toBe(202);
const surfaceCaptured = await waitForCapturedMediaResponse(surfaceCapturePath);
expect(surfaceCaptured.status).toBe(403);
expect(surfaceCaptured.tokenAttached).toBe(true);
expect(surfaceCaptured.body.error).toMatchObject({
code: 'MEDIA_SURFACE_DENIED',
});
const modelCapturePath = path.join(tempDir, 'legacy-model-denied.json');
await writeFakeAgent(
modelCapturePath,
{
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
output: 'poster.png',
},
{ endpoint: 'legacy' },
);
const modelProject = await startProjectServer('Legacy model denied media project');
const modelResponse = await fetch(`${modelProject.url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId: modelProject.projectId,
conversationId: modelProject.conversationId,
message: 'Try to create a poster image through the legacy path.',
mediaExecution: {
mode: 'enabled',
allowedModels: ['different-image-model'],
},
}),
});
expect(modelResponse.status).toBe(202);
const modelCaptured = await waitForCapturedMediaResponse(modelCapturePath);
expect(modelCaptured.status).toBe(403);
expect(modelCaptured.tokenAttached).toBe(true);
expect(modelCaptured.body.error).toMatchObject({
code: 'MEDIA_MODEL_DENIED',
});
});
it('rejects disallowed surfaces and models on token-gated media generation', async () => {
const surfaceCapturePath = path.join(tempDir, 'media-surface-denied.json');
await writeFakeAgent(surfaceCapturePath, {
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
output: 'poster.png',
});
const surfaceProject = await startProjectServer('Surface denied media project');
const surfaceResponse = await fetch(`${surfaceProject.url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId: surfaceProject.projectId,
conversationId: surfaceProject.conversationId,
message: 'Try to create a poster image.',
mediaExecution: {
mode: 'enabled',
allowedSurfaces: ['video'],
},
}),
});
expect(surfaceResponse.status).toBe(202);
const surfaceCaptured = await waitForCapturedMediaResponse(surfaceCapturePath);
expect(surfaceCaptured.status).toBe(403);
expect(surfaceCaptured.body.error).toMatchObject({
code: 'MEDIA_SURFACE_DENIED',
});
const modelCapturePath = path.join(tempDir, 'media-model-denied.json');
await writeFakeAgent(modelCapturePath, {
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
output: 'poster.png',
});
const modelProject = await startProjectServer('Model denied media project');
const modelResponse = await fetch(`${modelProject.url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId: modelProject.projectId,
conversationId: modelProject.conversationId,
message: 'Try to create a poster image.',
mediaExecution: {
mode: 'enabled',
allowedModels: ['different-image-model'],
},
}),
});
expect(modelResponse.status).toBe(202);
const modelCaptured = await waitForCapturedMediaResponse(modelCapturePath);
expect(modelCaptured.status).toBe(403);
expect(modelCaptured.body.error).toMatchObject({
code: 'MEDIA_MODEL_DENIED',
});
});
it('applies legacy chat disabled policy to the spawned media tool path', async () => {
const capturePath = path.join(tempDir, 'legacy-chat-disabled-response.json');
await writeFakeAgent(capturePath, {
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
output: 'poster.png',
});
const { url, projectId, conversationId } = await startProjectServer(
'Legacy chat disabled media project',
);
const chatResponse = await fetch(`${url}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'opencode',
projectId,
conversationId,
message: 'Create a poster image from legacy chat.',
mediaExecution: { mode: 'disabled' },
}),
});
expect(chatResponse.status).toBe(200);
const captured = await waitForCapturedMediaResponse(capturePath);
expect(captured.status).toBe(403);
expect(captured.tokenAvailable).toBe(true);
expect(captured.body.error).toMatchObject({
code: 'MEDIA_EXECUTION_DISABLED',
});
await chatResponse.text();
});
it('defaults omitted mediaExecution to enabled on run and legacy chat creation', async () => {
const { url, projectId, conversationId } = await startProjectServer('Default policy project');
const runResponse = await fetch(`${url}/api/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: `missing-agent-${randomUUID()}`,
projectId,
conversationId,
message: 'Create a poster image.',
}),
});
expect(runResponse.status).toBe(202);
const runBody = await runResponse.json() as { runId: string };
const runStatusResponse = await fetch(`${url}/api/runs/${encodeURIComponent(runBody.runId)}`);
const runStatus = await runStatusResponse.json() as {
mediaExecution?: { mode?: string };
};
expect(runStatus.mediaExecution).toEqual({ mode: 'enabled' });
const legacyConversationId = `legacy-${randomUUID()}`;
const chatResponse = await fetch(`${url}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: `missing-agent-${randomUUID()}`,
projectId,
conversationId: legacyConversationId,
message: 'Create a poster image.',
}),
});
expect(chatResponse.status).toBe(200);
await chatResponse.text();
const runsResponse = await fetch(
`${url}/api/runs?conversationId=${encodeURIComponent(legacyConversationId)}`,
);
const runsBody = await runsResponse.json() as {
runs: Array<{ mediaExecution?: { mode?: string } }>;
};
expect(runsBody.runs).toHaveLength(1);
expect(runsBody.runs[0]?.mediaExecution).toEqual({ mode: 'enabled' });
});
it('fails closed when a media tool request does not carry a valid run token', async () => {
const started = await startServer({ port: 0, returnServer: true }) as {
url: string;
server: http.Server;
shutdown?: () => Promise<void> | void;
};
server = started.server;
shutdown = started.shutdown;
const response = await fetch(`${started.url}/api/tools/media/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer invalid-token',
},
body: JSON.stringify({
surface: 'image',
model: 'gpt-image-2',
prompt: 'Create a launch poster',
}),
});
expect(response.status).toBe(401);
const body = await response.json() as { error?: { code?: string } };
expect(body.error).toMatchObject({ code: 'TOOL_TOKEN_INVALID' });
});
async function startProjectServer(name: string): Promise<{
url: string;
projectId: string;
conversationId: string;
}> {
if (server) {
await Promise.resolve(shutdown?.());
shutdown = undefined;
await new Promise<void>((resolve) => server?.close(() => resolve()));
server = null;
}
const projectId = `project_${randomUUID()}`;
const started = await startServer({ port: 0, returnServer: true }) as {
url: string;
server: http.Server;
shutdown?: () => Promise<void> | void;
};
server = started.server;
shutdown = started.shutdown;
const projectResponse = await fetch(`${started.url}/api/projects`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: projectId,
name,
metadata: { kind: 'image' },
}),
});
expect(projectResponse.status).toBe(200);
const projectBody = await projectResponse.json() as { conversationId: string };
return {
url: started.url,
projectId,
conversationId: projectBody.conversationId,
};
}
function memoryConfigPath(): string {
return path.join(memoryDir(process.env.OD_DATA_DIR!), '.config.json');
}
async function writeFakeAgent(
capturePath: string,
requestBody: unknown,
options: FakeMediaAgentOptions = {},
): Promise<void> {
const endpoint = options.endpoint ?? 'tool';
const attachToken = options.attachToken ?? true;
const source = `#!/usr/bin/env node
const fs = require('node:fs');
const endpoint = ${JSON.stringify(endpoint)};
const attachToken = ${JSON.stringify(attachToken)};
(async () => {
if (process.argv.includes('--version')) {
console.log('opencode 1.0.0');
return;
}
if (process.argv[2] === 'models') {
console.log('default');
return;
}
if (process.argv[2] !== 'run') {
return;
}
const token = process.env.OD_TOOL_TOKEN;
const daemonUrl = process.env.OD_DAEMON_URL;
const projectId = process.env.OD_PROJECT_ID;
const url = endpoint === 'legacy'
? daemonUrl + '/api/projects/' + encodeURIComponent(projectId || '') + '/media/generate'
: daemonUrl + '/api/tools/media/generate';
const headers = { 'content-type': 'application/json' };
if (attachToken && token) {
headers.authorization = 'Bearer ' + token;
}
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(${JSON.stringify(requestBody)}),
});
const text = await response.text();
let body;
try {
body = JSON.parse(text);
} catch {
body = { raw: text };
}
fs.writeFileSync(process.env.OD_CAPTURE_MEDIA_RESPONSE, JSON.stringify({
status: response.status,
tokenAvailable: Boolean(token),
tokenAttached: Boolean(attachToken && token),
endpoint,
url,
body,
}));
console.log(JSON.stringify({ type: 'text', part: { text: 'media policy checked' } }));
})().catch((error) => {
fs.writeFileSync(process.env.OD_CAPTURE_MEDIA_RESPONSE, JSON.stringify({
status: 0,
tokenAvailable: Boolean(process.env.OD_TOOL_TOKEN),
tokenAttached: Boolean(attachToken && process.env.OD_TOOL_TOKEN),
endpoint,
body: { error: String(error && error.message ? error.message : error) },
}));
process.exit(1);
});
`;
if (process.platform === 'win32') {
const runner = path.join(binDir, 'opencode-runner.cjs');
await writeFile(runner, source.replace(/^#![^\n]*\n/, ''));
await writeFile(
path.join(binDir, 'opencode.cmd'),
`@echo off\r\nnode "${runner}" %*\r\n`,
);
} else {
const bin = path.join(binDir, 'opencode');
await writeFile(bin, source);
await chmod(bin, 0o755);
}
process.env.OD_CAPTURE_MEDIA_RESPONSE = capturePath;
}
async function waitForCapturedMediaResponse(capturePath: string): Promise<{
status: number;
tokenAvailable: boolean;
tokenAttached?: boolean;
endpoint?: FakeMediaEndpoint;
url?: string;
body: any;
}> {
const startedAt = Date.now();
while (Date.now() - startedAt < 10_000) {
try {
return JSON.parse(await readFile(capturePath, 'utf8'));
} catch {
await new Promise((resolve) => setTimeout(resolve, 50));
}
}
throw new Error('timed out waiting for fake agent media response');
}
});