From 87122082fdcc0e7d9d92a473754f95efe9aef503 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 21 Feb 2026 15:28:51 +0100 Subject: [PATCH] Set fixed timestamp in ZIP exports to ensure deterministic outputs. --- .../Endpoints/WorkflowDefinitions/Export/Endpoint.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs index 506dea1bb..14cf00ffd 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs @@ -133,6 +133,12 @@ internal class Export : ElsaEndpoint var zipStream = new MemoryStream(); var sortedDefinitions = definitions.OrderBy(d => d.DefinitionId).ToList(); + // NOTE: + // - ZIP timestamps cannot be earlier than 1980-01-01 (the ZIP format's minimum). + // - We intentionally use a fixed timestamp (instead of DateTimeOffset.UtcNow) to keep exports deterministic. + // This avoids producing different ZIP bytes for identical exports, which helps tests, caching, and diffing. + var zipEpoch = new DateTimeOffset(1980, 1, 1, 0, 0, 0, TimeSpan.Zero); + #if NET10_0_OR_GREATER await using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true)) { @@ -143,7 +149,7 @@ internal class Export : ElsaEndpoint var binaryJson = await SerializeWorkflowDefinitionAsync(model, cancellationToken); var fileName = GetFileName(model); var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal); - entry.LastWriteTime = DateTimeOffset.UnixEpoch; + entry.LastWriteTime = zipEpoch; await using var entryStream = await entry.OpenAsync(cancellationToken); await entryStream.WriteAsync(binaryJson, cancellationToken); } @@ -158,7 +164,7 @@ internal class Export : ElsaEndpoint var binaryJson = await SerializeWorkflowDefinitionAsync(model, cancellationToken); var fileName = GetFileName(model); var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal); - entry.LastWriteTime = DateTimeOffset.UnixEpoch; + entry.LastWriteTime = zipEpoch; await using var entryStream = entry.Open(); await entryStream.WriteAsync(binaryJson, cancellationToken); }