fix: prevent path traversal in list volume files
This commit is contained in:
parent
ffc43e0c25
commit
88fa06b503
2 changed files with 51 additions and 1 deletions
48
app/server/modules/volumes/__tests__/volumes.service.test.ts
Normal file
48
app/server/modules/volumes/__tests__/volumes.service.test.ts
Normal 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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue