55 lines
2.0 KiB
TypeScript
55 lines
2.0 KiB
TypeScript
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();
|
|
});
|
|
});
|