From c971fcd2de97cda9cd745c8ae03a75fc0ab2aa81 Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Mon, 18 May 2026 09:56:29 -0700 Subject: [PATCH 1/7] Add macOS code signing and notarization via ESRP Wire up EsrpCodeSigning@5 in Mac.Build.Job.yml to sign the mxc-exec-mac binary with Microsoft's Developer ID Application certificate (CP-401337-Apple) and submit it to Apple's notary service during official builds. Changes: - Add isOfficialBuild and ESRPInfo parameters to Mac.Build.Job.yml - Add MacAppDeveloperSign step with hardened runtime (--options=runtime) - Add MacAppNotarize step with bundle ID com.microsoft.mxc - Update 1ES.Build.yml to pass ESRPInfo and isOfficialBuild to the Mac template (mirroring the Rust.Build.Job.yml pattern) Both steps are conditioned on isOfficialBuild=true so PR validation builds continue to skip signing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/1ES.Build.yml | 8 ++- .azure-pipelines/templates/Mac.Build.Job.yml | 65 ++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/1ES.Build.yml b/.azure-pipelines/1ES.Build.yml index 3aa2e3fc7..660e9bb88 100644 --- a/.azure-pipelines/1ES.Build.yml +++ b/.azure-pipelines/1ES.Build.yml @@ -87,10 +87,12 @@ extends: ESRPInfo: ${{ parameters.ESRPInfo }} # macOS uses a separate template because it runs on a different - # pool (Microsoft-hosted) and skips ESRP signing / cross-compile - # plumbing. The produced artifact name follows the same scheme - # (mxc-binaries-) so Package_MXC_NPM_SDK can consume it. + # pool (Microsoft-hosted). The produced artifact name follows the same + # scheme (mxc-binaries-) so Package_MXC_NPM_SDK can consume it. - template: .azure-pipelines/templates/Mac.Build.Job.yml@self + parameters: + isOfficialBuild: ${{ eq(parameters.officialBuild, 'Official') }} + ESRPInfo: ${{ parameters.ESRPInfo }} - stage: Package_MXC displayName: 'Package MXC' diff --git a/.azure-pipelines/templates/Mac.Build.Job.yml b/.azure-pipelines/templates/Mac.Build.Job.yml index fbb61ca11..0202b8a8a 100644 --- a/.azure-pipelines/templates/Mac.Build.Job.yml +++ b/.azure-pipelines/templates/Mac.Build.Job.yml @@ -15,6 +15,12 @@ parameters: - name: triplet type: string default: aarch64-apple-darwin +- name: isOfficialBuild + type: boolean + default: false +- name: ESRPInfo + type: object + default: {} jobs: - job: 'build_arm64_MAC' @@ -82,6 +88,65 @@ jobs: displayName: Run tests workingDirectory: $(workingDirectory) + # --- macOS Code Signing & Notarization (official builds only) --- + # Uses ESRP with Microsoft's Developer ID Application certificate + # (CP-401337-Apple) to codesign with hardened runtime, then submits + # to Apple's notary service. This mirrors the Windows signing pattern + # in Rust.Build.Job.yml but uses Apple-specific operations. + - task: EsrpCodeSigning@5 + displayName: 'Code Sign (macOS)' + condition: eq(${{ parameters.isOfficialBuild }}, true) + inputs: + ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} + AppRegistrationClientId: ${{ parameters.ESRPInfo.clientId }} + AppRegistrationTenantId: ${{ parameters.ESRPInfo.tenantId }} + AuthAKVName: ${{ parameters.ESRPInfo.azureKeyVaultName }} + AuthCertName: ${{ parameters.ESRPInfo.authCertName }} + AuthSignCertName: ${{ parameters.ESRPInfo.signCertName }} + FolderPath: $(targetTripleDir) + Pattern: mxc-exec-mac + UseMinimatch: true + signConfigType: 'inlineSignParams' + inlineOperation: >- + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppDeveloperSign", + "Parameters": { + "Hardening": "--options=runtime" + }, + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + + - task: EsrpCodeSigning@5 + displayName: 'Notarize (macOS)' + condition: eq(${{ parameters.isOfficialBuild }}, true) + inputs: + ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} + AppRegistrationClientId: ${{ parameters.ESRPInfo.clientId }} + AppRegistrationTenantId: ${{ parameters.ESRPInfo.tenantId }} + AuthAKVName: ${{ parameters.ESRPInfo.azureKeyVaultName }} + AuthCertName: ${{ parameters.ESRPInfo.authCertName }} + AuthSignCertName: ${{ parameters.ESRPInfo.signCertName }} + FolderPath: $(targetTripleDir) + Pattern: mxc-exec-mac + UseMinimatch: true + signConfigType: 'inlineSignParams' + inlineOperation: >- + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppNotarize", + "Parameters": { + "BundleId": "com.microsoft.mxc" + }, + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + - task: CopyFiles@2 displayName: Copy binary inputs: From 4e9adf4220c06df1161760504e267b30ceaede2f Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Mon, 18 May 2026 10:13:36 -0700 Subject: [PATCH 2/7] Address PR feedback: add succeeded() guard and stapling comment - Gate signing and notarization steps with and(succeeded(), ...) to prevent running on failed builds - Add YAML comment explaining why stapling is intentionally omitted (Apple does not support stapling standalone Mach-O binaries) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/templates/Mac.Build.Job.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/templates/Mac.Build.Job.yml b/.azure-pipelines/templates/Mac.Build.Job.yml index 0202b8a8a..5417b63cd 100644 --- a/.azure-pipelines/templates/Mac.Build.Job.yml +++ b/.azure-pipelines/templates/Mac.Build.Job.yml @@ -93,9 +93,14 @@ jobs: # (CP-401337-Apple) to codesign with hardened runtime, then submits # to Apple's notary service. This mirrors the Windows signing pattern # in Rust.Build.Job.yml but uses Apple-specific operations. + # + # Note: Stapling is intentionally omitted. Apple does not support + # stapling tickets to standalone Mach-O binaries — only .app bundles, + # .pkg installers, and .dmg disk images. Gatekeeper verifies the + # notarization ticket online for standalone binaries instead. - task: EsrpCodeSigning@5 displayName: 'Code Sign (macOS)' - condition: eq(${{ parameters.isOfficialBuild }}, true) + condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) inputs: ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} AppRegistrationClientId: ${{ parameters.ESRPInfo.clientId }} @@ -122,7 +127,7 @@ jobs: - task: EsrpCodeSigning@5 displayName: 'Notarize (macOS)' - condition: eq(${{ parameters.isOfficialBuild }}, true) + condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) inputs: ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} AppRegistrationClientId: ${{ parameters.ESRPInfo.clientId }} From 97fd4bc9b0c931948915d155c17a4c76c3c9bb91 Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Mon, 18 May 2026 10:45:56 -0700 Subject: [PATCH 3/7] Add full macOS signing flow: local codesign + ESRP harden + notarize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the pipeline to implement the complete Apple signing workflow per PRSS guidelines: 1. Fetch Developer ID cert from Azure Key Vault 2. Import into temporary keychain on macOS agent 3. Codesign binary with Developer ID + entitlements 4. ESRP MacAppDeveloperSign (hardened runtime) 5. ESRP MacAppNotarize (Apple notary service) Additional changes: - Add AppleSigningInfo parameter (Key Vault connection, cert secrets, signing identity) - Add mac-entitlements.plist (empty — no special entitlements needed for a CLI that spawns sandboxed children via sandbox_init) - Pass AppleSigningInfo from 1ES.Build.yml to Mac template Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/1ES.Build.yml | 11 ++++ .azure-pipelines/mac-entitlements.plist | 8 +++ .azure-pipelines/templates/Mac.Build.Job.yml | 65 ++++++++++++++++++-- 3 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 .azure-pipelines/mac-entitlements.plist diff --git a/.azure-pipelines/1ES.Build.yml b/.azure-pipelines/1ES.Build.yml index 660e9bb88..b717e9e5e 100644 --- a/.azure-pipelines/1ES.Build.yml +++ b/.azure-pipelines/1ES.Build.yml @@ -41,6 +41,16 @@ parameters: type: boolean default: false + - name: AppleSigningInfo + displayName: Apple Developer ID signing configuration + type: object + default: + keyVaultServiceConnection: '' + keyVaultName: '' + certSecret: 'AppleDeveloperIdCert' + certPasswordSecret: 'AppleDeveloperIdCertPassword' + signingIdentity: 'Developer ID Application: Microsoft Corporation' + variables: # Prevents failure when no Rust tests are found; pipeline runs will still fail if present # tests do not pass. @@ -93,6 +103,7 @@ extends: parameters: isOfficialBuild: ${{ eq(parameters.officialBuild, 'Official') }} ESRPInfo: ${{ parameters.ESRPInfo }} + AppleSigningInfo: ${{ parameters.AppleSigningInfo }} - stage: Package_MXC displayName: 'Package MXC' diff --git a/.azure-pipelines/mac-entitlements.plist b/.azure-pipelines/mac-entitlements.plist new file mode 100644 index 000000000..ac4027b8b --- /dev/null +++ b/.azure-pipelines/mac-entitlements.plist @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.azure-pipelines/templates/Mac.Build.Job.yml b/.azure-pipelines/templates/Mac.Build.Job.yml index 5417b63cd..30bcf16fd 100644 --- a/.azure-pipelines/templates/Mac.Build.Job.yml +++ b/.azure-pipelines/templates/Mac.Build.Job.yml @@ -21,6 +21,14 @@ parameters: - name: ESRPInfo type: object default: {} +- name: AppleSigningInfo + type: object + default: + keyVaultServiceConnection: '' + keyVaultName: '' + certSecret: 'AppleDeveloperIdCert' + certPasswordSecret: 'AppleDeveloperIdCertPassword' + signingIdentity: 'Developer ID Application: Microsoft Corporation' jobs: - job: 'build_arm64_MAC' @@ -89,17 +97,61 @@ jobs: workingDirectory: $(workingDirectory) # --- macOS Code Signing & Notarization (official builds only) --- - # Uses ESRP with Microsoft's Developer ID Application certificate - # (CP-401337-Apple) to codesign with hardened runtime, then submits - # to Apple's notary service. This mirrors the Windows signing pattern - # in Rust.Build.Job.yml but uses Apple-specific operations. + # Flow: Import cert from Key Vault → codesign with Developer ID → + # ESRP hardening (runtime flag) → ESRP notarization (Apple notary service). # # Note: Stapling is intentionally omitted. Apple does not support # stapling tickets to standalone Mach-O binaries — only .app bundles, # .pkg installers, and .dmg disk images. Gatekeeper verifies the # notarization ticket online for standalone binaries instead. + + # Step 1: Fetch the Developer ID Application certificate from Key Vault + - task: AzureKeyVault@2 + displayName: 'Fetch Apple signing cert from Key Vault' + condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) + inputs: + azureSubscription: ${{ parameters.AppleSigningInfo.keyVaultServiceConnection }} + KeyVaultName: ${{ parameters.AppleSigningInfo.keyVaultName }} + SecretsFilter: '${{ parameters.AppleSigningInfo.certSecret }},${{ parameters.AppleSigningInfo.certPasswordSecret }}' + RunAsPreJob: false + + # Step 2: Import cert into a temporary keychain and codesign the binary + - script: | + set -euo pipefail + KEYCHAIN_PATH="$(Agent.TempDirectory)/build.keychain" + KEYCHAIN_PASSWORD="$(Agent.TempDirectory)" + + # Create and configure temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security default-keychain -s "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -t 3600 "$KEYCHAIN_PATH" + + # Import the Developer ID certificate + echo "$(${{ parameters.AppleSigningInfo.certSecret }})" | base64 -d > "$(Agent.TempDirectory)/cert.p12" + security import "$(Agent.TempDirectory)/cert.p12" \ + -k "$KEYCHAIN_PATH" \ + -P "$(${{ parameters.AppleSigningInfo.certPasswordSecret }})" \ + -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + # Codesign the binary with Developer ID and entitlements + codesign --sign "${{ parameters.AppleSigningInfo.signingIdentity }}" \ + --entitlements "$(Build.SourcesDirectory)/.azure-pipelines/mac-entitlements.plist" \ + --timestamp --force \ + "$(targetTripleDir)/mxc-exec-mac" + + # Verify the signature + codesign --verify --verbose=2 "$(targetTripleDir)/mxc-exec-mac" + + # Cleanup cert file + rm -f "$(Agent.TempDirectory)/cert.p12" + displayName: 'Codesign binary (Developer ID)' + condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) + + # Step 3: ESRP hardening (adds hardened runtime flag) - task: EsrpCodeSigning@5 - displayName: 'Code Sign (macOS)' + displayName: 'Harden runtime (ESRP)' condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) inputs: ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} @@ -125,8 +177,9 @@ jobs: } ] + # Step 4: ESRP notarization (submits to Apple notary service) - task: EsrpCodeSigning@5 - displayName: 'Notarize (macOS)' + displayName: 'Notarize (ESRP)' condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) inputs: ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} From 770882a176715a21669b2a269be6dbfa19863496 Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Tue, 19 May 2026 09:19:28 -0700 Subject: [PATCH 4/7] Simplify macOS signing: single ESRP step, no local cert needed Based on research into Universal Print, Edge, and Teams pipelines, ESRP handles the full signing flow via CP-401337-Apple (MacAppDeveloperSign) without needing a local Developer ID cert. Removes: - Key Vault cert fetch step - Local codesign script - Notarization step (not needed for npm distribution) - mac-entitlements.plist (ESRP applies hardened runtime directly) - AppleSigningInfo parameter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/1ES.Build.yml | 11 --- .azure-pipelines/mac-entitlements.plist | 8 -- .azure-pipelines/templates/Mac.Build.Job.yml | 97 ++------------------ 3 files changed, 7 insertions(+), 109 deletions(-) delete mode 100644 .azure-pipelines/mac-entitlements.plist diff --git a/.azure-pipelines/1ES.Build.yml b/.azure-pipelines/1ES.Build.yml index b717e9e5e..660e9bb88 100644 --- a/.azure-pipelines/1ES.Build.yml +++ b/.azure-pipelines/1ES.Build.yml @@ -41,16 +41,6 @@ parameters: type: boolean default: false - - name: AppleSigningInfo - displayName: Apple Developer ID signing configuration - type: object - default: - keyVaultServiceConnection: '' - keyVaultName: '' - certSecret: 'AppleDeveloperIdCert' - certPasswordSecret: 'AppleDeveloperIdCertPassword' - signingIdentity: 'Developer ID Application: Microsoft Corporation' - variables: # Prevents failure when no Rust tests are found; pipeline runs will still fail if present # tests do not pass. @@ -103,7 +93,6 @@ extends: parameters: isOfficialBuild: ${{ eq(parameters.officialBuild, 'Official') }} ESRPInfo: ${{ parameters.ESRPInfo }} - AppleSigningInfo: ${{ parameters.AppleSigningInfo }} - stage: Package_MXC displayName: 'Package MXC' diff --git a/.azure-pipelines/mac-entitlements.plist b/.azure-pipelines/mac-entitlements.plist deleted file mode 100644 index ac4027b8b..000000000 --- a/.azure-pipelines/mac-entitlements.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/.azure-pipelines/templates/Mac.Build.Job.yml b/.azure-pipelines/templates/Mac.Build.Job.yml index 30bcf16fd..7c3820434 100644 --- a/.azure-pipelines/templates/Mac.Build.Job.yml +++ b/.azure-pipelines/templates/Mac.Build.Job.yml @@ -21,14 +21,6 @@ parameters: - name: ESRPInfo type: object default: {} -- name: AppleSigningInfo - type: object - default: - keyVaultServiceConnection: '' - keyVaultName: '' - certSecret: 'AppleDeveloperIdCert' - certPasswordSecret: 'AppleDeveloperIdCertPassword' - signingIdentity: 'Developer ID Application: Microsoft Corporation' jobs: - job: 'build_arm64_MAC' @@ -96,62 +88,15 @@ jobs: displayName: Run tests workingDirectory: $(workingDirectory) - # --- macOS Code Signing & Notarization (official builds only) --- - # Flow: Import cert from Key Vault → codesign with Developer ID → - # ESRP hardening (runtime flag) → ESRP notarization (Apple notary service). + # --- macOS Code Signing (official builds only) --- + # ESRP handles the full signing flow using Microsoft's Developer ID + # certificate (CP-401337-Apple). No local cert or codesign needed. # - # Note: Stapling is intentionally omitted. Apple does not support - # stapling tickets to standalone Mach-O binaries — only .app bundles, - # .pkg installers, and .dmg disk images. Gatekeeper verifies the - # notarization ticket online for standalone binaries instead. - - # Step 1: Fetch the Developer ID Application certificate from Key Vault - - task: AzureKeyVault@2 - displayName: 'Fetch Apple signing cert from Key Vault' - condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) - inputs: - azureSubscription: ${{ parameters.AppleSigningInfo.keyVaultServiceConnection }} - KeyVaultName: ${{ parameters.AppleSigningInfo.keyVaultName }} - SecretsFilter: '${{ parameters.AppleSigningInfo.certSecret }},${{ parameters.AppleSigningInfo.certPasswordSecret }}' - RunAsPreJob: false - - # Step 2: Import cert into a temporary keychain and codesign the binary - - script: | - set -euo pipefail - KEYCHAIN_PATH="$(Agent.TempDirectory)/build.keychain" - KEYCHAIN_PASSWORD="$(Agent.TempDirectory)" - - # Create and configure temporary keychain - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security default-keychain -s "$KEYCHAIN_PATH" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security set-keychain-settings -t 3600 "$KEYCHAIN_PATH" - - # Import the Developer ID certificate - echo "$(${{ parameters.AppleSigningInfo.certSecret }})" | base64 -d > "$(Agent.TempDirectory)/cert.p12" - security import "$(Agent.TempDirectory)/cert.p12" \ - -k "$KEYCHAIN_PATH" \ - -P "$(${{ parameters.AppleSigningInfo.certPasswordSecret }})" \ - -T /usr/bin/codesign - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - - # Codesign the binary with Developer ID and entitlements - codesign --sign "${{ parameters.AppleSigningInfo.signingIdentity }}" \ - --entitlements "$(Build.SourcesDirectory)/.azure-pipelines/mac-entitlements.plist" \ - --timestamp --force \ - "$(targetTripleDir)/mxc-exec-mac" - - # Verify the signature - codesign --verify --verbose=2 "$(targetTripleDir)/mxc-exec-mac" - - # Cleanup cert file - rm -f "$(Agent.TempDirectory)/cert.p12" - displayName: 'Codesign binary (Developer ID)' - condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) - - # Step 3: ESRP hardening (adds hardened runtime flag) + # Note: Notarization is omitted — the binary is distributed via npm + # (no quarantine xattr), so Gatekeeper never fires. Stapling is also + # impossible for standalone Mach-O binaries (Apple limitation). - task: EsrpCodeSigning@5 - displayName: 'Harden runtime (ESRP)' + displayName: 'Sign macOS binary (ESRP)' condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) inputs: ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} @@ -177,34 +122,6 @@ jobs: } ] - # Step 4: ESRP notarization (submits to Apple notary service) - - task: EsrpCodeSigning@5 - displayName: 'Notarize (ESRP)' - condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) - inputs: - ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} - AppRegistrationClientId: ${{ parameters.ESRPInfo.clientId }} - AppRegistrationTenantId: ${{ parameters.ESRPInfo.tenantId }} - AuthAKVName: ${{ parameters.ESRPInfo.azureKeyVaultName }} - AuthCertName: ${{ parameters.ESRPInfo.authCertName }} - AuthSignCertName: ${{ parameters.ESRPInfo.signCertName }} - FolderPath: $(targetTripleDir) - Pattern: mxc-exec-mac - UseMinimatch: true - signConfigType: 'inlineSignParams' - inlineOperation: >- - [ - { - "KeyCode": "CP-401337-Apple", - "OperationCode": "MacAppNotarize", - "Parameters": { - "BundleId": "com.microsoft.mxc" - }, - "ToolName": "sign", - "ToolVersion": "1.0" - } - ] - - task: CopyFiles@2 displayName: Copy binary inputs: From 566f2c6ae63809bacbd2b8b0720a7f663299d24f Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Tue, 19 May 2026 09:34:42 -0700 Subject: [PATCH 5/7] fix: zip binary before ESRP Mac signing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ESRP's MacAppDeveloperSign operation requires .zip or .dmg input — raw Mach-O binaries are rejected with 'Incorrect file type used for command -z'. Wrap the binary in a zip before signing and extract it afterward. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/templates/Mac.Build.Job.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.azure-pipelines/templates/Mac.Build.Job.yml b/.azure-pipelines/templates/Mac.Build.Job.yml index 7c3820434..6e6eb36df 100644 --- a/.azure-pipelines/templates/Mac.Build.Job.yml +++ b/.azure-pipelines/templates/Mac.Build.Job.yml @@ -92,9 +92,18 @@ jobs: # ESRP handles the full signing flow using Microsoft's Developer ID # certificate (CP-401337-Apple). No local cert or codesign needed. # + # ESRP's Mac signing requires a .zip (or .dmg) input — raw Mach-O + # binaries are rejected. We zip before signing and unzip afterward. + # # Note: Notarization is omitted — the binary is distributed via npm # (no quarantine xattr), so Gatekeeper never fires. Stapling is also # impossible for standalone Mach-O binaries (Apple limitation). + - script: | + cd "$(targetTripleDir)" + zip mxc-exec-mac.zip mxc-exec-mac + displayName: 'Zip binary for ESRP signing' + condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) + - task: EsrpCodeSigning@5 displayName: 'Sign macOS binary (ESRP)' condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) @@ -106,7 +115,7 @@ jobs: AuthCertName: ${{ parameters.ESRPInfo.authCertName }} AuthSignCertName: ${{ parameters.ESRPInfo.signCertName }} FolderPath: $(targetTripleDir) - Pattern: mxc-exec-mac + Pattern: mxc-exec-mac.zip UseMinimatch: true signConfigType: 'inlineSignParams' inlineOperation: >- @@ -122,6 +131,13 @@ jobs: } ] + - script: | + cd "$(targetTripleDir)" + unzip -o mxc-exec-mac.zip + rm mxc-exec-mac.zip + displayName: 'Unzip signed binary' + condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) + - task: CopyFiles@2 displayName: Copy binary inputs: From e67c817b4b94a0b54ec659d6db58e9bd0ddbec13 Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Tue, 19 May 2026 10:20:28 -0700 Subject: [PATCH 6/7] feat: add ESRP notarization step for macOS binary Add MacAppNotarize ESRP task after the existing MacAppDeveloperSign step, following the same pattern used by TypeSpec and Kiota. This submits the signed binary to Apple's notary service so Gatekeeper accepts it without the 'could not verify free of malware' warning. BundleId: com.microsoft.mxc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/templates/Mac.Build.Job.yml | 35 +++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/.azure-pipelines/templates/Mac.Build.Job.yml b/.azure-pipelines/templates/Mac.Build.Job.yml index 6e6eb36df..ddbd53bd7 100644 --- a/.azure-pipelines/templates/Mac.Build.Job.yml +++ b/.azure-pipelines/templates/Mac.Build.Job.yml @@ -88,16 +88,16 @@ jobs: displayName: Run tests workingDirectory: $(workingDirectory) - # --- macOS Code Signing (official builds only) --- + # --- macOS Code Signing + Notarization (official builds only) --- # ESRP handles the full signing flow using Microsoft's Developer ID # certificate (CP-401337-Apple). No local cert or codesign needed. # # ESRP's Mac signing requires a .zip (or .dmg) input — raw Mach-O # binaries are rejected. We zip before signing and unzip afterward. # - # Note: Notarization is omitted — the binary is distributed via npm - # (no quarantine xattr), so Gatekeeper never fires. Stapling is also - # impossible for standalone Mach-O binaries (Apple limitation). + # Flow: (1) zip → (2) ESRP sign (hardened runtime) → (3) ESRP notarize → (4) unzip + # Stapling is impossible for standalone Mach-O binaries (Apple limitation); + # Gatekeeper checks the notarization ticket online instead. - script: | cd "$(targetTripleDir)" zip mxc-exec-mac.zip mxc-exec-mac @@ -131,6 +131,33 @@ jobs: } ] + - task: EsrpCodeSigning@5 + displayName: 'Notarize macOS binary (ESRP)' + condition: and(succeeded(), eq(${{ parameters.isOfficialBuild }}, true)) + inputs: + ConnectedServiceName: ${{ parameters.ESRPInfo.serviceName }} + AppRegistrationClientId: ${{ parameters.ESRPInfo.clientId }} + AppRegistrationTenantId: ${{ parameters.ESRPInfo.tenantId }} + AuthAKVName: ${{ parameters.ESRPInfo.azureKeyVaultName }} + AuthCertName: ${{ parameters.ESRPInfo.authCertName }} + AuthSignCertName: ${{ parameters.ESRPInfo.signCertName }} + FolderPath: $(targetTripleDir) + Pattern: mxc-exec-mac.zip + UseMinimatch: true + signConfigType: 'inlineSignParams' + inlineOperation: >- + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppNotarize", + "Parameters": { + "BundleId": "com.microsoft.mxc" + }, + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + - script: | cd "$(targetTripleDir)" unzip -o mxc-exec-mac.zip From 66761801ef330f4f0d9a97424e8641065cd28d64 Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Tue, 19 May 2026 13:24:58 -0700 Subject: [PATCH 7/7] chore: retrigger CI pipeline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>