/** * Containment tests for lexical canonical paths and filesystem-identity aliases. */ import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join, parse } from 'node:path' import { isPathUnder } from '../src/containment.ts' let base: string beforeEach(async () => { base = await mkdtemp(join(tmpdir(), 'dsh-fssbx-containment-')) }) afterEach(async () => { await rm(base, { recursive: true, force: true }) }) describe('filesystem sandbox containment', () => { it('accepts equal paths, descendants, and a filesystem-root boundary', async () => { expect(await isPathUnder(base, base)).toBe(true) expect(await isPathUnder(join(base, 'child'), base)).toBe(true) expect(await isPathUnder(base, parse(base).root)).toBe(true) }) it('uses case-insensitive lexical comparison for Windows-style containment', async () => { expect(await isPathUnder(join(base.toUpperCase(), 'child'), base.toLowerCase(), false)).toBe(true) expect(await isPathUnder(join(base, 'case-sensitive-child'), base, true)).toBe(true) }) it('recognizes an alias-equivalent root by filesystem identity for a missing target', async () => { const realRoot = join(base, 'real') const aliasRoot = join(base, 'alias') await mkdir(realRoot) await symlink(realRoot, aliasRoot) expect(await isPathUnder(join(await realpath(realRoot), 'missing', 'file.txt'), aliasRoot)).toBe(true) }) it('denies unrelated and missing roots', async () => { const allowed = join(base, 'allowed') const outside = join(base, 'outside') await mkdir(allowed) await mkdir(outside) expect(await isPathUnder(join(outside, 'file.txt'), allowed)).toBe(false) expect(await isPathUnder(join(outside, 'file.txt'), join(base, 'missing-root'))).toBe(false) }) it('treats a regular-file path segment as a missing target, not containment', async () => { const allowed = join(base, 'allowed') const blocker = join(base, 'blocker') await mkdir(allowed) await writeFile(blocker, 'not a directory') expect(await isPathUnder(join(blocker, 'child.txt'), allowed)).toBe(false) }) })