using System.Text.RegularExpressions; namespace NozCli.Core.Security; /// Validates slugs before they are sent to the server or written to disk. public static partial class SlugGuard { // Valid slug: lowercase alphanumeric + hyphens, optionally separated by / // Examples: "hydroponik", "biosysteme/hydroponik", "a/b/c" [GeneratedRegex(@"^[a-z0-9][a-z0-9\-]*(?:/[a-z0-9][a-z0-9\-]*)*$")] private static partial Regex ValidSlugRegex(); /// Returns true if the slug is safe to use (no path traversal, valid chars). public static bool IsValid(string slug) => !string.IsNullOrWhiteSpace(slug) && !slug.Contains("..") && !slug.Contains("//") && ValidSlugRegex().IsMatch(slug); /// Resolves a slug to a file path inside baseDir. /// Returns null if the resulting path would escape baseDir (path traversal). public static string? ToSafePath(string baseDir, string slug, string extension = ".md") { var rel = slug.Replace('/', Path.DirectorySeparatorChar) + extension; var fullPath = Path.GetFullPath(Path.Combine(baseDir, rel)); var baseFull = Path.GetFullPath(baseDir); // Ensure the resolved path stays strictly inside baseDir return fullPath.StartsWith(baseFull + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ? fullPath : null; } }