diff --git a/.github/workflows/_ci-clients.yml b/.github/workflows/_ci-clients.yml index 465c4b93ef..e56b31dbd1 100644 --- a/.github/workflows/_ci-clients.yml +++ b/.github/workflows/_ci-clients.yml @@ -142,6 +142,8 @@ jobs: run: swift test --package-path mobile/ios/BuzzPushKit - name: Test native pagination geometry run: /bin/bash scripts/test-mobile-pagination.sh + - name: Trust pinned Swift source-copy plugin + run: python3 scripts/trust-ios-package-plugin.py --trust-file "$HOME/Library/org.swift.swiftpm/security/plugins.json" - name: Build complete iOS simulator app run: cd mobile && flutter build ios --simulator --debug --no-pub - name: Test native pagination accessibility diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f134626afb..1126c7e863 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -617,3 +617,8 @@ their sign-off. When in doubt, check with your legal team. *Thank you for contributing to Buzz. Every bug report, documentation fix, and code contribution makes the project better for everyone. 🐝* + +The iOS Swift dependency uses `SharedSourcesPlugin` to copy its shared Swift sources. +Xcode users must approve this plugin when prompted. CI trusts only its reviewed +commit fingerprint using `scripts/trust-ios-package-plugin.py`; package upgrades +require reviewing the plugin before updating that fingerprint. diff --git a/scripts/trust-ios-package-plugin.py b/scripts/trust-ios-package-plugin.py new file mode 100644 index 0000000000..843af8c971 --- /dev/null +++ b/scripts/trust-ios-package-plugin.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Trust only Buzz's reviewed, pinned Swift build plugin on a build worker.""" +import argparse +import json +from pathlib import Path + + +def trust_plugin(destination, root): + """Preserve other trust entries and reject an unreviewed package revision.""" + entry = { + "fingerprint": "e70a10e036a55fffea31568f0af92d69b6d449cd", + "packageIdentity": "swift-secp256k1", + "targetName": "SharedSourcesPlugin", + } + lock = json.loads((root / "mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved").read_text()) + pin = next(pin for pin in lock["pins"] if pin["identity"] == entry["packageIdentity"]) + if pin["state"]["revision"] != entry["fingerprint"]: + raise ValueError("Review the changed Swift plugin before updating its trusted fingerprint") + entries = json.loads(destination.read_text()) if destination.exists() else [] + if not isinstance(entries, list): + raise ValueError("Expected a Swift plugin trust list") + if entry not in entries: + entries.append(entry) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(json.dumps(entries, indent=2) + "\n") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--trust-file", type=Path, required=True) + args = parser.parse_args() + trust_plugin(args.trust_file, Path(__file__).resolve().parent.parent)