import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { afterEach, describe, expect, test } from 'vitest'; import { assertContainedRealPath } from '../../src/path-containment'; let dir: string; afterEach(async () => { if (dir) await rm(dir, { recursive: true, force: true }); }); describe('assertContainedRealPath', () => { test('allows a normal file inside the root', async () => { dir = await mkdtemp(path.join(tmpdir(), 'spoon-')); const root = path.join(dir, 'repo'); await mkdir(root, { recursive: true }); await writeFile(path.join(root, 'a.txt'), 'hi'); await expect(assertContainedRealPath(root, 'a.txt')).resolves.toBe( path.join(root, 'a.txt'), ); }); test('rejects a lexical .. escape', async () => { dir = await mkdtemp(path.join(tmpdir(), 'spoon-')); const root = path.join(dir, 'repo'); await mkdir(root, { recursive: true }); await expect(assertContainedRealPath(root, '../secret')).rejects.toThrow(); }); test('rejects reading through a symlink that escapes the root', async () => { dir = await mkdtemp(path.join(tmpdir(), 'spoon-')); const root = path.join(dir, 'repo'); const outside = path.join(dir, 'outside'); await mkdir(root, { recursive: true }); await mkdir(outside, { recursive: true }); await writeFile(path.join(outside, 'secret'), 'S'); await symlink(outside, path.join(root, 'link')); await expect( assertContainedRealPath(root, 'link/secret'), ).rejects.toThrow(); }); test('rejects a write whose final target is a symlink', async () => { dir = await mkdtemp(path.join(tmpdir(), 'spoon-')); const root = path.join(dir, 'repo'); await mkdir(root, { recursive: true }); await symlink(path.join(dir, 'evil'), path.join(root, 'out')); await expect( assertContainedRealPath(root, 'out', { forWrite: true }), ).rejects.toThrow(); }); });