import { chmod, mkdir, mkdtemp, readFile, rename, rm, stat, symlink, writeFile, } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { AgentEnvBoundaryError, parseAgentEnvironment, renderGeneratedAgentEnvironment, writeAgentEnvironmentProjection, } from './generated-env-boundary.js'; const generatedValues = { MOSAIC_AGENT_NAME: 'coder0', MOSAIC_AGENT_CLASS: 'code', MOSAIC_AGENT_RUNTIME: 'pi', MOSAIC_AGENT_MODEL: 'openai-codex/gpt-5.6-sol', MOSAIC_AGENT_REASONING: 'high', MOSAIC_AGENT_TOOL_POLICY: 'code', MOSAIC_AGENT_WORKDIR: '/srv/mosaic', MOSAIC_TMUX_SOCKET: 'mosaic-fleet', }; describe('generated fleet agent environment boundary', (): void => { let cleanup: string | undefined; afterEach(async (): Promise => { if (cleanup) { await rm(cleanup, { recursive: true, force: true }); cleanup = undefined; } }); it('renders a deterministic complete generated projection', (): void => { expect(renderGeneratedAgentEnvironment(generatedValues)).toBe( [ 'MOSAIC_AGENT_NAME=coder0', 'MOSAIC_AGENT_CLASS=code', 'MOSAIC_AGENT_RUNTIME=pi', 'MOSAIC_AGENT_MODEL=openai-codex/gpt-5.6-sol', 'MOSAIC_AGENT_REASONING=high', 'MOSAIC_AGENT_TOOL_POLICY=code', 'MOSAIC_AGENT_WORKDIR=/srv/mosaic', 'MOSAIC_TMUX_SOCKET=mosaic-fleet', '', ].join('\n'), ); }); it.each([ ['generated-key shadowing', 'MOSAIC_AGENT_RUNTIME=codex\n'], ['unknown key', 'UNRELATED_SETTING=value\n'], ['arbitrary command', 'MOSAIC_AGENT_COMMAND=mosaic yolo codex\n'], ['sensitive key', 'MOSAIC_AGENT_TOKEN=do-not-log-me\n'], ['malformed syntax', 'export MOSAIC_RUNTIME_BIN=/opt/bin\n'], ['duplicate keys', 'MOSAIC_RUNTIME_BIN=/opt/bin\nMOSAIC_RUNTIME_BIN=/other/bin\n'], ])('rejects local %s without echoing values', (_name: string, source: string): void => { let error: unknown; try { parseAgentEnvironment(source, 'local'); } catch (caught: unknown) { error = caught; } expect(error).toBeInstanceOf(AgentEnvBoundaryError); expect(String(error)).not.toContain('mosaic yolo codex'); expect(String(error)).not.toContain('do-not-log-me'); expect(String(error)).toMatch(/key=.*sha256=/); }); it('rejects unsafe generated paths before any launch consumer can use them', (): void => { expect((): void => { renderGeneratedAgentEnvironment({ ...generatedValues, MOSAIC_AGENT_WORKDIR: '../outside', }); }).toThrow(AgentEnvBoundaryError); }); it('creates missing managed directories privately before writing a projection', async (): Promise => { cleanup = await mkdtemp(join(tmpdir(), 'mosaic-generated-env-')); const mosaicHome = join(cleanup, 'mosaic'); const agentEnvDir = join(mosaicHome, 'fleet', 'agents'); const result = await writeAgentEnvironmentProjection({ mosaicHome, agentEnvDir, agentName: 'coder0', generated: generatedValues, }); for (const directory of [mosaicHome, join(mosaicHome, 'fleet'), agentEnvDir]) { expect((await stat(directory)).mode & 0o777).toBe(0o700); } expect((await stat(result.generatedPath)).mode & 0o777).toBe(0o600); }); it('regenerates desired keys, relocates safe legacy local data, and quarantines forbidden legacy input', async (): Promise => { cleanup = await mkdtemp(join(tmpdir(), 'mosaic-generated-env-')); const mosaicHome = join(cleanup, 'mosaic'); const agentEnvDir = join(mosaicHome, 'fleet', 'agents'); await mkdir(agentEnvDir, { recursive: true, mode: 0o700 }); await writeFile( join(agentEnvDir, 'coder0.env'), [ 'MOSAIC_AGENT_NAME=stale-name', 'MOSAIC_AGENT_RUNTIME=codex', 'MOSAIC_RUNTIME_BIN=/opt/mosaic/bin', 'MOSAIC_AGENT_COMMAND=mosaic yolo codex --dangerous', '', ].join('\n'), { mode: 0o600 }, ); const result = await writeAgentEnvironmentProjection({ mosaicHome, agentEnvDir, agentName: 'coder0', generated: generatedValues, }); expect(await readFile(result.generatedPath, 'utf8')).toBe( renderGeneratedAgentEnvironment(generatedValues), ); expect(await readFile(result.localPath, 'utf8')).toBe('MOSAIC_RUNTIME_BIN=/opt/mosaic/bin\n'); const quarantine = await readFile(result.quarantinePath!, 'utf8'); expect(quarantine).toContain('MOSAIC_AGENT_COMMAND=mosaic yolo codex --dangerous'); await expect(readFile(join(agentEnvDir, 'coder0.env'), 'utf8')).rejects.toThrow(); expect((await stat(result.generatedPath)).mode & 0o077).toBe(0); expect((await stat(result.localPath)).mode & 0o077).toBe(0); expect(result.diagnostics).toEqual([expect.objectContaining({ key: 'MOSAIC_AGENT_COMMAND' })]); expect(JSON.stringify(result.diagnostics)).not.toContain('mosaic yolo codex --dangerous'); }); it('fails closed on an existing local override with unsafe permissions', async (): Promise => { cleanup = await mkdtemp(join(tmpdir(), 'mosaic-generated-env-')); const mosaicHome = join(cleanup, 'mosaic'); const agentEnvDir = join(mosaicHome, 'fleet', 'agents'); await mkdir(agentEnvDir, { recursive: true, mode: 0o700 }); const localPath = join(agentEnvDir, 'coder0.env.local'); await writeFile(localPath, 'MOSAIC_RUNTIME_BIN=/opt/mosaic/bin\n', { mode: 0o600 }); await chmod(localPath, 0o644); await expect( writeAgentEnvironmentProjection({ mosaicHome, agentEnvDir, agentName: 'coder0', generated: generatedValues, }), ).rejects.toThrow(/permissions/i); }); it('rejects an existing group/world-accessible projection directory before reading it', async (): Promise => { cleanup = await mkdtemp(join(tmpdir(), 'mosaic-generated-env-')); const mosaicHome = join(cleanup, 'mosaic'); const agentEnvDir = join(mosaicHome, 'fleet', 'agents'); const localPath = join(agentEnvDir, 'coder0.env.local'); const generatedPath = join(agentEnvDir, 'coder0.env.generated'); await mkdir(agentEnvDir, { recursive: true, mode: 0o700 }); await writeFile(localPath, 'MOSAIC_RUNTIME_BIN=/opt/mosaic/bin\n', { mode: 0o600 }); await chmod(agentEnvDir, 0o777); await expect( writeAgentEnvironmentProjection({ mosaicHome, agentEnvDir, agentName: 'coder0', generated: generatedValues, }), ).rejects.toThrow(/unsafe-permissions/i); await expect(readFile(localPath, 'utf8')).resolves.toBe('MOSAIC_RUNTIME_BIN=/opt/mosaic/bin\n'); await expect(readFile(generatedPath, 'utf8')).rejects.toThrow(); }); it.each([ ['MOSAIC_HOME', 'symlink'], ['MOSAIC_HOME/fleet', 'symlink'], ['MOSAIC_HOME/fleet/agents', 'symlink'], ['MOSAIC_HOME', 'group/world-writable'], ['MOSAIC_HOME/fleet', 'group/world-writable'], ['MOSAIC_HOME/fleet/agents', 'group/world-writable'], ])( 'rejects a %s %s managed ancestor before any projection mutation', async (ancestor: string, hazard: string): Promise => { cleanup = await mkdtemp(join(tmpdir(), 'mosaic-generated-env-')); const mosaicHome = join(cleanup, 'mosaic'); const fleetDir = join(mosaicHome, 'fleet'); const agentEnvDir = join(fleetDir, 'agents'); const generatedPath = join(agentEnvDir, 'coder0.env.generated'); const localPath = join(agentEnvDir, 'coder0.env.local'); const legacyPath = join(agentEnvDir, 'coder0.env'); const quarantinePath = join(agentEnvDir, 'coder0.env.quarantine'); const managedPaths: Record = { MOSAIC_HOME: mosaicHome, 'MOSAIC_HOME/fleet': fleetDir, 'MOSAIC_HOME/fleet/agents': agentEnvDir, }; const unsafePath = managedPaths[ancestor]; if (unsafePath === undefined) throw new Error(`Unknown managed ancestor: ${ancestor}`); await mkdir(agentEnvDir, { recursive: true, mode: 0o700 }); await chmod(mosaicHome, 0o700); await chmod(fleetDir, 0o700); await chmod(agentEnvDir, 0o700); await writeFile(generatedPath, 'generated-before\n', { mode: 0o600 }); await writeFile(localPath, 'MOSAIC_RUNTIME_BIN=/safe/runtime\n', { mode: 0o600 }); await writeFile(legacyPath, 'MOSAIC_AGENT_COMMAND=legacy-command\n', { mode: 0o600 }); if (hazard === 'symlink') { const targetPath = `${unsafePath}-target`; await rename(unsafePath, targetPath); await symlink(targetPath, unsafePath, 'dir'); } else { await chmod(unsafePath, 0o777); } await expect( writeAgentEnvironmentProjection({ mosaicHome, agentEnvDir, agentName: 'coder0', generated: generatedValues, }), ).rejects.toThrow(/unsafe-(directory|permissions)/i); await expect(readFile(generatedPath, 'utf8')).resolves.toBe('generated-before\n'); await expect(readFile(localPath, 'utf8')).resolves.toBe('MOSAIC_RUNTIME_BIN=/safe/runtime\n'); await expect(readFile(legacyPath, 'utf8')).resolves.toBe( 'MOSAIC_AGENT_COMMAND=legacy-command\n', ); await expect(readFile(quarantinePath, 'utf8')).rejects.toThrow(); }, ); it('rejects a symlinked projection directory without mutating its target', async (): Promise => { cleanup = await mkdtemp(join(tmpdir(), 'mosaic-generated-env-')); const mosaicHome = join(cleanup, 'mosaic'); const targetDirectory = join(cleanup, 'target'); const linkedDirectory = join(mosaicHome, 'fleet', 'agents'); const legacyPath = join(targetDirectory, 'coder0.env'); const generatedPath = join(targetDirectory, 'coder0.env.generated'); const localPath = join(targetDirectory, 'coder0.env.local'); const quarantinePath = join(targetDirectory, 'coder0.env.quarantine'); await mkdir(join(mosaicHome, 'fleet'), { recursive: true, mode: 0o700 }); await mkdir(targetDirectory, { mode: 0o700 }); await writeFile(legacyPath, 'MOSAIC_AGENT_COMMAND=legacy-command\n', { mode: 0o600 }); await writeFile(generatedPath, 'generated-before\n', { mode: 0o600 }); await writeFile(localPath, 'local-before\n', { mode: 0o600 }); await writeFile(quarantinePath, 'quarantine-before\n', { mode: 0o600 }); await symlink(targetDirectory, linkedDirectory, 'dir'); await expect( writeAgentEnvironmentProjection({ mosaicHome, agentEnvDir: linkedDirectory, agentName: 'coder0', generated: generatedValues, }), ).rejects.toThrow(/unsafe-directory/i); await expect(readFile(legacyPath, 'utf8')).resolves.toBe( 'MOSAIC_AGENT_COMMAND=legacy-command\n', ); await expect(readFile(generatedPath, 'utf8')).resolves.toBe('generated-before\n'); await expect(readFile(localPath, 'utf8')).resolves.toBe('local-before\n'); await expect(readFile(quarantinePath, 'utf8')).resolves.toBe('quarantine-before\n'); }); });