18 lines · 972 B
const UNSAFE_SEGMENT = /^(\.{1,2}|\.git|git~\d+|gitmod~\d+)$|[. ]$|[\\\x00-\x1f\x7f]/i;

/** Rejects names git or common filesystems treat specially (CVE-2014-9390 style `.GIT`, `git~1`, trailing dots). */
export function isUnsafeSegment(segment: string): boolean {
  const visible = segment.replace(/[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/g, "");
  const odd = visible.toLowerCase() === ".gitmodules" && segment !== ".gitmodules";
  return odd || UNSAFE_SEGMENT.test(segment) || UNSAFE_SEGMENT.test(visible);
}

const MAX_TREE_PATH = 4096;

/** The GIT-7 path rules as a predicate, for callers that validate before a commit (the editor room). */
export function isSafeTreePath(path: string): boolean {
  if (path.length === 0 || path.length > MAX_TREE_PATH || path.startsWith("/") || path.endsWith("/")) return false;
  const segments = path.split("/");
  return segments.length > 0 && segments.every((segment) => segment !== "" && !isUnsafeSegment(segment));
}