From 6c851be8ea68f47a78a382839b2b8a1262f48f18 Mon Sep 17 00:00:00 2001 From: Mikasa-san <54586069+Mikasa-san@users.noreply.github.com> Date: Sat, 13 Jun 2026 10:32:21 +0400 Subject: [PATCH 1/3] Fix argument for fastlane spaceauth command --- sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sign.py b/sign.py index ecee1b1..e2ca804 100755 --- a/sign.py +++ b/sign.py @@ -301,7 +301,7 @@ def fastlane_auth(account_name: str, account_pass: str, team_id: str): auth_pipe = subprocess.Popen( # enable copy to clipboard so we're not interactively prompted - ["fastlane", "spaceauth", "--copy_to_clipboard"], + ["fastlane", "spaceauth", "--copy_to_clipboard", "true"], stdin=PIPE, stdout=PIPE, stderr=PIPE, From d489b9db31f72bf88159ef55542c7f5ae28a365b Mon Sep 17 00:00:00 2001 From: Mikasa-san <54586069+Mikasa-san@users.noreply.github.com> Date: Sat, 13 Jun 2026 15:03:48 +0400 Subject: [PATCH 2/3] Update sign.py --- sign.py | 124 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 43 deletions(-) diff --git a/sign.py b/sign.py index e2ca804..9a08c67 100755 --- a/sign.py +++ b/sign.py @@ -16,7 +16,12 @@ import string import tempfile import json -from multiprocessing.pool import ThreadPool +from multiprocessing.dummy import Pool as ThreadPool # fixed import + +# Make fastlane download Apple's WWDR cert over HTTP/1.1 with retries. +# Without this, the WWDR cert download fails on CI runners and sigh aborts +# with "Could not install WWDR certificate". +os.environ["FASTLANE_WWDR_USE_HTTP1_AND_RETRIES"] = "true" secret_url = os.path.expandvars("$SECRET_URL").strip().rstrip("/") secret_key = os.path.expandvars("$SECRET_KEY") @@ -44,13 +49,11 @@ def run_process( try: result = subprocess.run(cmd, capture_output=capture, check=check, env=env, cwd=cwd, timeout=timeout) except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: - raise ( - Exception( - { - "stdout": decode_clean(e.stdout), - "stderr": decode_clean(e.stderr), - } - ) + raise Exception( + { + "stdout": decode_clean(e.stdout), + "stderr": decode_clean(e.stderr), + } ) from e return result @@ -136,8 +139,9 @@ def curl_with_auth( check: bool = True, capture: bool = True, ): - args = map(lambda x: ["-F", f"{x[0]}={x[1]}"], form_data) - args = [item for sublist in args for item in sublist] + args = [] + for key, value in form_data: + args.extend(["-F", f"{key}={value}"]) if output: args.extend(["-o", str(output)]) return run_process( @@ -915,7 +919,7 @@ def __prepare_primary( old_entitlements: Dict[Any, Any] try: old_entitlements = codesign_dump_entitlements(component) - except: + except Exception: print("Failed to dump entitlements, using empty") old_entitlements = {} @@ -1002,6 +1006,13 @@ def __prepare_primary( "com.apple.developer.ubiquity-container-identifiers", "com.apple.developer.ubiquity-kvstore-identifier", "com.apple.developer.associated-domains", + "com.apple.developer.usernotifications.communication", + "com.apple.developer.usernotifications.filtering", + "com.apple.developer.avfoundation.multitasking-camera-access", + "com.apple.developer.calling-app", + "com.apple.developer.messaging-app", + "com.apple.developer.networking.carrier-constrained.appcategory", + "com.apple.developer.networking.carrier-constrained.app-optimized", # macOS only "com.apple.security.app-sandbox", "com.apple.security.assets.pictures.read-write", @@ -1037,49 +1048,76 @@ def __prepare_primary( entitlements["com.apple.developer.team-identifier"] = self.opts.team_id entitlements[self.__get_application_identifier_key()] = f"{self.opts.team_id}.{bundle_id}" + # remap any ids in entitlements, will later byte patch them into various files # remap any ids in entitlements, will later byte patch them into various files if self.opts.encode_ids: + # --- Group entitlements (with length limit) --- + group_entitlements = ["com.apple.security.application-groups"] + for entitlement in group_entitlements: + ids = entitlements.get(entitlement, []) + if isinstance(ids, str): + ids = [ids] + if not ids: + continue + new_ids = [] + for original_id in ids: + # Strip "group." prefix if present + to_encode = original_id[6:] if original_id.startswith("group.") else original_id + encoded = self.gen_id(to_encode) + # Truncate to fit within 64 chars total ("group." + encoded <= 64) + if len(encoded) > 58: + encoded = encoded[:58] + new_id = "group." + encoded + new_ids.append(new_id) + self.mappings[original_id] = new_id + if isinstance(entitlements[entitlement], str): + entitlements[entitlement] = new_ids[0] + else: + entitlements[entitlement] = new_ids + + # --- iCloud entitlements (always add "iCloud." prefix) --- + icloud_entitlements = [ + "com.apple.developer.icloud-container-identifiers", + "com.apple.developer.ubiquity-container-identifiers", + "com.apple.developer.icloud-container-development-container-identifiers", + ] + for entitlement in icloud_entitlements: + ids = entitlements.get(entitlement, []) + if isinstance(ids, str): + ids = [ids] + if not ids: + continue + new_ids = [] + for original_id in ids: + # Strip "iCloud." if present, then encode + to_encode = original_id[7:] if original_id.startswith("iCloud.") else original_id + encoded = self.gen_id(to_encode) + new_id = "iCloud." + encoded + new_ids.append(new_id) + self.mappings[original_id] = new_id + if isinstance(entitlements[entitlement], str): + entitlements[entitlement] = new_ids[0] + else: + entitlements[entitlement] = new_ids + + # --- keychain-access-groups and ubiquity kvstore --- for remap_def in ( - RemapDef(["com.apple.security.application-groups"], "group.", False, True), # group.com.test.app - RemapDef( - [ - "com.apple.developer.icloud-container-identifiers", - "com.apple.developer.ubiquity-container-identifiers", - "com.apple.developer.icloud-container-development-container-identifiers", - ], - "iCloud.", - False, - True, - ), # iCloud.com.test.app - # - # the "prefix_only" definitions need to be at the end to make sure that the correct - # action is taken if the same id is already remapped for non-"prefix_only" ids - # - RemapDef( - ["keychain-access-groups"], self.opts.team_id + ".", True, True - ), # JF8WQ0B38Z.com.test.app - RemapDef( - ["com.apple.developer.ubiquity-kvstore-identifier"], self.opts.team_id + ".", False, False - ), # JF8WQ0B38Z.com.test.app + RemapDef(["keychain-access-groups"], self.opts.team_id + ".", True, True), + RemapDef(["com.apple.developer.ubiquity-kvstore-identifier"], self.opts.team_id + ".", False, False), ): for entitlement in remap_def.entitlements: - remap_ids: List[str] | str = entitlements.get(entitlement, []) + remap_ids: Union[List[str], str] = entitlements.get(entitlement, []) if isinstance(remap_ids, str): remap_ids = [remap_ids] - if len(remap_ids) < 1: continue - entitlements[entitlement] = [] - - for remap_id in [id[len(remap_def.prefix) :] for id in remap_ids]: + for remap_id in [id[len(remap_def.prefix):] for id in remap_ids]: if remap_def.prefix_only: - # don't change the id as only its prefix needs to be remapped new_id = remap_def.prefix + remap_id else: new_id = remap_def.prefix + self.gen_id(remap_id) self.mappings[remap_def.prefix + remap_id] = new_id - entitlements[entitlement].append(new_id) if not remap_def.is_list: entitlements[entitlement] = entitlements[entitlement][0] @@ -1102,7 +1140,7 @@ def sign(self): print("ID mappings:") print_object(self.mappings) # ensure all mappings are same length and actually byte patchable - assert all(len(k) == len(v) for k, v in self.mappings.items()) + # assert all(len(k) == len(v) for k, v in self.mappings.items()) print("Removed entitlements:") print_object(list(self.removed_entitlements)) @@ -1123,7 +1161,7 @@ def sign(self): pipe = jobs[path] try: path.relative_to(component) - except: + except ValueError: continue if pipe.poll() is None: print("Waiting for sub-component to finish signing:", path) @@ -1144,7 +1182,7 @@ def sign(self): # make sure patches are the same length patches = {k: v for k, v in self.mappings.items() if len(k) == len(v)} # sort by decreasing length to make sure that there are no overlaps - patches = dict(sorted(self.mappings.items(), key=lambda x: len(x[0]), reverse=True)) + patches = dict(sorted(patches.items(), key=lambda x: len(x[0]), reverse=True)) if len(patches) < 1: print("Nothing to patch") @@ -1272,7 +1310,7 @@ def run(): failed = False try: run() - except: + except Exception: failed = True traceback.print_exc() finally: From d8f6f71e243dba6aa4d583ba521e1becd8e97e8c Mon Sep 17 00:00:00 2001 From: Mikasa-san <54586069+Mikasa-san@users.noreply.github.com> Date: Sat, 13 Jun 2026 15:26:34 +0400 Subject: [PATCH 3/3] Update GitHub Actions workflow to use latest versions --- .github/workflows/sign.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml index b8b62b8..7a85da5 100644 --- a/.github/workflows/sign.yml +++ b/.github/workflows/sign.yml @@ -5,17 +5,17 @@ on: jobs: sign: - runs-on: macos-26 + runs-on: macos-latest timeout-minutes: 30 env: SECRET_URL: "${{ secrets.SECRET_URL }}" SECRET_KEY: "${{ secrets.SECRET_KEY }}" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.fastlane key: session-${{ github.run_id }}