From 97b781961615d56067ccd17c90a5c94100cae575 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Tue, 3 Mar 2026 14:59:10 -0500 Subject: [PATCH 01/16] Add a design doc for deterministic pack --- accepted/2026/deterministic-pack.md | 244 ++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 accepted/2026/deterministic-pack.md diff --git a/accepted/2026/deterministic-pack.md b/accepted/2026/deterministic-pack.md new file mode 100644 index 000000000..7f5e5c2ba --- /dev/null +++ b/accepted/2026/deterministic-pack.md @@ -0,0 +1,244 @@ +# Deterministic Pack + +- Author Name [omajid](https://github.com/omajid) +- GitHub Issue: [#8601](https://github.com/NuGet/Home/issues/8601) + +## Summary + +Make NuGet pack deterministic. When enabled, all operations that produce nuget +packages - dotnet CLI commands, nuget CLI command and msbuild tasks - will +produce nuget packages that are deterministic and, optionally, bit-by-bit +reproducible. + +## Motivation + +Deterministic packages provide a very nice set of security +advantages to a piece of software: + +- Security and verification: It becomes possible to detect and deal with a + whole new class of attacks in the supply chain - including on build servers. + It becomes easier to verify items like SBOMs. + +- Increasing user trust: Users can trust the binaries they have matches the + sources, reducing risks of backdoors and other vulnerabilities. + +- Auditing and Compliance: It becomes easier to verify that the sources and + binaries match for compliance reasons. + +Users are already trying to make packages deterministic on their own; providing +a first class feature will make it easier for them and everyone else to adapt +this too. + +Bit-by-bit reproducible nuget packages are more important than they might +appear at first because the hash of a nuget package is added to the deps.json +of a .NET application that depends on the nuget package. In other words, one +package not being reproducible can impact things that build on it. + +This is also one of the pieces needed to make the .NET SDK build itself +deterministic and reproducible. For more details, see +https://github.com/dotnet/source-build/issues/4963 + +Note: this proposal uses "deterministic" and "reproducible" and interchangeably +as synonyms. .NET uses the term deterministic. The wider ecosystem uses +reproducible too. + +For more information on deterministic and reproducible builds, see: + +- https://en.wikipedia.org/wiki/Reproducible_builds +- https://reproducible-builds.org/ +- New SDL requirement: Enable deterministic builds (https://github.com/dotnet/arcade/issues/15910) + +## Explanation + +Abstractly, being deterministic or reproducible simply means the same inputs +produces the same outputs. + +This proposal is for moving packages built by NuGet.Client from being +non-deterministic to being deterministic in package contents, and optionally to +being bit-by-bit deterministic. + +### Functional explanation + + + + +From an implementation point of view there are 3 levels to deterministic-ness +in NuGet: + +0. Always enabled and already the default. + + Some things that help make nuget packages more deterministic are already + enabled and the default, and can't be turned off. For example order of files + in the nuget package archive is already fully deterministic + ([NuGet.Client#6963](https://github.com/NuGet/NuGet.Client/pull/6963)). + +1. Can be enabled or made the default with low risk + + This includes the names of some files, which are otherwise random and based + on a GUID. The names will now be based on a hash of the file contents. + + This is tied to the `Deterministic` property. Use it like this: + + - For `dotnet pack`: + + Use the `/p:Deterministic=true` argument. For example: + + `dotnet pack /p:Deterministic=true` + + - For msbuild project files: + + Use the property `Deterministic`. For example: + + `true` + + This is already the default for recent versions of .NET, at least as far + as .NET Core 3.0. + + - For `nuget`: + + Use the `-Deterministic` argument. For example: + + `pack packageA.nuspec -Deterministic` + +2. Enabling introduces slight risk + + Some things improve deterministic-ness. However, they violate assumptions + that other/external tools may rely on as contracts. There's a risk + that changes in this bucket can break tools and users. + + The only known instance of this is embedded timestamps in the zip metadata + in the nuget archives. This is controlled via the new (optional) + `DeterministicTimestamp` property. Use it like this: + + - For `dotnet pack`: + + Use the `/p:DeterministicTimestamp={DATE_TIME}` argument. For example, in + bash: + + `dotnet pack /p:DeterministicTimestamp=$(date --rfc-3339=seconds)` + + - For msbuild project files: + + Use `DeterministicTimestamp`. For example: + + ``` + + $(DATE_TIME) + + ``` + + If `DeterministicTimestamp` is not set, but `SOURCE_DATE_EPOCH` is set + (eg, from environment variable), then `DeterministicTimestamp` is set to + the value of `SOURCE_DATE_EPOCH`. + + - For `nuget`: + + Use the `-DeterministicTimestamp {DATE_TIME}` argument. For example: + + `nuget pack packageA.nuspec -DeterministicTimestamp $(date --rfc-3339=seconds)` + + `DeterministicTimestamp` must be either a full date/time string + specified in the RFC3339 format, or a single number indicating the + number of seconds since the unix epoch (`Jan 1 1970, 00:00:00 UTC`). + +### Technical explanation + + + +- `Deterministic` is a boolean. When `Deterministic` is set to true, a single + deterministic time is used as the file modification time for all the files + inside the nuget archive. Which time is used depends on whether + `DeterministicTimestamp` is set or not + +- `DeterministicTimestamp` must be either a full date/time string specified in + the RFC3339 format, or a single number indicating the number of seconds since + the unix epoch. + + If `Deterministic=false`, then `DeterministicTimestamp` is ignored. + + If `DeterministicTimestamp` is not set but `SOURCE_DATE_EPOCH` is set, + `DeterministicTimestamp` will use that value. + + If neither `DeterministicTimestamp` nor `SOURCE_DATE_EPOCH` is set, but + `Deterministic=true` then the current UTC time is used. + + In other words, the precedence is: + + 1. Value of `DeterministicTimestamp` + 2. Value of `SOURCE_DATE_EPOCH` + 3. Value of `DateTimeOffset.UtcNow` + +- The standard `SOURCE_DATE_EPOCH` variable is used to make nuget package + creation more consistent with other tools: + https://reproducible-builds.org/docs/source-date-epoch/ + +- The value of `DeterministicTimestamp` and `SOURCE_DATE_EPOCH` must be in the + range of valid ZIP archive file modification date and times. + +## Drawbacks + +- We enabled this in the past with fixed 1980-based timestamps. Customers + reported deployments failing. Their tools used the time to determine whether + a file was newer, which returned bad results with fixed timestamps. For more + details see [#3388](https://github.com/dotnet/core/issues/3388). To mitigate + this, this proposal uses real wall-clock-based-timestamps by default, and + allows developers to override it. + +- This feature makes the code more complex. + +- The implicit use of `SOURCE_DATE_EPOCH` as an environment variable can lead + to action-at-a-distance issues and it's not immediately obvious how this + variable is used. + +## Rationale and alternatives + + + + + +- We considered making what is now called `DeterministicTimestamp` + automatically infer the timestamp of the last commit in the source repository + through a change in sourcelink. This was deemed as too much code to maintain + for a small benefit: https://github.com/dotnet/sourcelink/pull/1552. + +- We considered making `DeterministicTimestamp` the default, but it is risky. + +## Prior Art + + + + + + +- Deterministic builds were enabled in NuGet.Client in the past (see the + [spec](https://github.com/NuGet/Home/wiki/%5BSpec%5D-Deterministic-Pack)) and + had to be disabled due to regressions: + https://github.com/NuGet/Home/issues/8601 + +- Deterministic builds are supported in many other .NET components, including + [roslyn](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/code-generation#deterministic) + and [sourcelink](https://github.com/dotnet/sourcelink/issues/601) + +- There is an effort to make the entire .NET SDK build end to end + deterministic: https://github.com/dotnet/source-build/issues/4963 + +- Some Linux distributions like Fedora expect all distro-packages, including + .NET, to be deterministic: + - https://fedoraproject.org/wiki/Changes/Package_builds_are_expected_to_be_reproducible + +## Unresolved Questions + + + + + +- Is `DeterministicTimestamp` the best name? + +## Future Possibilities + + + +- With `Deterministic=true` by default, support for `Deterministic=false` could + be fully dropped, and the code paths simplified + +- Should turning off `Deterministic=true` produce warnings or errors? From 1ad01be428099960504cba6634b892f50c036c6e Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Wed, 4 Mar 2026 07:02:09 -0500 Subject: [PATCH 02/16] Update based on feedback --- ...ack.md => deterministic-pack-revisited.md} | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) rename accepted/2026/{deterministic-pack.md => deterministic-pack-revisited.md} (81%) diff --git a/accepted/2026/deterministic-pack.md b/accepted/2026/deterministic-pack-revisited.md similarity index 81% rename from accepted/2026/deterministic-pack.md rename to accepted/2026/deterministic-pack-revisited.md index 7f5e5c2ba..efef333fa 100644 --- a/accepted/2026/deterministic-pack.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -1,6 +1,6 @@ -# Deterministic Pack +# Deterministic Pack Revisited -- Author Name [omajid](https://github.com/omajid) +- Author: [omajid](https://github.com/omajid) - GitHub Issue: [#8601](https://github.com/NuGet/Home/issues/8601) ## Summary @@ -38,7 +38,7 @@ This is also one of the pieces needed to make the .NET SDK build itself deterministic and reproducible. For more details, see https://github.com/dotnet/source-build/issues/4963 -Note: this proposal uses "deterministic" and "reproducible" and interchangeably +Note: this proposal uses "deterministic" and "reproducible" interchangeably as synonyms. .NET uses the term deterministic. The wider ecosystem uses reproducible too. @@ -46,6 +46,8 @@ For more information on deterministic and reproducible builds, see: - https://en.wikipedia.org/wiki/Reproducible_builds - https://reproducible-builds.org/ +- [Deterministic builds in Roslyn](https://blog.paranoidcoding.org/2016/04/05/deterministic-builds-in-roslyn.html) +- [DotNet.ReproducibleBuilds](https://github.com/dotnet/reproducible-builds) - New SDL requirement: Enable deterministic builds (https://github.com/dotnet/arcade/issues/15910) ## Explanation @@ -59,9 +61,6 @@ being bit-by-bit deterministic. ### Functional explanation - - - From an implementation point of view there are 3 levels to deterministic-ness in NuGet: @@ -94,11 +93,11 @@ in NuGet: This is already the default for recent versions of .NET, at least as far as .NET Core 3.0. - - For `nuget`: + - For `NuGet.exe`: Use the `-Deterministic` argument. For example: - `pack packageA.nuspec -Deterministic` + `nuget pack packageA.nuspec -Deterministic` 2. Enabling introduces slight risk @@ -131,7 +130,7 @@ in NuGet: (eg, from environment variable), then `DeterministicTimestamp` is set to the value of `SOURCE_DATE_EPOCH`. - - For `nuget`: + - For `NuGet.exe: Use the `-DeterministicTimestamp {DATE_TIME}` argument. For example: @@ -143,8 +142,6 @@ in NuGet: ### Technical explanation - - - `Deterministic` is a boolean. When `Deterministic` is set to true, a single deterministic time is used as the file modification time for all the files inside the nuget archive. Which time is used depends on whether @@ -180,21 +177,24 @@ in NuGet: - We enabled this in the past with fixed 1980-based timestamps. Customers reported deployments failing. Their tools used the time to determine whether a file was newer, which returned bad results with fixed timestamps. For more - details see [#3388](https://github.com/dotnet/core/issues/3388). To mitigate - this, this proposal uses real wall-clock-based-timestamps by default, and - allows developers to override it. + details see [dotnet/core#3388](https://github.com/dotnet/core/issues/3388). + To mitigate this, this proposal uses real wall-clock-based-timestamps by + default, and allows developers to override the timestamp. - This feature makes the code more complex. - The implicit use of `SOURCE_DATE_EPOCH` as an environment variable can lead - to action-at-a-distance issues and it's not immediately obvious how this - variable is used. + to action-at-a-distance issues. To mitigate this, we will reading this + variable using msbuild which should make the variable's usage and value + available through the binlog. -## Rationale and alternatives +- Package signing breaks the possibility of bit-by-bit reproducibility, due to + embedding a timestamp. Nuget has the concept of a contnet hash, that can + mitigate this somewhat, by comparing the contents of two packages. A command + to show a package's content hash is available starting in .NET 10.0.100: + 0`dotnet nuget verify`. - - - +## Rationale and alternatives - We considered making what is now called `DeterministicTimestamp` automatically infer the timestamp of the last commit in the source repository @@ -203,12 +203,11 @@ in NuGet: - We considered making `DeterministicTimestamp` the default, but it is risky. -## Prior Art +- As an alternative, we can simply not implement this. This would make .NET's + security story and positioning weaker than many other programming language + stacks. - - - - +## Prior Art - Deterministic builds were enabled in NuGet.Client in the past (see the [spec](https://github.com/NuGet/Home/wiki/%5BSpec%5D-Deterministic-Pack)) and @@ -222,22 +221,20 @@ in NuGet: - There is an effort to make the entire .NET SDK build end to end deterministic: https://github.com/dotnet/source-build/issues/4963 +- Some Linux and \*nix distributions actively test all their distribution + packages for reproduciblity and share the live status: + https://reproducible-builds.org/citests/ + - Some Linux distributions like Fedora expect all distro-packages, including - .NET, to be deterministic: + .NET, to be deterministic. This proposal will support that. - https://fedoraproject.org/wiki/Changes/Package_builds_are_expected_to_be_reproducible ## Unresolved Questions - - - - - Is `DeterministicTimestamp` the best name? ## Future Possibilities - - - With `Deterministic=true` by default, support for `Deterministic=false` could be fully dropped, and the code paths simplified From 7e50f08bc8361f8d9af15f68058e3d6cf7a70be4 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Wed, 4 Mar 2026 07:04:31 -0500 Subject: [PATCH 03/16] Fix typo --- accepted/2026/deterministic-pack-revisited.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index efef333fa..46b23eea5 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -192,7 +192,7 @@ in NuGet: embedding a timestamp. Nuget has the concept of a contnet hash, that can mitigate this somewhat, by comparing the contents of two packages. A command to show a package's content hash is available starting in .NET 10.0.100: - 0`dotnet nuget verify`. + `dotnet nuget verify`. ## Rationale and alternatives From e4b8753e4ae6105fedc2d87164429d349dd5cb9c Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Wed, 4 Mar 2026 14:00:01 -0500 Subject: [PATCH 04/16] Add a note about the PackageBuilder API changes --- accepted/2026/deterministic-pack-revisited.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index 46b23eea5..e407ef520 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -99,6 +99,11 @@ in NuGet: `nuget pack packageA.nuspec -Deterministic` + - For `PackageBuilder` API: + + There is no change in the `NuGet.Packaging.PackageBuilder` API. It already + contains support for this via the `deterministic` constructor. + 2. Enabling introduces slight risk Some things improve deterministic-ness. However, they violate assumptions @@ -136,6 +141,19 @@ in NuGet: `nuget pack packageA.nuspec -DeterministicTimestamp $(date --rfc-3339=seconds)` + - For `PackageBuilder` API: + + There's a new property: + + ``` + public string DeterministicTimestamp + { + init { ... } + } + ``` + + This will accept a string-ified version of `{DATE_TIME}`. + `DeterministicTimestamp` must be either a full date/time string specified in the RFC3339 format, or a single number indicating the number of seconds since the unix epoch (`Jan 1 1970, 00:00:00 UTC`). @@ -194,6 +212,10 @@ in NuGet: to show a package's content hash is available starting in .NET 10.0.100: `dotnet nuget verify`. +- This doesn't auto-enable `DeterministicTimestamp` fallback handling in users + of the `PackageBuilder` API. Users of that will need to explicitly specify + the timestamp and re-implement handling of `SOURCE_DATE_EPOCH`. + ## Rationale and alternatives - We considered making what is now called `DeterministicTimestamp` From ed405f06dc9fee8fe18936d9bdb764579d858d41 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Thu, 5 Mar 2026 12:44:32 -0500 Subject: [PATCH 05/16] Fix missing closing quote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Köplinger --- accepted/2026/deterministic-pack-revisited.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index e407ef520..ea7b6c91f 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -135,7 +135,7 @@ in NuGet: (eg, from environment variable), then `DeterministicTimestamp` is set to the value of `SOURCE_DATE_EPOCH`. - - For `NuGet.exe: + - For `NuGet.exe`: Use the `-DeterministicTimestamp {DATE_TIME}` argument. For example: From 3c54ed0b8151cc7a56654a7cb4956702c9491e00 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Thu, 5 Mar 2026 12:47:50 -0500 Subject: [PATCH 06/16] Fix typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Köplinger --- accepted/2026/deterministic-pack-revisited.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index ea7b6c91f..663533569 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -207,7 +207,7 @@ in NuGet: available through the binlog. - Package signing breaks the possibility of bit-by-bit reproducibility, due to - embedding a timestamp. Nuget has the concept of a contnet hash, that can + embedding a timestamp. NuGet has the concept of a content hash, that can mitigate this somewhat, by comparing the contents of two packages. A command to show a package's content hash is available starting in .NET 10.0.100: `dotnet nuget verify`. From cc645bc18b331a8a0a32a0b64fc2946816848938 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Mon, 9 Mar 2026 10:20:04 -0400 Subject: [PATCH 07/16] Minor updates --- accepted/2026/deterministic-pack-revisited.md | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index 663533569..08f472832 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -1,6 +1,6 @@ # Deterministic Pack Revisited -- Author: [omajid](https://github.com/omajid) +- Author Name: [omajid](https://github.com/omajid) - GitHub Issue: [#8601](https://github.com/NuGet/Home/issues/8601) ## Summary @@ -74,7 +74,8 @@ in NuGet: 1. Can be enabled or made the default with low risk This includes the names of some files, which are otherwise random and based - on a GUID. The names will now be based on a hash of the file contents. + on a GUID. The names will now be based on a hash that's going to be + deterministic. This is tied to the `Deterministic` property. Use it like this: @@ -101,8 +102,9 @@ in NuGet: - For `PackageBuilder` API: - There is no change in the `NuGet.Packaging.PackageBuilder` API. It already - contains support for this via the `deterministic` constructor. + There is no change in the `NuGet.Packaging.PackageBuilder` API for + enabling deterministic mode. It already contains support for this via the + `bool deterministic` constructor parameter. 2. Enabling introduces slight risk @@ -154,9 +156,10 @@ in NuGet: This will accept a string-ified version of `{DATE_TIME}`. - `DeterministicTimestamp` must be either a full date/time string - specified in the RFC3339 format, or a single number indicating the - number of seconds since the unix epoch (`Jan 1 1970, 00:00:00 UTC`). + `DeterministicTimestamp` must be either a full date/time string specified in + the RFC3339 format, or a single number indicating the number of seconds + since the unix epoch (`Jan 1 1970, 00:00:00 UTC`). The behaviour is + unspecified if the value can not be parsed. ### Technical explanation @@ -167,17 +170,18 @@ in NuGet: - `DeterministicTimestamp` must be either a full date/time string specified in the RFC3339 format, or a single number indicating the number of seconds since - the unix epoch. + the unix epoch (which is `Jan 1 1970, 00:00:00 UTC`). If `Deterministic=false`, then `DeterministicTimestamp` is ignored. - If `DeterministicTimestamp` is not set but `SOURCE_DATE_EPOCH` is set, - `DeterministicTimestamp` will use that value. +- In an msbuild context using the `PackTask`, if `DeterministicTimestamp` is + not set but `SOURCE_DATE_EPOCH` is set, `DeterministicTimestamp` will use + that value. If neither `DeterministicTimestamp` nor `SOURCE_DATE_EPOCH` is set, but `Deterministic=true` then the current UTC time is used. - In other words, the precedence is: + In other words, when using the `PackTask` the precedence is: 1. Value of `DeterministicTimestamp` 2. Value of `SOURCE_DATE_EPOCH` @@ -188,7 +192,8 @@ in NuGet: https://reproducible-builds.org/docs/source-date-epoch/ - The value of `DeterministicTimestamp` and `SOURCE_DATE_EPOCH` must be in the - range of valid ZIP archive file modification date and times. + range of valid ZIP archive file modification date and times. If this is + violated, zip-creation code will throw exceptions. ## Drawbacks @@ -207,8 +212,8 @@ in NuGet: available through the binlog. - Package signing breaks the possibility of bit-by-bit reproducibility, due to - embedding a timestamp. NuGet has the concept of a content hash, that can - mitigate this somewhat, by comparing the contents of two packages. A command + embedding a timestamp. Nuget has the concept of a content hash, which can + mitigate this somewhat, by comparing the contents of two packages. A command to show a package's content hash is available starting in .NET 10.0.100: `dotnet nuget verify`. From fbfde72bee39fbd6e2cdebe084f66cb56213dba3 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Mon, 9 Mar 2026 10:20:37 -0400 Subject: [PATCH 08/16] Remove unanswered question section During review, we agreed that the DeterministicTimestamp is a good name. --- accepted/2026/deterministic-pack-revisited.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index 08f472832..e3ac63a0d 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -256,10 +256,6 @@ in NuGet: .NET, to be deterministic. This proposal will support that. - https://fedoraproject.org/wiki/Changes/Package_builds_are_expected_to_be_reproducible -## Unresolved Questions - -- Is `DeterministicTimestamp` the best name? - ## Future Possibilities - With `Deterministic=true` by default, support for `Deterministic=false` could From 5cc96f7fb4c303dd2b98042a84456019b74d4294 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Thu, 12 Mar 2026 11:59:54 -0400 Subject: [PATCH 09/16] Update based on feedback - Make summary more focused and explicit about changes. - Drop the technical explanation section. --- accepted/2026/deterministic-pack-revisited.md | 45 +++---------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index e3ac63a0d..4be2e2316 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -5,10 +5,11 @@ ## Summary -Make NuGet pack deterministic. When enabled, all operations that produce nuget -packages - dotnet CLI commands, nuget CLI command and msbuild tasks - will -produce nuget packages that are deterministic and, optionally, bit-by-bit -reproducible. +Make NuGet packaging deterministic by default by respecting the existing +`Deterministic` property. + +Optionally, support making NuGet packages bit-by-bit deterministic and +reproducible through the new `DeterministicTimestamp` property. ## Motivation @@ -113,7 +114,7 @@ in NuGet: that changes in this bucket can break tools and users. The only known instance of this is embedded timestamps in the zip metadata - in the nuget archives. This is controlled via the new (optional) + in the nuget archives. This is controlled via the new (optional) `DeterministicTimestamp` property. Use it like this: - For `dotnet pack`: @@ -161,40 +162,6 @@ in NuGet: since the unix epoch (`Jan 1 1970, 00:00:00 UTC`). The behaviour is unspecified if the value can not be parsed. -### Technical explanation - -- `Deterministic` is a boolean. When `Deterministic` is set to true, a single - deterministic time is used as the file modification time for all the files - inside the nuget archive. Which time is used depends on whether - `DeterministicTimestamp` is set or not - -- `DeterministicTimestamp` must be either a full date/time string specified in - the RFC3339 format, or a single number indicating the number of seconds since - the unix epoch (which is `Jan 1 1970, 00:00:00 UTC`). - - If `Deterministic=false`, then `DeterministicTimestamp` is ignored. - -- In an msbuild context using the `PackTask`, if `DeterministicTimestamp` is - not set but `SOURCE_DATE_EPOCH` is set, `DeterministicTimestamp` will use - that value. - - If neither `DeterministicTimestamp` nor `SOURCE_DATE_EPOCH` is set, but - `Deterministic=true` then the current UTC time is used. - - In other words, when using the `PackTask` the precedence is: - - 1. Value of `DeterministicTimestamp` - 2. Value of `SOURCE_DATE_EPOCH` - 3. Value of `DateTimeOffset.UtcNow` - -- The standard `SOURCE_DATE_EPOCH` variable is used to make nuget package - creation more consistent with other tools: - https://reproducible-builds.org/docs/source-date-epoch/ - -- The value of `DeterministicTimestamp` and `SOURCE_DATE_EPOCH` must be in the - range of valid ZIP archive file modification date and times. If this is - violated, zip-creation code will throw exceptions. - ## Drawbacks - We enabled this in the past with fixed 1980-based timestamps. Customers From 2d21d92bbc6bf2a14d09affa4b329e5b5349c3c4 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Fri, 13 Mar 2026 10:11:41 -0400 Subject: [PATCH 10/16] Update based on feedback --- accepted/2026/deterministic-pack-revisited.md | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index 4be2e2316..bf56f04cb 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -5,16 +5,18 @@ ## Summary -Make NuGet packaging deterministic by default by respecting the existing -`Deterministic` property. +Make NuGet packaging more deterministic by default. NuGet will start respecting +the existing `Deterministic` property, which already defaults to `true`. Optionally, support making NuGet packages bit-by-bit deterministic and reproducible through the new `DeterministicTimestamp` property. +This is targeted for .NET 11. + ## Motivation -Deterministic packages provide a very nice set of security -advantages to a piece of software: +Deterministic packages provide a very nice set of security advantages to a +piece of software: - Security and verification: It becomes possible to detect and deal with a whole new class of attacks in the supply chain - including on build servers. @@ -57,8 +59,8 @@ Abstractly, being deterministic or reproducible simply means the same inputs produces the same outputs. This proposal is for moving packages built by NuGet.Client from being -non-deterministic to being deterministic in package contents, and optionally to -being bit-by-bit deterministic. +non-deterministic to being deterministic in package contents by default, and +optionally to being bit-by-bit deterministic. ### Functional explanation @@ -68,17 +70,18 @@ in NuGet: 0. Always enabled and already the default. Some things that help make nuget packages more deterministic are already - enabled and the default, and can't be turned off. For example order of files - in the nuget package archive is already fully deterministic + enabled and the default in already-released versions of NuGet.Client. Some + can't be turned off. For example, the order of files within the nuget + package archive is already fully deterministic ([NuGet.Client#6963](https://github.com/NuGet/NuGet.Client/pull/6963)). -1. Can be enabled or made the default with low risk +1. New: Enable more determinism by default - This includes the names of some files, which are otherwise random and based - on a GUID. The names will now be based on a hash that's going to be + This includes the names of psmdcp files, which are otherwise random and + based on a GUID. The names will now be based on a hash that's going to be deterministic. - This is tied to the `Deterministic` property. Use it like this: + This is tied to the existing `Deterministic` property. Use it like this: - For `dotnet pack`: @@ -86,14 +89,17 @@ in NuGet: `dotnet pack /p:Deterministic=true` + This property is already the set to true in recent versions of .NET, at + least as far as .NET Core 3.0. + - For msbuild project files: Use the property `Deterministic`. For example: `true` - This is already the default for recent versions of .NET, at least as far - as .NET Core 3.0. + This is property is already set to true in recent versions of .NET, at + least as far as .NET Core 3.0. - For `NuGet.exe`: @@ -107,15 +113,15 @@ in NuGet: enabling deterministic mode. It already contains support for this via the `bool deterministic` constructor parameter. -2. Enabling introduces slight risk +2. New: Optionally enable things that introduces slight risk Some things improve deterministic-ness. However, they violate assumptions - that other/external tools may rely on as contracts. There's a risk - that changes in this bucket can break tools and users. + that other/external tools may rely on as contracts. There's a risk that + changes in this bucket can break tools and users. The only known instance of this is embedded timestamps in the zip metadata in the nuget archives. This is controlled via the new (optional) - `DeterministicTimestamp` property. Use it like this: + `DeterministicTimestamp` property. Use it like this: - For `dotnet pack`: @@ -174,9 +180,9 @@ in NuGet: - This feature makes the code more complex. - The implicit use of `SOURCE_DATE_EPOCH` as an environment variable can lead - to action-at-a-distance issues. To mitigate this, we will reading this - variable using msbuild which should make the variable's usage and value - available through the binlog. + to action-at-a-distance issues. To mitigate this, we will read this variable + using msbuild which should make the variable's usage and value available + through the binlog. - Package signing breaks the possibility of bit-by-bit reproducibility, due to embedding a timestamp. Nuget has the concept of a content hash, which can From 731fe3f715d29013dfba9b2e7e33d0b7c31e6391 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Fri, 13 Mar 2026 16:45:55 -0400 Subject: [PATCH 11/16] More updates based on feedback and discussions --- accepted/2026/deterministic-pack-revisited.md | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index bf56f04cb..aac7b94f9 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -77,9 +77,12 @@ in NuGet: 1. New: Enable more determinism by default - This includes the names of psmdcp files, which are otherwise random and - based on a GUID. The names will now be based on a hash that's going to be - deterministic. + This includes using a single timestamp for all files added to the archive. + Currently, this also includes the making the names of psmdcp files + deterministic, which are otherwise random and based on a GUID. + + In the future this might affect more things that are safe to enable by + default. This is tied to the existing `Deterministic` property. Use it like this: @@ -107,11 +110,15 @@ in NuGet: `nuget pack packageA.nuspec -Deterministic` + The `-Deterministic` flag is **not** the default. + - For `PackageBuilder` API: - There is no change in the `NuGet.Packaging.PackageBuilder` API for - enabling deterministic mode. It already contains support for this via the - `bool deterministic` constructor parameter. + There is a **behavioural change** in the `NuGet.Packaging.PackageBuilder` + API: not explicitly setting `deterministic` value in the constructor will + use a default of `true` now. There is no change to the API itself: it + already contains support for the `bool deterministic` constructor + parameter. 2. New: Optionally enable things that introduces slight risk @@ -161,7 +168,8 @@ in NuGet: } ``` - This will accept a string-ified version of `{DATE_TIME}`. + This will accept a string-ified version of `{DATE_TIME}`. If + `Deterministic` is explicitly set to `false`, this will have not be used. `DeterministicTimestamp` must be either a full date/time string specified in the RFC3339 format, or a single number indicating the number of seconds @@ -201,8 +209,6 @@ in NuGet: through a change in sourcelink. This was deemed as too much code to maintain for a small benefit: https://github.com/dotnet/sourcelink/pull/1552. -- We considered making `DeterministicTimestamp` the default, but it is risky. - - As an alternative, we can simply not implement this. This would make .NET's security story and positioning weaker than many other programming language stacks. @@ -232,6 +238,6 @@ in NuGet: ## Future Possibilities - With `Deterministic=true` by default, support for `Deterministic=false` could - be fully dropped, and the code paths simplified + be fully dropped, and the code paths simplified. - Should turning off `Deterministic=true` produce warnings or errors? From 176947b81c878ea7b515f722e882f511f188c4f0 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Fri, 13 Mar 2026 16:55:34 -0400 Subject: [PATCH 12/16] Remove extra 'the' Co-authored-by: Frulfump --- accepted/2026/deterministic-pack-revisited.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index aac7b94f9..84b19e9c3 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -78,7 +78,7 @@ in NuGet: 1. New: Enable more determinism by default This includes using a single timestamp for all files added to the archive. - Currently, this also includes the making the names of psmdcp files + Currently, this also includes making the names of psmdcp files deterministic, which are otherwise random and based on a GUID. In the future this might affect more things that are safe to enable by From 3a63600f1a77a9928d32972fd8122392d71b74c2 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Tue, 17 Mar 2026 09:23:37 -0400 Subject: [PATCH 13/16] More adjustments based on feedback --- accepted/2026/deterministic-pack-revisited.md | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index 84b19e9c3..afd80bf99 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -77,8 +77,11 @@ in NuGet: 1. New: Enable more determinism by default - This includes using a single timestamp for all files added to the archive. - Currently, this also includes making the names of psmdcp files + This includes using a single timestamp, based on the current wall clock + time, for all files added to the archive. See the `DeterministicTimestamp` + property below on how to override the timestamp. + + Currently, this also includes the making the names of psmdcp files deterministic, which are otherwise random and based on a GUID. In the future this might affect more things that are safe to enable by @@ -92,8 +95,8 @@ in NuGet: `dotnet pack /p:Deterministic=true` - This property is already the set to true in recent versions of .NET, at - least as far as .NET Core 3.0. + This property is already the set to `true` in recent versions of .NET, at + least as far back as .NET Core 3.0. - For msbuild project files: @@ -101,7 +104,7 @@ in NuGet: `true` - This is property is already set to true in recent versions of .NET, at + This is property is already set to `true` in recent versions of .NET, at least as far as .NET Core 3.0. - For `NuGet.exe`: @@ -114,11 +117,11 @@ in NuGet: - For `PackageBuilder` API: - There is a **behavioural change** in the `NuGet.Packaging.PackageBuilder` - API: not explicitly setting `deterministic` value in the constructor will - use a default of `true` now. There is no change to the API itself: it - already contains support for the `bool deterministic` constructor - parameter. + There is a **behavioral change** in the `NuGet.Packaging.PackageBuilder` + API: not explicitly setting the `deterministic` parameter in the + constructor will use a default of `true` now. There is no change to the + API itself: it already contains support for the `bool deterministic` + constructor parameter. 2. New: Optionally enable things that introduces slight risk @@ -169,12 +172,12 @@ in NuGet: ``` This will accept a string-ified version of `{DATE_TIME}`. If - `Deterministic` is explicitly set to `false`, this will have not be used. + `Deterministic` is explicitly set to `false`, this will not be used. `DeterministicTimestamp` must be either a full date/time string specified in - the RFC3339 format, or a single number indicating the number of seconds - since the unix epoch (`Jan 1 1970, 00:00:00 UTC`). The behaviour is - unspecified if the value can not be parsed. + the [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) format, or a single + number indicating the number of seconds since the unix epoch (`Jan 1 1970, + 00:00:00 UTC`). The behaviour is unspecified if the value can not be parsed. ## Drawbacks From 3af84e2786405bd32d7874de1a14ede999177c45 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Tue, 17 Mar 2026 10:19:26 -0400 Subject: [PATCH 14/16] Fix extra the's --- accepted/2026/deterministic-pack-revisited.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index afd80bf99..170aebf10 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -81,7 +81,7 @@ in NuGet: time, for all files added to the archive. See the `DeterministicTimestamp` property below on how to override the timestamp. - Currently, this also includes the making the names of psmdcp files + Currently, this also includes making the names of psmdcp files deterministic, which are otherwise random and based on a GUID. In the future this might affect more things that are safe to enable by @@ -95,7 +95,7 @@ in NuGet: `dotnet pack /p:Deterministic=true` - This property is already the set to `true` in recent versions of .NET, at + This property is already set to `true` in recent versions of .NET, at least as far back as .NET Core 3.0. - For msbuild project files: From 28018cb4dbea892cdb2fc2b40a37e79228fc39ee Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Tue, 17 Mar 2026 12:48:34 -0400 Subject: [PATCH 15/16] Update accepted/2026/deterministic-pack-revisited.md Co-authored-by: Matt Kotsenas <51421+MattKotsenas@users.noreply.github.com> --- accepted/2026/deterministic-pack-revisited.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index 170aebf10..1777bd6c2 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -29,7 +29,7 @@ piece of software: binaries match for compliance reasons. Users are already trying to make packages deterministic on their own; providing -a first class feature will make it easier for them and everyone else to adapt +a first-class feature will make it easier for them and everyone else to adopt this too. Bit-by-bit reproducible nuget packages are more important than they might From 69198d88e61384806aff33bae25f107a0bd650c9 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Thu, 19 Mar 2026 07:31:37 -0400 Subject: [PATCH 16/16] Update based on feedback --- accepted/2026/deterministic-pack-revisited.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/accepted/2026/deterministic-pack-revisited.md b/accepted/2026/deterministic-pack-revisited.md index 1777bd6c2..5a32c9c9f 100644 --- a/accepted/2026/deterministic-pack-revisited.md +++ b/accepted/2026/deterministic-pack-revisited.md @@ -79,7 +79,8 @@ in NuGet: This includes using a single timestamp, based on the current wall clock time, for all files added to the archive. See the `DeterministicTimestamp` - property below on how to override the timestamp. + property below on how to override the timestamp, or how to opt out of this + behaviour. Currently, this also includes making the names of psmdcp files deterministic, which are otherwise random and based on a GUID. @@ -174,10 +175,17 @@ in NuGet: This will accept a string-ified version of `{DATE_TIME}`. If `Deterministic` is explicitly set to `false`, this will not be used. - `DeterministicTimestamp` must be either a full date/time string specified in - the [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) format, or a single - number indicating the number of seconds since the unix epoch (`Jan 1 1970, - 00:00:00 UTC`). The behaviour is unspecified if the value can not be parsed. + `DeterministicTimestamp` must be one of: + + - The literal string `true` to indicate using `DateTime.UtcNow` as the + timestamp for all files added to the archive. + - The literal string `false` to use the original file modification times. + - A full date/time string specified in the [RFC + 3339](https://www.rfc-editor.org/rfc/rfc3339) format + - A a single number indicating the number of seconds since the unix epoch + (`Jan 1 1970, 00:00:00 UTC`). + + The default value of `DeterministicTimestamp` is `true`. ## Drawbacks