// Création d'un nouveau projet : un dossier `/` créé sous une racine existante, dans // lequel on lance ensuite une session. Couche PURE (testable sans fs) : la création réelle du // dossier, le `git init` et le spawn vivent dans `routes/projects.ts`. import { join } from 'node:path'; import { isSafeAbsolutePath } from './git.js'; /** * Valide un nom de projet : UN SEUL segment de dossier. Anti-traversal de base : refuse les noms * vides, trop longs, `.`/`..`, et tout caractère de séparation (`/`, `\`) ou NUL. La résolution du * chemin complet (et sa re-validation) est faite par {@link resolveProjectDir}. */ export function isSafeProjectName(name: string): boolean { const n = name.trim(); if (n.length === 0 || n.length > 255) return false; if (n === '.' || n === '..') return false; return !/[/\\\0]/.test(n); } /** * Résout le dossier de projet `/`. Suppose `root` déjà validé absolu et existant par * l'appelant ; re-valide le chemin final via {@link isSafeAbsolutePath} (défense en profondeur). * Lève si `name` est invalide ou si le chemin résultant échappe l'arborescence. */ export function resolveProjectDir(root: string, name: string): string { if (!isSafeProjectName(name)) { throw Object.assign(new Error(`Invalid project name: ${name}`), { statusCode: 400 }); } const dir = join(root, name.trim()); if (!isSafeAbsolutePath(dir)) { throw Object.assign(new Error(`Unsafe project path: ${dir}`), { statusCode: 400 }); } return dir; }