macdeploy: subprocess out to zip rather than shutil.make_archive
What changed, and why it matters
This change fixes how the macOS version of Bitcoin's wallet app is packaged into a ZIP file. The old method silently turned file shortcuts (symlinks) inside the app into duplicate copies, which can make the app bundle larger and may break macOS code-signing or notarization checks. The fix calls the system's `zip` command with an option that preserves symlinks. There is no direct evidence this was exploitable as an attack, but it removes a packaging defect that could cause a shipped app to be rejected or behave incorrectly.
Treat as a build-system hardening fix. Verify that produced macOS ZIP archives still pass `codesign --verify` and Apple notarization after the change. No runtime node or wallet action is required.
Security signals we found
Loss of symlink integrity during archive creation
Potential code-signature / notarization breakage on macOS
Packaging defect in release build tooling
Reference to upstream CPython bug #139679
Evidence from the diff
The commit replaces shutil.make_archive(..., format='zip', ...) with subprocess.check_call(['zip', '-ry', ...]) in contrib/macdeploy/macdeployqtplus. CPython issue #139679 notes that shutil.make_archive with the zip format does not preserve symlinks, instead copying the target file. In macOS app bundles, symlinks are common (e.g., Contents/Frameworks/Qt*.framework/Versions/Current). Losing symlinks inflates bundle size and can invalidate code signatures or notarization. The -y flag to zip stores symlinks as symlinks. This only affects macOS release builds that use the optional --zip flag.
Changed components
contrib/macdeploy/macdeployqtplusmacOS Bitcoin-Qt.app release ZIP packagingInspect captured patch +1 / −1
diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus
index 476ac133..dc03d710 100755
--- a/contrib/macdeploy/macdeployqtplus
+++ b/contrib/macdeploy/macdeployqtplus
@@ -499,7 +499,7 @@ if config.zip is not None:
print("+ Removing existing .zip +")
os.unlink(name + ".zip")
- shutil.make_archive('{}'.format(name), format='zip', root_dir='dist', base_dir='Bitcoin-Qt.app')
+ subprocess.check_call(["zip", "-ry", os.path.abspath(name + ".zip"), 'Bitcoin-Qt.app'], cwd='dist')
# ------------------------------------------------
Why this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.