import { lstat, realpath, stat } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; function isMissing(error) { return error && typeof error === 'object' && error.code === 'ENOENT'; } export async function canonicalExistingPath(candidate, label = 'Path') { const absolute = path.resolve(candidate); try { return await realpath(absolute); } catch (error) { throw new Error( `${label} does not resolve to an existing path: ${absolute}`, { cause: error, } ); } } export async function canonicalProspectivePath(candidate) { const absolute = path.resolve(candidate); const suffix = []; let current = absolute; while (true) { let details; try { details = await lstat(current); } catch (error) { if (!isMissing(error)) throw error; const parent = path.dirname(current); if (parent === current) throw error; suffix.unshift(path.basename(current)); current = parent; continue; } let canonical; try { canonical = await realpath(current); } catch (error) { if (details.isSymbolicLink() && isMissing(error)) { throw new Error(`Path contains a dangling symbolic link: ${current}`, { cause: error, }); } throw error; } if (suffix.length > 0) { const canonicalDetails = await stat(canonical); if (!canonicalDetails.isDirectory()) { throw new Error( `Path has a non-directory existing ancestor: ${current}` ); } } return path.join(canonical, ...suffix); } } export function canonicalPathsOverlap(left, right) { const resolvedLeft = path.resolve(left); const resolvedRight = path.resolve(right); return ( resolvedLeft === resolvedRight || resolvedLeft.startsWith(`${resolvedRight}${path.sep}`) || resolvedRight.startsWith(`${resolvedLeft}${path.sep}`) ); } export async function assertSafeRemovalTarget(target) { const canonical = await canonicalProspectivePath(target); const root = path.parse(canonical).root; const protectedPaths = await Promise.all([ canonicalProspectivePath(process.cwd()), canonicalProspectivePath(os.homedir()), ]); if ( canonical === root || protectedPaths.some( (protectedPath) => protectedPath === canonical || protectedPath.startsWith(`${canonical}${path.sep}`) ) ) { throw new Error(`Refusing unsafe output target: ${canonical}`); } return canonical; }