Skip to content

Bump to 4.5.0: preserve provider XCFramework signatures through staging - #244

Merged
FeodorFitsner merged 1 commit into
mainfrom
preserve-provider-xcframework-signatures
Jul 29, 2026
Merged

Bump to 4.5.0: preserve provider XCFramework signatures through staging#244
FeodorFitsner merged 1 commit into
mainfrom
preserve-provider-xcframework-signatures

Conversation

@FeodorFitsner

Copy link
Copy Markdown
Contributor

Third and last of the three-repo effort. dart-bridge#13v1.7.0 and python-build#3720260729 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 .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. And the fix was counterproductive — rewriting an Info.plist inside 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_ids calls on provider directories are gone, and the helper's doc comment now states that provider bundles are off limits and why.

SERIOUS_PYTHON_BUNDLE_ID still works, and still does the same thing — for frameworks built from your wheels. Provider frameworks arrive with stable dev.flet.python.* / dev.flet.dartbridge identifiers assigned upstream, so there is nothing left for it to fix.

Two independent guards — xcframework_verify.sh

Digest 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, and TeamIdentifier when SERIOUS_PYTHON_EXPECTED_TEAM_ID is set. Gated by SERIOUS_PYTHON_VERIFY_PROVIDER_SIGNATURES:

value behaviour
warn (default) report and continue
require fail the build — use for App Store submissions
off skip

The default is warn deliberately: a consumer can pin a pre-signing python-build or dart-bridge release through SERIOUS_PYTHON_BUILD_DATE / DART_BRIDGE_VERSION, and that must keep building.

prepare_macos.sh's Python.app removal now runs before the snapshot rather than after. python-build has excluded Python.app upstream 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 .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.

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:

  • Android .sos and libdart_bridge-linux-*.so: byte-identical to 1.6.1
  • dart_bridge.dll: rebuilt, because MSVC stamps a build timestamp and PDB GUID into every DLL; no Windows source changed
  • Apple: every XCFramework now signed by Apple Distribution: Appveyor Systems Inc. (GXXRQJK434) with a secure timestamp

Version 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=require and the real team id:

prepare_ios    → provider signatures verified on 57 xcframework(s)
sync           → provider artifacts unchanged (xcframeworks, python-xcframeworks,
                 site-xcframeworks provider subset)
stage_spm ios  → provider artifacts unchanged (staged Python-ios, dart_bridge,
                 extra-xcframeworks provider subset)
prepare_macos  → verified;  stage_spm macos → unchanged

The decisive check — the staged _ssl.xcframework inside the Swift package, after download, extract, sync and staging:

Identifier=dev.flet.python.-ssl
Authority=Apple Distribution: Appveyor Systems Inc. (GXXRQJK434)
Timestamp=Jul 29, 2026 at 1:01:58 PM
TeamIdentifier=GXXRQJK434

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 -replace on a provider plist fails the build with the intended message, require mode 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 read signed = 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.

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.
@FeodorFitsner
FeodorFitsner merged commit ffb16ff into main Jul 29, 2026
30 of 58 checks passed
@FeodorFitsner
FeodorFitsner deleted the preserve-provider-xcframework-signatures branch July 29, 2026 23:11
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.

1 participant