Bump to 4.5.0: preserve provider XCFramework signatures through staging - #244
Merged
Merged
Conversation
Xcode records the state of every .xcframework an app links against AS ITS PUBLISHER SHIPPED IT and writes that into the IPA as Signatures/<name>.xcframework-<platform>.signature. ITMS-91065: Missing signature (flet-dev/flet#6724) reports on THOSE receipts -- not on the embedded copy, which Xcode re-signs with the submitting team's identity at embed and again at exportArchive. This retracts the reasoning behind 4.4.2. That release namespaced framework CFBundleIdentifiers under the host app on the theory that a shared org.python.* identifier was what Apple keyed on, and argued that "a signature applied at the source can't matter" because exportArchive overwrites it. The overwrite is real; the conclusion was not. Worse, the fix was counterproductive: rewriting an Info.plist inside a provider XCFramework is exactly what destroys the SDK-origin signature the receipt reports on. So provider artifacts are now immutable here. Locally built site-package frameworks are created in their own temporary directory, mutated there (bundle ids, install names, ad-hoc re-sign), and merged over verbatim provider copies afterwards. Both rewrite_framework_bundle_ids calls on provider directories are gone; the helper's contract now says provider bundles are off limits and why. Provider frameworks carry stable dev.flet.python.* / dev.flet.dartbridge identifiers assigned upstream, so there was nothing left for it to fix anyway. xcframework_verify.sh adds two independent guards at every staging step: * A SHA-256 manifest (files plus symlink targets) recorded when the archives are extracted, re-checked after site-package sync and after SwiftPM staging. Always fatal -- a mismatch is a bug in this repo, not a property of whatever upstream release is pinned. This keeps working even against unsigned artifacts, which is what makes the guarantee testable rather than aspirational. * codesign verification of the provider signature: real Timestamp=, expected authority, and TeamIdentifier when SERIOUS_PYTHON_EXPECTED_TEAM_ID is set. Gated by SERIOUS_PYTHON_VERIFY_PROVIDER_SIGNATURES (warn default / require / off). The default is warn so pinning a pre-signing python-build or dart-bridge release still builds; use require for App Store submissions. prepare_macos.sh's Python.app removal now runs before the snapshot rather than after it. python-build has excluded Python.app upstream for some time so it matches nothing on current artifacts, but it does delete a file from inside a provider bundle, so it cannot sit after the provenance check. iOS packaging: the CocoaPods path installs stdlib and site-package extensions by copying INNER .framework bundles in a Pods-Runner-frameworks.sh script phase. Xcode only writes SDK-origin receipts for xcframeworks it consumes as declared binary dependencies, so that path cannot produce complete receipts however well the source is signed. It now emits an Xcode `warning:` saying so, and the README documents SwiftPM as the required path for App Store submissions. Re-pins python-build to 20260729 and dart_bridge to 1.7.0, the first releases of each whose Apple XCFrameworks are provider-signed. No Python version moved (3.12.13 / 3.13.14 / 3.14.6). dart_bridge's Android .so's and Linux .so are byte-identical to 1.6.1 (verified); the Windows DLL is rebuilt because MSVC stamps a timestamp and PDB GUID into every build, with no Windows source change. Verified end to end against the signed artifacts with require mode and the real team id: 57 provider signatures verified at extraction, digests unchanged after sync and SwiftPM staging, and the staged _ssl.xcframework inside the Swift package still reporting Identifier=dev.flet.python.-ssl with its original signing timestamp -- i.e. the signature survived download, extract, sync and staging untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third and last of the three-repo effort. dart-bridge#13 → v1.7.0 and python-build#37 → 20260729 are both released and verified signed; this side stops destroying those signatures and starts checking them.
What was wrong
Xcode records the state of every
.xcframeworkan app links against as its publisher shipped it, and writes that into the IPA asSignatures/<name>.xcframework-<platform>.signature.ITMS-91065: Missing signature(flet-dev/flet#6724) reports on those receipts — not on the embedded copy, which Xcode re-signs with the submitting team's identity at embed and again atexportArchive.This retracts the reasoning behind 4.4.2. That release namespaced framework
CFBundleIdentifiers under the host app, on the theory that a sharedorg.python.*identifier was what Apple keyed on, and argued that "a signature applied at the source can't matter" becauseexportArchiveoverwrites it. The overwrite is real; the conclusion was not. And the fix was counterproductive — rewriting anInfo.plistinside a provider XCFramework is precisely what destroys the SDK-origin signature the receipt reports on.Provider artifacts are now immutable here
Locally built site-package frameworks are created in their own temporary directory, mutated there (bundle ids, install names, ad-hoc re-sign), and merged over verbatim provider copies afterwards. Both
rewrite_framework_bundle_idscalls on provider directories are gone, and the helper's doc comment now states that provider bundles are off limits and why.SERIOUS_PYTHON_BUNDLE_IDstill works, and still does the same thing — for frameworks built from your wheels. Provider frameworks arrive with stabledev.flet.python.*/dev.flet.dartbridgeidentifiers assigned upstream, so there is nothing left for it to fix.Two independent guards —
xcframework_verify.shDigest manifest. SHA-256 over every file plus every symlink target, recorded when the archives are extracted and re-checked after site-package sync and after SwiftPM staging. Always fatal — a mismatch is a bug in this repo, not a property of whatever upstream release is pinned. Crucially this works even against unsigned artifacts, which is what makes "we don't touch these" a tested property rather than an aspiration.
Signature verification. Real
Timestamp=, expected authority, andTeamIdentifierwhenSERIOUS_PYTHON_EXPECTED_TEAM_IDis set. Gated bySERIOUS_PYTHON_VERIFY_PROVIDER_SIGNATURES:warn(default)requireoffThe default is
warndeliberately: a consumer can pin a pre-signing python-build or dart-bridge release throughSERIOUS_PYTHON_BUILD_DATE/DART_BRIDGE_VERSION, and that must keep building.prepare_macos.sh'sPython.appremoval now runs before the snapshot rather than after. python-build has excludedPython.appupstream for a while so it matches nothing on current artifacts, but it does delete a file from inside a provider bundle, so it cannot sit after the provenance check.iOS packaging path
The CocoaPods path installs stdlib and site-package extensions by copying inner
.frameworkbundles in aPods-Runner-frameworks.shscript phase. Xcode only writes SDK-origin receipts for xcframeworks it consumes as declared binary dependencies, so that path cannot produce complete receipts however well the source is signed. It now emits an Xcodewarning:saying so, and the README documents SwiftPM as the required path for App Store submissions.Replacing that hack with real vendored XCFramework declarations is the eventual fix; it is a much larger change to the embed path and is deliberately not in this PR.
Snapshot re-pin
python-build 20260729, dart_bridge 1.7.0. No Python version moved — 3.12.13 / 3.13.14 / 3.14.6 and Pyodide unchanged from 20260727. Per-platform, verified rather than assumed:
.sos andlibdart_bridge-linux-*.so: byte-identical to 1.6.1dart_bridge.dll: rebuilt, because MSVC stamps a build timestamp and PDB GUID into every DLL; no Windows source changedApple Distribution: Appveyor Systems Inc. (GXXRQJK434)with a secure timestampVersion tables regenerated with
gen_version_tables --release-date 20260729, and the no-arg re-run is a no-op (the CI in-sync check).Verified end to end
Against the real signed artifacts,
SERIOUS_PYTHON_VERIFY_PROVIDER_SIGNATURES=requireand the real team id:The decisive check — the staged
_ssl.xcframeworkinside the Swift package, after download, extract, sync and staging:That timestamp is python-build's signing job, not anything local — the signature reached the Swift package untouched.
Also confirmed the negative cases: a deliberate
plutil -replaceon a provider plist fails the build with the intended message,requiremode exits 1 against pre-signing artifacts, and locally built frameworks are still app-scoped (com.example.testapp.foo) and correctly excluded from provider checks.Not done here
The final acceptance criterion — a clean release IPA whose
Signatures/receipts readsigned = true/isSecureTimestamp = true, uploaded to TestFlight with no ITMS-91065 — needs this released and flet's pin updated. Build from uncached dependencies: existing caches hold the unsigned 20260727 / 1.6.1 artifacts.