Skip to main content
Glama
path-validation.test.tsβ€’5.38 kB
import * as fs from "node:fs"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { SecurityError, setWorkspaceRoot, validateFilePath } from "../../src/utils/security.js"; // Mock fs.realpathSync to control the behavior vi.mock("node:fs", async () => { const actual = await vi.importActual("node:fs"); return { ...actual, existsSync: vi.fn(() => true), // Always return true for parent directory existence realpathSync: vi.fn((path: string) => path), // Just return the path as-is for testing }; }); beforeEach(() => { // Reset mocks before each test vi.clearAllMocks(); }); describe("Path Validation - Workspace Boundary Check", () => { it("should block absolute paths when no workspace root is set (backward compatibility)", () => { expect(() => validateFilePath("/etc/passwd", "/workspace")).toThrow(SecurityError); expect(() => validateFilePath("/home/user/file.txt", "/workspace")).toThrow(SecurityError); expect(() => validateFilePath("/usr/local/bin/script.js", "/workspace")).toThrow(SecurityError); }); it("should allow valid relative paths", () => { expect(() => validateFilePath("src/index.ts", "/workspace")).not.toThrow(); expect(() => validateFilePath("test/utils/security.test.ts", "/workspace")).not.toThrow(); expect(() => validateFilePath("package.json", "/workspace")).not.toThrow(); expect(() => validateFilePath("src/utils/security.ts", "/workspace")).not.toThrow(); }); it("should reject path traversal attempts", () => { expect(() => validateFilePath("../../etc/passwd", "/workspace")).toThrow(SecurityError); expect(() => validateFilePath("../config.json", "/workspace")).toThrow(SecurityError); expect(() => validateFilePath("../../../etc/passwd", "/workspace")).toThrow(SecurityError); }); describe("With workspace root set", () => { beforeEach(() => { setWorkspaceRoot("/workspace"); }); it("should accept absolute paths within workspace - VS Code compatibility", () => { // VS Code provides absolute paths like this: const vscodePath = "/workspace/src/index.ts"; // This should now work with our new implementation expect(() => validateFilePath(vscodePath, "/workspace")).not.toThrow(); }); it("should reject absolute paths outside workspace", () => { const outsidePath = "/etc/passwd"; expect(() => validateFilePath(outsidePath, "/workspace")).toThrow(SecurityError); }); it("should still reject path traversal attempts", () => { expect(() => validateFilePath("../../etc/passwd", "/workspace")).toThrow(SecurityError); expect(() => validateFilePath("../config.json", "/workspace")).toThrow(SecurityError); expect(() => validateFilePath("../../../etc/passwd", "/workspace")).toThrow(SecurityError); }); it("should reject paths with null bytes", () => { const nullBytePath = "/workspace/file.txt\0.jpg"; expect(() => validateFilePath(nullBytePath, "/workspace")).toThrow(SecurityError); }); it("should reject symlink paths outside workspace", () => { // Mock realpathSync to simulate a symlink that points outside the workspace vi.spyOn(fs, "realpathSync").mockImplementation((path: fs.PathLike) => { if (path === "/workspace/symlink") { return "/etc/passwd"; // Symlink pointing outside workspace } return path as string; }); const symlinkPath = "/workspace/symlink"; expect(() => validateFilePath(symlinkPath, "/workspace")).toThrow(SecurityError); }); it("should handle non-existent files within workspace", () => { // Mock realpathSync to throw an error (simulating non-existent file) vi.spyOn(fs, "realpathSync").mockImplementation((path: fs.PathLike) => { if (path === "/workspace/nonexistent/file.ts") { throw new Error("ENOENT: no such file or directory"); } // For the parent directory check if (path === "/workspace/nonexistent") { return path as string; } return path as string; }); // Mock existsSync to return false for parent directory vi.spyOn(fs, "existsSync").mockImplementation((path: fs.PathLike) => { if (path === "/workspace/nonexistent") { return false; } return true; }); const newPath = "/workspace/nonexistent/file.ts"; expect(() => validateFilePath(newPath, "/workspace")).toThrow(SecurityError); }); it("should handle Windows-style paths correctly", () => { // Mock process.platform to simulate Windows const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform"); Object.defineProperty(process, "platform", { value: "win32" }); try { // Test Windows UNC paths expect(() => validateFilePath("\\\\server\\share", "/workspace")).toThrow(SecurityError); // Test Windows drive letters expect(() => validateFilePath("C:\\Windows", "/workspace")).toThrow(SecurityError); } finally { // Restore original platform if (originalPlatform) { Object.defineProperty(process, "platform", originalPlatform); } else { delete (process as any).platform; } } }); it("should allow legitimate filenames with double dots", () => { expect(() => validateFilePath("/workspace/config..json", "/workspace")).not.toThrow(); expect(() => validateFilePath("/workspace/file..txt", "/workspace")).not.toThrow(); expect(() => validateFilePath("/workspace/test.config..js", "/workspace")).not.toThrow(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/snapback-dev/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server