fix: prevent path traversal in list volume files

This commit is contained in:
Nicolas Meienberger 2026-01-22 22:32:04 +01:00
parent ffc43e0c25
commit 88fa06b503
2 changed files with 51 additions and 1 deletions

View file

@ -0,0 +1,48 @@
import { test, describe, expect } from "bun:test";
import { volumeService } from "../volume.service";
import { db } from "~/server/db/db";
import { volumesTable } from "~/server/db/schema";
import { randomUUID } from "node:crypto";
import * as fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { createTestSession } from "~/test/helpers/auth";
import { withContext } from "~/server/core/request-context";
describe("volumeService security", () => {
describe("path traversal", () => {
test("should reject traversal outside the volume root in listFiles", async () => {
const { organizationId, user } = await createTestSession();
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-vol-svc-"));
const volumePath = path.join(tempRoot, "vol");
const secretPath = path.join(tempRoot, "volume-secret");
await fs.mkdir(volumePath, { recursive: true });
await fs.mkdir(secretPath, { recursive: true });
await fs.writeFile(path.join(secretPath, "secret.txt"), "top secret", "utf-8");
const [volume] = await db
.insert(volumesTable)
.values({
shortId: randomUUID().slice(0, 8),
name: `test-vol-${randomUUID().slice(0, 8)}`,
type: "directory",
status: "mounted",
config: { backend: "directory", path: volumePath },
autoRemount: true,
organizationId,
})
.returning();
try {
await withContext({ organizationId, userId: user.id }, async () => {
const traversalPath = `../${path.basename(secretPath)}`;
expect(volumeService.listFiles(volume.id, traversalPath)).rejects.toThrow("Invalid path");
});
} finally {
await fs.rm(tempRoot, { recursive: true, force: true });
}
});
});
});

View file

@ -329,7 +329,9 @@ const listFiles = async (idOrShortId: string | number, subPath?: string) => {
const requestedPath = subPath ? path.join(volumePath, subPath) : volumePath;
const normalizedPath = path.normalize(requestedPath);
if (!normalizedPath.startsWith(volumePath)) {
const relative = path.relative(volumePath, normalizedPath);
if (relative.startsWith("..") || path.isAbsolute(relative)) {
throw new InternalServerError("Invalid path");
}