fix(release): make unsigned Windows artifacts reproducible - #4690
Conversation
Exclude node-gyp projects and link scratch files from the existing global file set while preserving the native addons and ConPTY runtime helpers. This avoids creating a platform-only catch-all matcher that would reintroduce tests and renderer side-files. Exercise electron-builder normalization and dependency collection against representative runtime and build-output fixtures. Generated-by: Codex
Apply /Brepro and /DEBUG:NONE through the existing Windows command runner. /Brepro derives PE timestamps from content; /DEBUG:NONE removes unshipped PDB identity and paths after node-gyp adds /DEBUG. Two clean builds remained byte-identical after removing /INCREMENTAL:NO. Removing /DEBUG:NONE reintroduced four unpacked-payload differences, so the two remaining flags are the minimal set. Generated-by: Codex
The two clean Windows builds have identical compressed ZIP entry data; all remaining ZIP differences are DOS and NTFS modification times. Disable modification and access times at the existing 7-Zip invocation. This requires a one-line app-builder-lib patch because there is no archive flag passthrough. Test real ZIP bytes with different source timestamps and verify the extracted payload. Generated-by: Codex
The only differing NSIS header fields are the build-time FILETIMEs of app-64.7z and the uninstaller. SetDateSave off removes those fields at compilation without changing the embedded bytes. A real NSIS fixture verifies identical output after changing input timestamps; the full Windows release remains the acceptance test. Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
98a0157 to
ffc1f10
Compare
jackwener
left a comment
There was a problem hiding this comment.
Approving at exact head ffc1f104. All eight checks pass. No findings.
The patch is minimal and the licensing is complete
The change to app-builder-lib is one line — args.push("-mtm=off", "-mta=off") — which stops 7z from storing modification and access times. Timestamps are the usual reason two builds of identical content differ, so this is the smallest edit that reaches the goal.
The LICENSE entry is what makes redistributing that patch correct, and it has every part it needs: the upstream source and repository, the exact version the patch applies to, the path to the patch in this tree, the MIT license it inherits, the full license text, and a sentence stating what the patched material actually does. Nothing here has to be inferred by a downstream consumer.
The tests pin reproducibility rather than merely exercising it
Both new cases build twice with different input timestamps and require the outputs to be byte-identical:
assert.ok(results[0].equals(results[1]), 'ZIP bytes changed with the input file timestamp');
assert.ok(results[0].equals(results[1]), 'NSIS bytes changed with the embedded file timestamp');What makes them more than a byte comparison is the assertion that comes first. The ZIP case asserts the extracted payload is identical payload, and the NSIS case asserts the produced bytes still contain the embedded payload. Without those, two builds that both produced an empty or truncated archive would compare equal and the test would pass having verified nothing. The precondition is what keeps "identical" from being vacuously true — and it is the part most easily left out.
release-windows-check.yml puts the check in CI rather than leaving it as something to remember to run.
This is a release-tooling fix, so the merge decision remains a human's.
简体中文
在 ffc1f104 上批准。八项检查全部通过。没有发现问题。
补丁极小,而许可声明是完整的
对 app-builder-lib 的改动只有一行——args.push("-mtm=off", "-mta=off")——它让 7z 不再存储修改时间与访问时间。时间戳正是「内容相同的两次构建却产出不同字节」的常见原因,所以这是能达到目的的最小改动。
LICENSE 条目是「重新分发这个补丁」得以正当的关键,而它该有的部分一样不缺:上游来源与仓库、补丁所针对的确切版本、补丁在本仓库中的路径、它所继承的 MIT 许可、完整的许可文本,以及一句说明被打补丁的材料实际做了什么。下游使用者不需要去推断任何东西。
测试钉住了可复现性,而不只是演练了它
两条新用例都用不同的输入时间戳构建两次,并要求输出逐字节相同:
assert.ok(results[0].equals(results[1]), 'ZIP bytes changed with the input file timestamp');
assert.ok(results[0].equals(results[1]), 'NSIS bytes changed with the embedded file timestamp');让它们不止是一次字节比较的,是排在前面的那条断言。 ZIP 那条先断言解出的 payload 是 identical payload,NSIS 那条先断言产出的字节仍包含被嵌入的 payload。没有它们,两次都产出空归档或截断归档的构建同样会「相等」,测试会在什么都没验证的情况下通过。 那条前置条件正是让「相同」不至于平凡为真的东西——而它也是最容易被省略的一部分。
release-windows-check.yml 把这道检查放进了 CI,而不是留成一件「要记得去跑」的事。
这是发布工具链的修复,合并与否仍由人决定。
Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.
Summary
Make independently built unsigned Windows x64 payloads byte-identical while keeping
latest.yml.releaseDateas real publication metadata./Brepro /DEBUG:NONElinker flagsRefs #3276 and #3414. This does not add signing credentials or claim trusted-hardware release validation.
Verification
npm run buildnpm run check:release— 194 tests passednpm run test:windows-archives— 2 tests passednpm run formatnpm run lintactionlint .github/workflows/release-windows-check.yml/INCREMENTAL:NOremoved. The comparison job reports onlylatest.ymland the non-distributedbuilder-debug.yml./DEBUG:NONEreintroduced four unpacked-payload differences, so/Brepro /DEBUG:NONEis the minimal linker set.AI use
Select exactly one:
Tool(s) and scope: Codex diagnosed the byte differences, implemented the packaging changes and regression tests, and ran the validation and ablation experiments. Every affected commit retains a
Generated-by: Codextrailer.Checklist
Does this PR entail a change in behavior?