diff --git a/Jellyfin.Server/Infrastructure/SafeTimestampFileInfo.cs b/Jellyfin.Server/Infrastructure/SafeTimestampFileInfo.cs
new file mode 100644
index 0000000000..ac2fed2e75
--- /dev/null
+++ b/Jellyfin.Server/Infrastructure/SafeTimestampFileInfo.cs
@@ -0,0 +1,67 @@
+using System;
+using System.IO;
+using Microsoft.Extensions.FileProviders;
+
+namespace Jellyfin.Server.Infrastructure
+{
+ ///
+ /// An wrapper that sanitizes timestamps
+ /// to ensure they are valid Win32 FileTimes.
+ ///
+ ///
+ /// This wrapper prevents in
+ /// when serving files
+ /// with timestamps before 1601-01-01 (the Win32 epoch), which can occur in Docker containers
+ /// or on certain filesystems.
+ ///
+ public class SafeTimestampFileInfo : IFileInfo
+ {
+ ///
+ /// The minimum valid Win32 FileTime is 1601-01-01. We use 1601-01-02 for safety margin.
+ ///
+ private static readonly DateTimeOffset _minValidWin32Time =
+ new DateTimeOffset(1601, 1, 2, 0, 0, 0, TimeSpan.Zero);
+
+ ///
+ /// Safe fallback timestamp (Unix epoch: 1970-01-01).
+ ///
+ private static readonly DateTimeOffset _safeFallbackTime =
+ new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
+
+ private readonly IFileInfo _inner;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The inner to wrap.
+ public SafeTimestampFileInfo(IFileInfo inner)
+ {
+ _inner = inner ?? throw new ArgumentNullException(nameof(inner));
+ }
+
+ ///
+ public bool Exists => _inner.Exists;
+
+ ///
+ public long Length => _inner.Length;
+
+ ///
+ public string? PhysicalPath => _inner.PhysicalPath;
+
+ ///
+ public string Name => _inner.Name;
+
+ ///
+ ///
+ /// Returns the original timestamp if valid, otherwise returns 1970-01-01 (Unix epoch).
+ ///
+ public DateTimeOffset LastModified =>
+ _inner.LastModified < _minValidWin32Time ? _safeFallbackTime : _inner.LastModified;
+
+ ///
+ public bool IsDirectory => _inner.IsDirectory;
+
+ ///
+ public Stream CreateReadStream() => _inner.CreateReadStream();
+ }
+}
diff --git a/Jellyfin.Server/Infrastructure/SafeTimestampFileProvider.cs b/Jellyfin.Server/Infrastructure/SafeTimestampFileProvider.cs
new file mode 100644
index 0000000000..db986fc4e3
--- /dev/null
+++ b/Jellyfin.Server/Infrastructure/SafeTimestampFileProvider.cs
@@ -0,0 +1,57 @@
+using System;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.FileProviders.Physical;
+using Microsoft.Extensions.Primitives;
+
+namespace Jellyfin.Server.Infrastructure
+{
+ ///
+ /// An wrapper that sanitizes file timestamps to ensure
+ /// they are valid Win32 FileTimes.
+ ///
+ ///
+ /// This wrapper prevents in
+ /// when serving files
+ /// with timestamps before 1601-01-01 (the Win32 epoch), which can occur in Docker containers
+ /// or on certain filesystems.
+ ///
+ public sealed class SafeTimestampFileProvider : IFileProvider, IDisposable
+ {
+ private readonly PhysicalFileProvider _inner;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The root directory for this provider.
+ public SafeTimestampFileProvider(string root)
+ {
+ ArgumentNullException.ThrowIfNull(root);
+ _inner = new PhysicalFileProvider(root);
+ }
+
+ ///
+ public IFileInfo GetFileInfo(string subpath)
+ {
+ var fileInfo = _inner.GetFileInfo(subpath);
+ return new SafeTimestampFileInfo(fileInfo);
+ }
+
+ ///
+ public IDirectoryContents GetDirectoryContents(string subpath)
+ {
+ return _inner.GetDirectoryContents(subpath);
+ }
+
+ ///
+ public IChangeToken Watch(string filter)
+ {
+ return _inner.Watch(filter);
+ }
+
+ ///
+ public void Dispose()
+ {
+ _inner.Dispose();
+ }
+ }
+}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index f6a4ae7d6e..a334a09504 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -16,6 +16,7 @@ using Jellyfin.Networking.HappyEyeballs;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks;
using Jellyfin.Server.Implementations.Extensions;
+using Jellyfin.Server.Infrastructure;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
@@ -182,12 +183,12 @@ namespace Jellyfin.Server
extensionProvider.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
mainApp.UseDefaultFiles(new DefaultFilesOptions
{
- FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
+ FileProvider = new SafeTimestampFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
RequestPath = "/web"
});
mainApp.UseStaticFiles(new StaticFileOptions
{
- FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
+ FileProvider = new SafeTimestampFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
RequestPath = "/web",
ContentTypeProvider = extensionProvider,
OnPrepareResponse = (context) =>
diff --git a/tests/Jellyfin.Server.Tests/SafeTimestampFileInfoTests.cs b/tests/Jellyfin.Server.Tests/SafeTimestampFileInfoTests.cs
new file mode 100644
index 0000000000..22c678cedd
--- /dev/null
+++ b/tests/Jellyfin.Server.Tests/SafeTimestampFileInfoTests.cs
@@ -0,0 +1,128 @@
+using System;
+using System.IO;
+using Jellyfin.Server.Infrastructure;
+using Microsoft.Extensions.FileProviders;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Tests
+{
+ public class SafeTimestampFileInfoTests
+ {
+ private static readonly DateTimeOffset ValidTimestamp = new DateTimeOffset(2024, 12, 31, 12, 0, 0, TimeSpan.Zero);
+ private static readonly DateTimeOffset PreWin32Timestamp = new DateTimeOffset(1600, 1, 1, 0, 0, 0, TimeSpan.Zero);
+ private static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
+
+ [Fact]
+ public void LastModified_WithValidTimestamp_ReturnsOriginal()
+ {
+ // Arrange
+ var mockFileInfo = new Mock();
+ mockFileInfo.Setup(f => f.LastModified).Returns(ValidTimestamp);
+
+ var safeFileInfo = new SafeTimestampFileInfo(mockFileInfo.Object);
+
+ // Act
+ var result = safeFileInfo.LastModified;
+
+ // Assert
+ Assert.Equal(ValidTimestamp, result);
+ }
+
+ [Fact]
+ public void LastModified_WithDateMinValue_ReturnsSafeFallback()
+ {
+ // Arrange
+ var mockFileInfo = new Mock();
+ mockFileInfo.Setup(f => f.LastModified).Returns(DateTimeOffset.MinValue);
+
+ var safeFileInfo = new SafeTimestampFileInfo(mockFileInfo.Object);
+
+ // Act
+ var result = safeFileInfo.LastModified;
+
+ // Assert
+ Assert.Equal(UnixEpoch, result);
+ }
+
+ [Fact]
+ public void LastModified_WithPre1601Timestamp_ReturnsSafeFallback()
+ {
+ // Arrange
+ var mockFileInfo = new Mock();
+ mockFileInfo.Setup(f => f.LastModified).Returns(PreWin32Timestamp);
+
+ var safeFileInfo = new SafeTimestampFileInfo(mockFileInfo.Object);
+
+ // Act
+ var result = safeFileInfo.LastModified;
+
+ // Assert
+ Assert.Equal(UnixEpoch, result);
+ }
+
+ [Fact]
+ public void LastModified_WithWin32EpochPlusOneDay_ReturnsOriginal()
+ {
+ // Arrange - exactly at the boundary (1601-01-02 should be valid)
+ var boundaryTimestamp = new DateTimeOffset(1601, 1, 2, 0, 0, 0, TimeSpan.Zero);
+ var mockFileInfo = new Mock();
+ mockFileInfo.Setup(f => f.LastModified).Returns(boundaryTimestamp);
+
+ var safeFileInfo = new SafeTimestampFileInfo(mockFileInfo.Object);
+
+ // Act
+ var result = safeFileInfo.LastModified;
+
+ // Assert
+ Assert.Equal(boundaryTimestamp, result);
+ }
+
+ [Fact]
+ public void Properties_DelegateCorrectly()
+ {
+ // Arrange
+ var mockFileInfo = new Mock();
+ mockFileInfo.Setup(f => f.Exists).Returns(true);
+ mockFileInfo.Setup(f => f.Length).Returns(12345);
+ mockFileInfo.Setup(f => f.PhysicalPath).Returns("/path/to/file.txt");
+ mockFileInfo.Setup(f => f.Name).Returns("file.txt");
+ mockFileInfo.Setup(f => f.IsDirectory).Returns(false);
+ mockFileInfo.Setup(f => f.LastModified).Returns(ValidTimestamp);
+
+ var safeFileInfo = new SafeTimestampFileInfo(mockFileInfo.Object);
+
+ // Act & Assert
+ Assert.True(safeFileInfo.Exists);
+ Assert.Equal(12345, safeFileInfo.Length);
+ Assert.Equal("/path/to/file.txt", safeFileInfo.PhysicalPath);
+ Assert.Equal("file.txt", safeFileInfo.Name);
+ Assert.False(safeFileInfo.IsDirectory);
+ }
+
+ [Fact]
+ public void CreateReadStream_DelegatesCorrectly()
+ {
+ // Arrange
+ using var expectedStream = new MemoryStream();
+ var mockFileInfo = new Mock();
+ mockFileInfo.Setup(f => f.CreateReadStream()).Returns(expectedStream);
+ mockFileInfo.Setup(f => f.LastModified).Returns(ValidTimestamp);
+
+ var safeFileInfo = new SafeTimestampFileInfo(mockFileInfo.Object);
+
+ // Act
+ var result = safeFileInfo.CreateReadStream();
+
+ // Assert
+ Assert.Same(expectedStream, result);
+ }
+
+ [Fact]
+ public void Constructor_WithNullInner_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.Throws(() => new SafeTimestampFileInfo(null!));
+ }
+ }
+}