test / paths.test.ts
import { promises as fs } from "fs";
import * as path from "path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { OUT_OF_SCOPE, resolveInScope } from "../src/paths";
import { makeRoot } from "./helpers";
let root: string;
let outside: string;
beforeEach(async () => {
root = await makeRoot();
outside = await makeRoot();
});
afterEach(async () => {
await fs.rm(root, { recursive: true, force: true });
await fs.rm(outside, { recursive: true, force: true });
});
describe("resolveInScope", () => {
it("resolves a relative path that does not exist yet", async () => {
expect(await resolveInScope(root, "a.txt")).toEqual({ ok: true, abs: path.join(root, "a.txt") });
});
it("resolves a nested non-existent path", async () => {
expect(await resolveInScope(root, "x/y/z.txt")).toEqual({
ok: true,
abs: path.join(root, "x", "y", "z.txt"),
});
});
it("accepts the root itself", async () => {
expect(await resolveInScope(root, ".")).toEqual({ ok: true, abs: root });
});
it("accepts an absolute path inside the root", async () => {
const p = path.join(root, "in.txt");
expect(await resolveInScope(root, p)).toEqual({ ok: true, abs: p });
});
it("rejects .. traversal", async () => {
expect(await resolveInScope(root, "../x")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("rejects an absolute path outside the root", async () => {
expect(await resolveInScope(root, path.join(outside, "f.txt"))).toEqual({
ok: false,
error: OUT_OF_SCOPE,
});
});
it("rejects a sibling directory that shares the root's name as a prefix", async () => {
const sibling = root + "-evil";
await fs.mkdir(sibling);
try {
expect(await resolveInScope(root, path.join(sibling, "x"))).toEqual({
ok: false,
error: OUT_OF_SCOPE,
});
} finally {
await fs.rm(sibling, { recursive: true, force: true });
}
});
it("rejects a symlink that points outside the root", async () => {
await fs.writeFile(path.join(outside, "secret.txt"), "s");
await fs.symlink(outside, path.join(root, "link"));
expect(await resolveInScope(root, "link")).toEqual({ ok: false, error: OUT_OF_SCOPE });
expect(await resolveInScope(root, "link/secret.txt")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("rejects a non-existent path under a symlinked parent that points outside", async () => {
await fs.symlink(outside, path.join(root, "link"));
expect(await resolveInScope(root, "link/new/file.txt")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("rejects a dangling symlink", async () => {
await fs.symlink(path.join(outside, "does-not-exist"), path.join(root, "dangling"));
expect(await resolveInScope(root, "dangling")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("accepts a symlink that stays inside the root", async () => {
await fs.writeFile(path.join(root, "real.txt"), "r");
await fs.symlink(path.join(root, "real.txt"), path.join(root, "alias"));
expect(await resolveInScope(root, "alias")).toEqual({ ok: true, abs: path.join(root, "real.txt") });
});
it("rejects an empty path", async () => {
expect(await resolveInScope(root, " ")).toEqual({ ok: false, error: "Error: no path provided" });
});
});
test / paths.test.ts
import { promises as fs } from "fs";
import * as path from "path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { OUT_OF_SCOPE, resolveInScope } from "../src/paths";
import { makeRoot } from "./helpers";
let root: string;
let outside: string;
beforeEach(async () => {
root = await makeRoot();
outside = await makeRoot();
});
afterEach(async () => {
await fs.rm(root, { recursive: true, force: true });
await fs.rm(outside, { recursive: true, force: true });
});
describe("resolveInScope", () => {
it("resolves a relative path that does not exist yet", async () => {
expect(await resolveInScope(root, "a.txt")).toEqual({ ok: true, abs: path.join(root, "a.txt") });
});
it("resolves a nested non-existent path", async () => {
expect(await resolveInScope(root, "x/y/z.txt")).toEqual({
ok: true,
abs: path.join(root, "x", "y", "z.txt"),
});
});
it("accepts the root itself", async () => {
expect(await resolveInScope(root, ".")).toEqual({ ok: true, abs: root });
});
it("accepts an absolute path inside the root", async () => {
const p = path.join(root, "in.txt");
expect(await resolveInScope(root, p)).toEqual({ ok: true, abs: p });
});
it("rejects .. traversal", async () => {
expect(await resolveInScope(root, "../x")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("rejects an absolute path outside the root", async () => {
expect(await resolveInScope(root, path.join(outside, "f.txt"))).toEqual({
ok: false,
error: OUT_OF_SCOPE,
});
});
it("rejects a sibling directory that shares the root's name as a prefix", async () => {
const sibling = root + "-evil";
await fs.mkdir(sibling);
try {
expect(await resolveInScope(root, path.join(sibling, "x"))).toEqual({
ok: false,
error: OUT_OF_SCOPE,
});
} finally {
await fs.rm(sibling, { recursive: true, force: true });
}
});
it("rejects a symlink that points outside the root", async () => {
await fs.writeFile(path.join(outside, "secret.txt"), "s");
await fs.symlink(outside, path.join(root, "link"));
expect(await resolveInScope(root, "link")).toEqual({ ok: false, error: OUT_OF_SCOPE });
expect(await resolveInScope(root, "link/secret.txt")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("rejects a non-existent path under a symlinked parent that points outside", async () => {
await fs.symlink(outside, path.join(root, "link"));
expect(await resolveInScope(root, "link/new/file.txt")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("rejects a dangling symlink", async () => {
await fs.symlink(path.join(outside, "does-not-exist"), path.join(root, "dangling"));
expect(await resolveInScope(root, "dangling")).toEqual({ ok: false, error: OUT_OF_SCOPE });
});
it("accepts a symlink that stays inside the root", async () => {
await fs.writeFile(path.join(root, "real.txt"), "r");
await fs.symlink(path.join(root, "real.txt"), path.join(root, "alias"));
expect(await resolveInScope(root, "alias")).toEqual({ ok: true, abs: path.join(root, "real.txt") });
});
it("rejects an empty path", async () => {
expect(await resolveInScope(root, " ")).toEqual({ ok: false, error: "Error: no path provided" });
});
});