Skip to content

Commit 9cd0177

Browse files
committed
fix: add reparse point guards to all recursive deletion paths — prevent symlink/junction traversal attacks
1 parent 2a5fb7b commit 9cd0177

4 files changed

Lines changed: 32 additions & 3 deletions

File tree

src/DeepPurge.Core/FileSystem/FileLeftoverScanner.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ private void ScanDirectory(string basePath, List<LeftoverItem> leftovers, string
323323
var dirName = Path.GetFileName(dir);
324324
if (SharedFolderNames.Contains(dirName)) continue;
325325
if (IsProtected(dir)) continue;
326+
if (Safety.SafetyGuard.IsReparsePoint(dir)) continue;
326327

327328
var confidence = MatchConfidence(dirName, dir);
328329
if (confidence == null) continue;
@@ -610,7 +611,12 @@ public static long GetDirectorySize(string path)
610611
try
611612
{
612613
return new DirectoryInfo(path)
613-
.EnumerateFiles("*", SearchOption.AllDirectories)
614+
.EnumerateFiles("*", new EnumerationOptions
615+
{
616+
RecurseSubdirectories = true,
617+
IgnoreInaccessible = true,
618+
AttributesToSkip = FileAttributes.ReparsePoint,
619+
})
614620
.Sum(fi => { try { return fi.Length; } catch { return 0; } });
615621
}
616622
catch { return 0; }

src/DeepPurge.Core/FileSystem/JunkFilesCleaner.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public static DeleteSummary DeleteJunkSafe(
132132
{
133133
if (file.IsDirectory && Directory.Exists(file.Path))
134134
{
135+
if (Safety.SafetyGuard.IsReparsePoint(file.Path)) { skipped++; continue; }
135136
if (options.SecureDelete) SecureDelete.WipeDirectory(file.Path);
136137
else Directory.Delete(file.Path, recursive: true);
137138
freed += file.Size;
@@ -635,7 +636,12 @@ private static long GetDirSize(string path)
635636
try
636637
{
637638
return new DirectoryInfo(path)
638-
.EnumerateFiles("*", SearchOption.AllDirectories)
639+
.EnumerateFiles("*", new EnumerationOptions
640+
{
641+
RecurseSubdirectories = true,
642+
IgnoreInaccessible = true,
643+
AttributesToSkip = FileAttributes.ReparsePoint,
644+
})
639645
.Sum(fi => { try { return fi.Length; } catch { return 0L; } });
640646
}
641647
catch { return 0; }

src/DeepPurge.Core/Privacy/EvidenceRemover.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ public static DeleteSummary CleanTracesSafe(
121121
}
122122
else if (item.IsDirectory && Directory.Exists(item.Path))
123123
{
124+
if (Safety.SafetyGuard.IsReparsePoint(item.Path)) { skipped++; continue; }
124125
if (options.SecureDelete) SecureDelete.WipeDirectory(item.Path);
125126
else Directory.Delete(item.Path, recursive: true);
126127
freed += item.SizeBytes;
@@ -528,7 +529,12 @@ private static long GetDirSize(string path)
528529
try
529530
{
530531
return new DirectoryInfo(path)
531-
.EnumerateFiles("*", SearchOption.AllDirectories)
532+
.EnumerateFiles("*", new EnumerationOptions
533+
{
534+
RecurseSubdirectories = true,
535+
IgnoreInaccessible = true,
536+
AttributesToSkip = FileAttributes.ReparsePoint,
537+
})
532538
.Sum(fi => { try { return fi.Length; } catch { return 0L; } });
533539
}
534540
catch { return 0; }

src/DeepPurge.Core/Safety/SafetyGuard.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,17 @@ public static bool IsJunkPathSafeToDelete(string path)
212212
normalized.StartsWith(parent, StringComparison.OrdinalIgnoreCase));
213213
}
214214

215+
/// <summary>Returns true if the path is a reparse point (symlink, junction, mount point). Callers must NOT recurse into reparse points during deletion.</summary>
216+
public static bool IsReparsePoint(string path)
217+
{
218+
try
219+
{
220+
var attr = File.GetAttributes(path);
221+
return (attr & FileAttributes.ReparsePoint) != 0;
222+
}
223+
catch { return false; }
224+
}
225+
215226
/// <summary>Get a human-readable safety assessment</summary>
216227
public static (bool Safe, string Reason) AssessOperation(string operationType, string target)
217228
{

0 commit comments

Comments
 (0)