Skip to content

fix: Made build output reproducible#3709

Open
Gueupo wants to merge 1 commit intomozilla:masterfrom
Gueupo:fix/deterministic-zip-build
Open

fix: Made build output reproducible#3709
Gueupo wants to merge 1 commit intomozilla:masterfrom
Gueupo:fix/deterministic-zip-build

Conversation

@Gueupo
Copy link
Copy Markdown

@Gueupo Gueupo commented May 5, 2026

Fixes #3613.

Motivation

web-ext build currently produces ZIPs whose bytes vary between runs even when
the source tree is unchanged: the zip-dir wrapper passes no date option to
JSZip, so every entry is stamped with new Date(), and because of asynchronicity,
order isn't guaranteed. This breaks downstream verification : checksum-based
provenance, SLSA/in-toto attestations, self-hosted XPI mirrors, build caches.

Per @Rob--W's design notes in #3613, this PR cuts out the zip-dir middleman
(unmaintained for 6 years) and uses JSZip directly with deterministic options.

What changed

  • src/cmd/build.js: replaced zip-dir with an inline routine that walks
    the source tree depth-first in lexicographic order, stamps every entry
    (including explicit folder entries) with a fixed timestamp, normalizes
    path separators to /, and pins platform: 'UNIX' in generateAsync.
  • The default timestamp is 1980-01-01T00:00:00Z (the ZIP DOS-time minimum,
    obviously synthetic). It can be overridden via the standard
    SOURCE_DATE_EPOCH environment variable.
  • package.json / package-lock.json: dropped zip-dir. jszip is already
    a direct dep (used by submit-addon.js); no new dependencies.
  • tests/unit/test-cmd/test.build.js: two new tests open the produced ZIP
    and assert every entry's date matches the expected fixed timestamp
    (default + SOURCE_DATE_EPOCH override). Verified that both tests fail
    reliably on the pre-fix implementation.
  • README.md: updated.

Non-obvious choices

  • SOURCE_DATE_EPOCH rather than a WEB_EXT_* variable: the canonical
    spec name is what cargo, dpkg, sphinx and other reproducible-build tools
    already read, so reusing it lets web-ext slot into existing pipelines.
  • 1980-01-01 as the default: it is the earliest timestamp the ZIP DOS-time
    format can encode, and the value being obviously synthetic signals to
    anyone inspecting the archive that this is not a real mtime.
  • platform: 'UNIX': without it JSZip derives the external-attribute byte
    from process.platform, which would make the same source produce
    different bytes on Windows vs Linux.
  • Fixed timestamp rather than file mtime: mtimes are reset to the checkout
    time by git clone, which would defeat the cross-machine reproducibility
    the use case in Unpredictable checksum #3613 needs.

Acknowledgements

Big thanks to @kylekatarnls and @Rob--W for the original investigation and discussion !

  Fixes mozilla#3613.

  Replaced the unmaintained zip-dir dependency with an inline JSZip routine
  that walks the source tree in lexicographic order, stamps every entry
  (including explicit folder entries) with a fixed timestamp, normalizes
  path separators to '/', and pins platform: 'UNIX' in generateAsync so
  the same source produces identical bytes regardless of host OS.

  The default timestamp is 1980-01-01T00:00:00Z (the ZIP DOS-time minimum,
  obviously synthetic). It can be overridden via the standard
  SOURCE_DATE_EPOCH environment variable.

  Co-authored-by: kylekatarnls <kylekatarnls@gmail.com>
@Gueupo Gueupo mentioned this pull request May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unpredictable checksum

1 participant