Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Packages/Shared/CmuxIrohTransport/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Packages/Shared/CmuxIrohTransport/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
.package(path: "../CMUXMobileCore"),
.package(
url: "https://github.com/manaflow-ai/iroh-ffi.git",
exact: "1.0.2-cmux.1"
exact: "1.0.2-cmux.2"
),
],
targets: [
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ios/cmuxPackage/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions ios/scripts/upload-testflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,58 @@ verify_ipa_bundle_identity() {
return 0
}

# App Store Connect rejects embedded iOS frameworks whose bundle metadata omits
# MinimumOSVersion (90530/90360). Validate the final IPA, after export and any
# re-sign, so a malformed binary Swift package fails here with its framework
# name instead of after a slow upload.
verify_ipa_framework_minimum_os_versions() {
local ipa="$1"
local workdir app framework plist minimum framework_name major

workdir="$(mktemp -d)"
if ! ( cd "$workdir" && unzip -q "$ipa" ); then
echo "error: could not unzip IPA to verify framework metadata: $ipa" >&2
rm -rf "$workdir"
return 1
fi
app="$(find "$workdir/Payload" -maxdepth 1 -name '*.app' -type d 2>/dev/null | head -n 1)"
if [[ -z "$app" || ! -d "$app" ]]; then
echo "error: IPA has no Payload/*.app to verify framework metadata: $ipa" >&2
rm -rf "$workdir"
return 1
fi

while IFS= read -r -d '' framework; do
framework_name="$(basename "$framework")"
plist="$framework/Info.plist"
if [[ ! -f "$plist" ]]; then
echo "error: $framework_name is missing Info.plist" >&2
rm -rf "$workdir"
return 1
fi
minimum="$("$PLISTBUDDY" -c 'Print :MinimumOSVersion' "$plist" 2>/dev/null || true)"
if [[ -z "$minimum" ]]; then
echo "error: $framework_name is missing MinimumOSVersion" >&2
rm -rf "$workdir"
return 1
fi
if [[ ! "$minimum" =~ ^[0-9]+([.][0-9]+){0,2}$ ]]; then
echo "error: $framework_name has invalid MinimumOSVersion '$minimum'" >&2
rm -rf "$workdir"
return 1
fi
major="${minimum%%.*}"
if (( major < 8 )); then
echo "error: $framework_name MinimumOSVersion '$minimum' must be 8.0 or later" >&2
rm -rf "$workdir"
return 1
fi
done < <(find "$app" -type d -name '*.framework' -print0)

rm -rf "$workdir"
return 0
}

verify_app_store_ipa_has_no_external_purchase_links() {
local ipa="$1"
local workdir app matches
Expand Down Expand Up @@ -1048,6 +1100,12 @@ else
echo "automatic-signed IPA verified to carry aps-environment=production: $IPA_PATH"
fi

if ! verify_ipa_framework_minimum_os_versions "$IPA_PATH"; then
echo "error: signed IPA contains invalid framework deployment metadata; refusing to upload" >&2
exit 1
fi
echo "signed IPA framework deployment metadata verified"

echo "IPA_PATH=$IPA_PATH"

EXPECTED_IPA_CRASH_REPORTING=""
Expand Down
38 changes: 38 additions & 0 deletions tests/test_ios_appstore_lane_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ def setting(prefix):
payload_root = export_path / "Payload"
app = payload_root / "cmux.app"
write_plist(app / "Info.plist", archived_info)
if os.environ.get("CMUX_FAKE_EMBED_FRAMEWORK_WITHOUT_MINIMUM_OS") == "1":
write_plist(
app / "Frameworks" / "Iroh.framework" / "Info.plist",
{{
"CFBundleIdentifier": "computer.iroh.Iroh",
"CFBundlePackageType": "FMWK",
}},
)
profile_marker = "beta profile" if bundle_id == BETA_BUNDLE_ID else "fake profile"
(app / "embedded.mobileprovision").write_text(profile_marker, encoding="utf-8")
ipa = export_path / "cmux.ipa"
Expand Down Expand Up @@ -630,6 +638,33 @@ def test_upload_beta_lane_uses_beta_marketing_version(tmp: Path, fakebin: Path)
)


def test_upload_rejects_framework_without_minimum_os_version(tmp: Path, fakebin: Path) -> None:
env = _base_env(tmp, fakebin)
env["CMUX_IOS_UPLOAD_DIR"] = str(tmp / "upload")
env["CMUX_FAKE_EMBED_FRAMEWORK_WITHOUT_MINIMUM_OS"] = "1"
result = _run(
[
"bash",
str(ROOT / "ios" / "scripts" / "upload-testflight.sh"),
"--lane",
"beta",
"--signing",
"manual",
"--export-only",
"--build-number",
"20260710041753",
],
env=env,
tmp=tmp,
log_failure=False,
)
_check(result.returncode != 0, "upload rejects a framework without MinimumOSVersion")
_check(
"Iroh.framework is missing MinimumOSVersion" in result.stderr,
"framework metadata failure names the malformed framework",
)


def test_upload_beta_archive_path_accepts_marketing_version_override(tmp: Path, fakebin: Path) -> None:
override_version = "1.0.3"
archive = tmp / "cmux-beta.xcarchive"
Expand Down Expand Up @@ -1266,6 +1301,9 @@ def main() -> None:
fakebin = tmp / "bin"
_install_fake_tools(fakebin)
test_upload_beta_lane_uses_beta_marketing_version(tmp / "beta-upload-test", fakebin)
test_upload_rejects_framework_without_minimum_os_version(
tmp / "beta-framework-metadata-test", fakebin
)
test_upload_beta_archive_path_accepts_marketing_version_override(tmp / "beta-archive-override-test", fakebin)
test_upload_beta_auto_version_uses_checked_in_beta_floor(tmp / "beta-auto-version-test", fakebin)
test_bump_ios_version_accepts_trailing_appstore_lane(tmp / "version-bump-test", fakebin)
Expand Down
Loading