Merge pull request #1796 from rmcrackan/rmcrackan/1711-linux-safety

#1711 : Linux/Docker: the default in-progress download/decrypt folder…
This commit is contained in:
rmcrackan
2026-05-08 09:10:23 -04:00
committed by GitHub
3 changed files with 57 additions and 5 deletions

View File

@@ -31,8 +31,19 @@ public abstract class AudibleFileStorage
{
try
{
return (DateTime.UtcNow - lastInProgressFail) < RetryInProgressInterval ? null
: new DirectoryInfo(Configuration.Instance.InProgress).CreateSubdirectoryEx(subDirectory);
if ((DateTime.UtcNow - lastInProgressFail) < RetryInProgressInterval)
return null;
var parent = new DirectoryInfo(Configuration.Instance.InProgress);
var dir = parent.CreateSubdirectoryEx(subDirectory);
// On Unix, lock down the parent and the subdir to the current user (0700).
// Prevents other local users from reading partial downloads / decrypted artifacts
// when InProgress lives on a world-traversable filesystem like /tmp.
TrySetOwnerOnlyMode(parent.FullName);
TrySetOwnerOnlyMode(dir.FullName);
return dir;
}
catch (Exception ex)
{
@@ -42,6 +53,20 @@ public abstract class AudibleFileStorage
}
}
private static void TrySetOwnerOnlyMode(string path)
{
if (OperatingSystem.IsWindows())
return;
try
{
File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
}
catch
{
// best-effort. user may have intentionally set their own perms, or the FS may not support it.
}
}
public static LongPath? DownloadsInProgressDirectory => CreateInProgressDirectory("DownloadsInProgress")?.FullName;
public static LongPath? DecryptInProgressDirectory => CreateInProgressDirectory("DecryptInProgress")?.FullName;

View File

@@ -13,7 +13,10 @@ public partial class Configuration
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(ProcessDirectory, LibationFiles.LIBATION_FILES_KEY));
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
public static string MyMusic => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), "Libation"));
public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation"));
// Use a per-user subdir so we don't collide with -- or get blocked by -- another local
// user's leftover dir on shared-/tmp systems (Linux). On Windows and macOS, GetTempPath
// already returns a per-user location, so this is just a harmless extra path segment.
public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), $"Libation-{Environment.UserName}"));
public static string UserProfile => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Libation"));
public static string LocalAppData => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation"));

View File

@@ -62,8 +62,9 @@ public class Upgrader : UpgraderBase
}
//Silently download the upgrade in the background, save it to a temp file.
var zipFile = Path.Combine(Path.GetTempPath(), Path.GetFileName(upgradeProperties.ZipUrl));
var zipFile = GetUpgradeDownloadPath(upgradeProperties.ZipUrl);
if (zipFile is null)
return null;
Serilog.Log.Logger.Information($"Downloading {zipFile}");
@@ -103,6 +104,29 @@ public class Upgrader : UpgraderBase
return null;
}
}
/// <summary>
/// Allocate a fresh per-run temp directory for the upgrade zip and return the full path
/// the zip should be downloaded to. Uses a random subdirectory name (and 0700 perms on
/// Unix) so we never extract or execute from a predictable, shared-temp location.
/// </summary>
/// <returns>Destination path for the upgrade zip, or <c>null</c> if the temp directory
/// could not be created (in which case the upgrade-failed event has already been raised).</returns>
private string? GetUpgradeDownloadPath(string zipUrl)
{
try
{
var stagingDir = Directory.CreateTempSubdirectory("Libation-upgrade-").FullName;
return Path.Combine(stagingDir, Path.GetFileName(zipUrl));
}
catch (Exception ex)
{
var message = "Failed to create a temp directory for the upgrade download.";
Serilog.Log.Logger.Error(ex, message);
OnUpgradeFailed(message, ex);
return null;
}
}
}
public class MockUpgrader : UpgraderBase