just-bash-mcp / src /utils /paths.ts
victor's picture
victor HF Staff
Initial deploy of just-bash MCP server
548a458 verified
import { posix as path } from "node:path";
export class InvalidPathError extends Error {
constructor(message: string) {
super(message);
this.name = "InvalidPathError";
}
}
const ALLOWED_PREFIXES = ["/home/user", "/tmp"];
export function validateAbsolutePath(input: string): string {
if (typeof input !== "string" || input.length === 0) {
throw new InvalidPathError("Path must be a non-empty string");
}
if (input.includes("\0")) {
throw new InvalidPathError("Path contains null byte");
}
if (!input.startsWith("/")) {
throw new InvalidPathError("Path must be absolute");
}
const normalized = path.normalize(input);
if (normalized.includes("\0")) {
throw new InvalidPathError("Path contains null byte after normalization");
}
if (normalized === "/" || normalized === "" || normalized.startsWith("..")) {
throw new InvalidPathError("Path traversal not allowed");
}
// Forbid traversal after normalization (defense in depth)
const parts = normalized.split("/");
if (parts.some((p) => p === "..")) {
throw new InvalidPathError("Path traversal not allowed");
}
if (!isUnderAllowedRoot(normalized)) {
throw new InvalidPathError("Path outside allowed namespace");
}
return normalized;
}
function isUnderAllowedRoot(p: string): boolean {
return ALLOWED_PREFIXES.some(
(root) => p === root || p.startsWith(`${root}/`),
);
}
export function encodePathForUri(p: string): string {
return encodeURIComponent(p);
}
export function decodePathFromUri(p: string): string {
return decodeURIComponent(p);
}