| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- import { existsSync } from 'node:fs'
- import { chmod, mkdir, mkdtemp, readFile, rm, stat, symlink } from 'node:fs/promises'
- import { homedir, tmpdir } from 'node:os'
- import { dirname, join, parse } from 'node:path'
- import { Context } from '@deepseek-ai/cordis'
- import { SandboxedFileSystem } from '@deepseek-ai/dsh-fs-sandbox'
- import { canonicalPath } from '@deepseek-ai/dsh-sandbox'
- import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy'
- import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
- import { describe, expect, it } from 'vitest'
- import { assertWorkspaceOutsideTemp, outsideTempWorkspaceParent } from './snapshot-workspace-parent.ts'
- // Host disk exhaustion is not simulated: placement and the real write fence are the regression oracles.
- describe('snapshot workspace parent', () => {
- // Windows directory permissions and root bypass do not enforce POSIX write bits.
- it.skipIf(process.platform === 'win32' || process.getuid?.() === 0)('uses home when the temp parent is not writable', async () => {
- const base = await mkdtemp(join(outsideTempWorkspaceParent(), '.dsh-snapshot-readonly-'))
- try {
- const temporary = join(base, '_temp')
- await mkdir(temporary)
- await chmod(base, 0o500)
- expect(outsideTempWorkspaceParent(temporary)).toBe(homedir())
- } finally {
- await chmod(base, 0o700)
- await rm(base, { recursive: true, force: true })
- }
- })
- it('uses home when a temp sibling would require a system directory or inherit its grant', () => {
- expect(outsideTempWorkspaceParent('/tmp')).toBe(homedir())
- expect(outsideTempWorkspaceParent(parse(tmpdir()).root)).toBe(homedir())
- expect(outsideTempWorkspaceParent(join(canonicalPath('/tmp'), 'runner', '_temp'))).toBe(homedir())
- })
- it('rejects automatically writable temporary workspaces, including symlink aliases', async () => {
- const root = await mkdtemp(join(tmpdir(), 'dsh-snapshot-parent-'))
- try {
- expect(() => { assertWorkspaceOutsideTemp(root) }).toThrow('must be outside temporary writable root')
- const alias = join(root, 'alias')
- await symlink(tmpdir(), alias, process.platform === 'win32' ? 'junction' : 'dir')
- expect(() => { assertWorkspaceOutsideTemp(alias) }).toThrow('must be outside temporary writable root')
- } finally {
- await rm(root, { recursive: true, force: true })
- }
- })
- it('allows the allocated workspace but denies sibling writes and cleans the complete tree', async () => {
- const base = await mkdtemp(join(outsideTempWorkspaceParent(), 'dsh-snapshot-parent-'))
- const ctx = new Context()
- const fibers: Awaited<ReturnType<Context['plugin']>>[] = []
- try {
- const temporary = join(base, '_temp')
- await mkdir(temporary)
- expect(outsideTempWorkspaceParent(temporary)).toBe(canonicalPath(base))
- const workspace = await mkdtemp(join(outsideTempWorkspaceParent(temporary), 'workspace-'))
- const outside = join(base, 'outside.txt')
- assertWorkspaceOutsideTemp(workspace)
- expect(dirname(workspace)).toBe(canonicalPath(base))
- expect((await stat(workspace)).dev).toBe((await stat(temporary)).dev)
- fibers.push(await ctx.plugin(SessionProjectionRegistry))
- fibers.push(await ctx.plugin(SandboxPolicyService, { mode: 'workspace-write', workspaceRoot: workspace }))
- fibers.push(await ctx.plugin(SandboxedFileSystem, { cwd: workspace }))
- const inside = join(workspace, 'inside.txt')
- await ctx.fs.writeText(await ctx.fs.resolve(inside), 'inside')
- expect(await readFile(inside, 'utf8')).toBe('inside')
- await expect(ctx.fs.writeText(await ctx.fs.resolve(outside), 'outside'))
- .rejects.toMatchObject({ code: 'FS_SANDBOX_DENIED' })
- expect(existsSync(outside)).toBe(false)
- } finally {
- try {
- for (const fiber of fibers.reverse()) await fiber.dispose()
- } finally {
- await rm(base, { recursive: true, force: true })
- }
- }
- expect(existsSync(base)).toBe(false)
- })
- })
|