- SlugGuard.IsValid() rejects slugs with path traversal (../, //, uppercase) - SlugGuard.ToSafePath() ensures sync writes stay within target directory - PushCommand: skip files with invalid slugs before upload - SyncCommand: validate server-returned slugs before writing to disk - EditCommand/NewCommand: try/finally guarantees temp file deletion on crash - NotesCache: set 600 permissions on cache file (metadata only, no token) - NewCommand: remove dead Sha256 method (HashHelper already covers this)
34 lines
1.4 KiB
C#
34 lines
1.4 KiB
C#
using System.Text.RegularExpressions;
|
|
|
|
namespace NozCli.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;
|
|
}
|
|
}
|