---
title: "Electron製Macアプリのビルド・コード署名・Apple公証｜Electron 45とnotarytoolで実測した手順"
url: "https://www.issoh.co.jp/tech/details/6874/"
published: 2025-05-22
updated: 2026-10-23
categories: ["フレームワーク"]
publisher: "株式会社一創"
---

# Electron製Macアプリのビルド・コード署名・Apple公証｜Electron 45とnotarytoolで実測した手順

Electronで作ったMacアプリをMac App Store外で配布し、利用者にセキュリティ設定の例外操作をさせずに起動させるには、Developer ID証明書によるコード署名とAppleの公証（Notarization）の両方が必要です。どちらかが欠けると、初回起動時にGatekeeperの警告が出ます。この記事では、Electron Forge 8で`.app`を作るところから始めて、Appleが公証に課す要件、entitlementsのキー、`xcrun notarytool`の実コマンド、electron-builderとForgeでの自動化までを、2026年10月23日に手元のIntel Mac（macOS 26.6.2）とnpmレジストリで確かめた値でまとめます。対象はElectron 45.0.0（2026年10月21日公開）です。署名していないと不具合として現れるElectron API（`safeStorage`や`autoUpdater`など）も整理します。

## まとめ：Electron製Macアプリの署名と公証で押さえる要点

- **Electron 45はmacOS 13 Ventura以降**。macOS 12のサポートはElectron 44で終了した。ビルドした`.app`の`LSMinimumSystemVersion`は`13.0`だった。
- **2段階が必須**。コード署名（Developer ID Application証明書）と公証（Appleへ提出）の両方が要る。Mac App Store配布だけは公証不要。
- **universalビルドは署名設定とセットで考える**。Forge 8.0.1の雛形のまま`--arch=universal`を実行すると、arm64側だけが再署名されて結合に失敗した。`osxSign`を書くと通る。
- **altoolは2023年11月1日に受付停止**。本記事では後継の`xcrun notarytool`を使う。手元のCommand Line Toolsに入っていたのは`notarytool 1.1.2 (41)`。
- **署名に`--deep`を使わない**。Appleは署名時の`--deep`を明示的に禁じている（検証時の使用は可）。
- **entitlementsは@electron/osx-signの既定が出発点**。arm64ビルドで`com.apple.security.cs.allow-jit`が無いとアプリがクラッシュする、とelectron-builderが明記している。
- **提出前の点検は`syspolicy_check`**。未署名のビルドにかけると、本体・ヘルパー・dylibの計15ファイルが「File is not signed at all」で止まった。
- **@electron/notarizeは3.1.2へ上げる**。stapleの再試行は3.1.1では実際には走らず、3.1.2（2026年10月9日公開）で修正された。

## MacでElectronアプリを.appにするまで：Forge 8の雛形とarm64・x64・universal

署名と公証は、ビルドした`.app`に対して行います。先に、何をどのアーキテクチャ向けに作るかを決めておきます。Electronそのものの仕組みや最初の1画面の作り方は[Electronとは？Web技術でデスクトップアプリを作る仕組みと始め方](/tech/details/3322/)に譲り、ここではMac向けに固有の点だけを扱います。

### Electron 45が動くmacOSとCPU

Electron 45.0.0のREADMEは対応環境を「macOS (Ventura and up): Electron provides 64-bit Intel and Apple Silicon / ARM binaries for macOS.」としています。macOS 13 Ventura以降で、IntelとApple Siliconの両方のバイナリが配布されます。[breaking-changes.md](https://raw.githubusercontent.com/electron/electron/v45.0.0/docs/breaking-changes.md)では、macOS 12 Montereyの切り捨てはElectron 44の変更として載っています。手元でビルドした`.app`の`Info.plist`を`PlistBuddy`で読むと、`LSMinimumSystemVersion`は`13.0`でした。macOS 12以前の社内端末が残っている場合は、Electronを44未満に固定するか端末側を更新するかを、ビルド前に決める必要があります。

### 雛形の作成とパッケージ化のコマンド

Electron Forge 8.0.1（2026年9月29日公開）で雛形を作ります。テンプレートの指定方法が変わっており、以前の書き方の`--template=vite-typescript`を渡すと「The “vite-typescript” template no longer exists. Use “–template vite –typescript” instead.」で止まりました。

```
npx create-electron-app@8.0.1 myapp --template vite --typescript
cd myapp

# .app を作る（out/myapp-darwin-<arch>/myapp.app）
npx electron-forge package --arch=arm64
npx electron-forge package --arch=x64

# 配布用の ZIP まで作る（out/make/zip/darwin/<arch>/）
# 既定は実行しているMacのCPU向け。別のCPU向けは -- --arch=arm64 を足す
npm run make
```

雛形は`electron@latest`を`--save-exact`で入れるため、この日は`45.0.0`に固定されました。`src/main.ts`にはmacOS固有の処理が最初から入っています。全ウィンドウを閉じても`process.platform !== 'darwin'`のときしか`app.quit()`しない分岐と、Dockアイコンのクリックで`activate`が来たらウィンドウを作り直す分岐です。Macのアプリはウィンドウを閉じてもCmd+Qまで終了しない、という作法に合わせたものなので、Windowsと同じ挙動にしたい場合もここを消すのではなく意図して書き換えます。

出力の大きさは`du -sh`でx64が258M、arm64が251M、両方を1つにしたuniversalが476Mでした。`.app`の中のファイル数は258で、開発ディレクトリの`node_modules`（10,022ファイル）が丸ごと入るわけではありません。雛形は`asar: true`でアプリのコードを`app.asar`（この雛形では11,516バイト）1ファイルにまとめています。Appleは公証時間を抑える方法としてファイル総数の削減を挙げています。asarでファイル数を減らすことは有効な対策ですが、この実測では公証時間そのものの短縮までは確認していません。

### universalビルドが署名設定なしで失敗する理由

Intel機とApple Silicon機の両方に1つのファイルで配るなら`--arch=universal`を使います。ところが雛形のまま実行すると、x64とarm64を個別にビルドした後の結合で次のエラーになりました。

```
✖ Stitching darwin/x64 and darwin/arm64 into a darwin/universal package
  [FAILED: While trying to merge mach-o files across your apps we found a mismatch,
   the number of mach-o files is not the same between the arm64 and x64 builds]
```

直前に出る差分の一覧（`uniqueToArm64`）は、ヘルパーやフレームワークの`_CodeSignature/CodeResources`だけでした。arm64側にだけ署名ファイルが増えています。原因は雛形が有効にしている`@electron-forge/plugin-fuses` 8.0.1で、fuseを書き換えたあと`resetAdHocDarwinSignature`をarm64のときだけ有効にしています。条件は`!hasOSXSignConfig && applePlatforms.includes(platform) && arch === 'arm64'`で、署名設定が無いarm64ビルドに限ってad hoc署名をかけ直すため、x64側と中身が揃わなくなります。

`packagerConfig`に`osxSign`を書くと条件から外れ、同じ環境で結合まで通りました。空オブジェクトの`osxSign: {}`でも通ります。配布用に署名するなら`osxSign`はどのみち書くので、universalで配る構成では署名設定を入れてからビルドを試す、という順にしておくと詰まりません。結合後のメインバイナリは`lipo -archs`で`x86_64 arm64`と表示されました。

## Mac App Store外配布で必要になる署名と公証の要件

Appleの「[Notarizing macOS software before distribution](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution)」は、公証を「App Reviewではない」と明言しています。逐語では「Notarization of macOS software is not App Review. The Apple notary service is an automated system that scans your software for malicious content, checks for code-signing issues, and returns the results to you quickly.」。人間の審査ではなく、マルウェア混入と署名不備を機械的に走査する仕組みです。

問題が無ければ公証サービスがチケットを発行し、開発者はそれをアプリへ貼り付け（staple）ます。Appleはチケットをオンラインにも公開するため、Gatekeeperはアプリに貼られたチケットからでもオンラインからでも公証済みであることを確認できます。適用範囲は「Beginning in macOS 10.15, all software built after June 1, 2019, and distributed with Developer ID must be notarized.」で、2019年6月1日以降にビルドしたDeveloper ID配布物が対象です。Mac App Store配布は提出プロセスに同等の検査が含まれるため除外されます。

### Appleが公証に要求する前提条件

提出前に満たしておく条件は同ドキュメントに列挙されています。Electron側の設定と対応づけると次のようになります。

| Appleの要件                     | Electronでの実現方法                                         |
| ---------------------------- | ------------------------------------------------------ |
| 実行ファイル全てに有効なコード署名            | @electron/osx-sign が入れ子の順に署名                           |
| Developer ID証明書を使用           | Mac Distribution・ad hoc・Apple Development・ローカル開発証明書は不可 |
| Hardened Runtime を有効化        | electron-builder は `hardenedRuntime` 既定 `true`         |
| secure timestamp を含める        | codesign の `--timestamp`（署名ツールが自動付与）                   |
| `get-task-allow` を true にしない | デバッグ用entitlementsを本番plistに残さない                         |
| macOS 10.9 以降のSDKへリンク        | Electron本体が満たす                                         |
| entitlements は整形済みXML・ASCII  | `plutil -lint` で検証（バイナリplist不可）                        |

Developer ID以外の証明書を使わないという条件は取り違えやすい箇所です。Appleは逐語で「Don’t use a Mac Distribution, ad hoc, Apple Developer, or local development certificate.」と書いています。Xcodeが自動生成した開発用証明書で署名したビルドは、公証に出す前の段階で対象外です。前の章でForgeが作った`.app`も、`codesign -dv`で見ると`Signature=adhoc`・`TeamIdentifier=not set`で、このままでは提出できません。

### Gatekeeperで弾かれたときの現行手順

公証が通っていないアプリをユーザーが開こうとすると、Gatekeeperが警告を出します。Appleのサポート記事「[Safely open apps on your Mac](https://support.apple.com/en-us/102445)」が示す回避手順は、アプリを一度開こうとしたうえで「Open System Settings. Click Privacy & Security, scroll down, and click the Open Anyway button」です。かつて広く案内されていたFinderでの右クリック（Control-click）からの「開く」は、このページの現行版には一切記載がありません。社内配布で「右クリックで開いてください」と案内している手順書は、現在のmacOSの画面と合わなくなっています。開発者側で公証を通しておけば、この案内自体が不要になります。

## Apple Developer Programへの登録と証明書の準備

### 個人登録と組織登録で変わる要件

Developer ID Application証明書はApple Developer Programのメンバーシップがないと発行できません。費用はApple公式の日本語ページに「Apple Developer Programの年間登録料は99米ドル、Apple Developer Enterprise Programの年間登録料は299米ドルです」とあります。価格は地域により異なり、登録手続き時に現地通貨で表示されます。非営利団体・認定教育機関・政府機関は登録料免除を申請できます。

組織として登録する場合はD-U-N-S番号が要ります。Dun & Bradstreetが割り当てる9桁の事業者識別番号で、Appleは登録審査で組織の実在と法人格の確認に使います。Appleの日本語ページは「組織（政府機関を除く）を登録するには、組織の情報、法人のステータス、および住所を確認するためのD-U-N-S番号を有している必要があります」とし、さらに「DBA、架空事業名、商号、支店名での登録は認められません」と明記しています。屋号や支店名義では通りません。申請担当者は組織の所有者・創設者・経営管理者・上級プロジェクト管理者か、上級社員から法的権限を付与された従業員である必要があります。D-U-N-S番号の取得自体に日数がかかるため、法人配布を計画しているなら最初に着手する項目です。

### 証明書の確認と秘密鍵の保管

証明書をインストールしたあと、手元に使える署名IDがあるかはコマンドで確認できます。Electron Forgeの公式ガイドが案内しているのと同じコマンドです。

```
security find-identity -p codesigning -v
```

ここに`Developer ID Application: 組織名 (TEAMID)` の行が出れば署名に使えます。証明書が無い環境では`0 valid identities found`だけが返ります。出てこない場合は証明書が未インストールか、秘密鍵がキーチェーンに無い（別のマシンで発行したまま書き出していない）状態です。証明書署名要求（CSR）はキーチェーンアクセスの「認証局に証明書を要求」から作りますが、このとき生成される秘密鍵はそのMacのキーチェーンにしか存在しません。秘密鍵だけを後から再取得する手段は無いため、証明書を発行したマシンから`.p12`形式で書き出して保管しておきます。

## entitlements.plistに書くキー：既定で足りる範囲と足すべき範囲

Hardened Runtimeを有効にすると、JITコンパイルや署名されていないライブラリの読み込みが既定で禁止されます。ChromiumのV8を抱えるElectronはここに正面からぶつかるため、entitlementsで例外を宣言する必要があります。

@electron/osx-sign 2.7.1（2026年9月28日公開）のパッケージに同梱されている既定のentitlementsを展開すると、直接配布用の`default.darwin.plist`には8つのキーが入っていました。`com.apple.security.cs.allow-jit`、`com.apple.security.device.audio-input`、`com.apple.security.device.bluetooth`、`com.apple.security.device.camera`、`com.apple.security.device.print`、`com.apple.security.device.usb`、`com.apple.security.personal-information.location`、`com.apple.security.personal-information.photos-library` です。GPUプロセス用とレンダラープロセス用は`com.apple.security.cs.allow-jit`のみ、プラグイン用だけが`allow-unsigned-executable-memory`と`disable-library-validation`を追加で持ちます。この構成は2.7.0から変わっていません。2.7.1の変更はMac App Store向けで、リリースノートの「throw when MAS entitlements are missing app-sandbox key」のとおり、MAS用entitlementsに`app-sandbox`が無いとエラーで止まるようになりました。

自前のplistを書くなら、まず`allow-jit`だけの最小構成から始め、使う機能に応じて上の一覧からキーを足していく形にします。ただし既定の8キーは性質が揃っていません。Appleの[Hardened Runtime](https://developer.apple.com/documentation/security/hardened-runtime)のページが資源アクセスとして挙げているのはマイク（`audio-input`）・カメラ・位置情報・写真ライブラリなどで、`device.usb`はAppleの説明が「Use this key to allow your sandboxed app to interact with USB devices」とあるとおりApp Sandbox用のキーです。Mac App Store外で配るアプリでは、使う機能に対応するHardened Runtime側のキーを選び、カメラやマイクのように利用者の許可を求める機能では`Info.plist`の用途説明文も用意します。

```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
  </dict>
</plist>
```

electron-builderの設定には親用の`entitlements`と、フレームワークやヘルパーに適用する`entitlementsInherit`の2つがあります。プロセスごとに権限を分けない構成なら、両方に同じ内容のファイルを指定して構いません。分ける場合は、ヘルパー側を`allow-jit`だけにした`build/entitlements.mac.inherit.plist`を上と同じ書式で別途用意します。

このキーを落としたときの症状は起動失敗です。electron-builderの型定義には「Be aware that your app may crash if the right entitlements are not set like `com.apple.security.cs.allow-jit` for example on arm64 builds with Electron 20+」と書かれています。Intel機では動いたのにApple Silicon機だけ落ちる、という報告はこの条件に一致します。

迷いやすいのが`com.apple.security.cs.allow-unsigned-executable-memory`です。古い記事では必ず足すよう書かれていますが、@electron/notarizeのREADMEは「If you are using Electron 11 or below, you must add the `com.apple.security.cs.allow-unsigned-executable-memory` entitlement too. When using version 12+, this entitlement should not be applied as it increases your app’s attack surface.」としています。Electron 12以降では付けるべきではない、という判断です。`disable-library-validation`も同様で、ネイティブアドオンを別のTeam IDで署名している場合を除いて、むやみに付けるとHardened Runtimeの保護を自分から外すことになります。

絶対に入れてはいけないのが`com.apple.security.get-task-allow`です。デバッガのアタッチを許可するキーで、値を`true`にしたまま提出すると公証が通りません。Xcodeは新規macOSプロジェクトで`CODE_SIGN_INJECT_BASE_ENTITLEMENTS`を`YES`に設定し、ビルド時にこのキーを自動で足すため、Xcode由来のplistを本番へ流用すると混入します。

形式面の落とし穴もあります。Appleはentitlementsを整形済みXMLかつASCIIで要求しており、バイナリplist（bplist）のまま渡すと公証サービスが拒否します。手で編集したplistは提出前に検証しておきます。

```
plutil -convert xml1 build/entitlements.mac.plist
plutil -lint build/entitlements.mac.plist
```

## codesignでの署名：`--deep`を使わない理由

Electronアプリは`MyApp Helper (GPU).app`・`(Renderer).app`・`(Plugin).app`やElectron Frameworkなど、入れ子の実行コードを多数抱えています。これらは内側から順に署名し、最後にアプリ本体を署名する必要があります。

ここで`--deep`を使ってまとめて済ませたくなりますが、Appleは「[Creating distribution-signed code for the Mac](https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac)」で明確に禁じています。逐語は「Don’t pass the `--deep` option to `codesign` when you sign code.」で、理由も2つ挙げられています。1つは「The `--deep` option applies the same code-signing options to every code item that it signs.」で、親と子で別のentitlementsが必要な構成では成り立ちません。もう1つは「`codesign` only signs nested code when you use the `--deep` option if that code is in particular locations in the top-level bundle.」で、想定外の場所に置いたコードは署名されないまま残ります。Appleは同じ箇所で、検証目的での`--deep`は有用だと補足しています。

アプリ本体に対する署名は次の形になります。`dist/mac-arm64/MyApp.app`はelectron-builderの出力先を例にしたパスで、Forgeの雛形なら`out/myapp-darwin-arm64/myapp.app`に読み替えます（以降のコマンドも同じ）。ヘルパーとフレームワークは種類に応じた署名オプションで先に署名してから実行します。

```
codesign --force --options runtime --timestamp \
  --entitlements build/entitlements.mac.plist \
  --sign "Developer ID Application: 組織名 (TEAMID)" \
  "dist/mac-arm64/MyApp.app"
```

Electronのバンドル構成では署名対象が数十個になるため、この順序制御を手書きのスクリプトで維持するのは現実的ではありません。@electron/osx-signがこの処理を担っており、electron-builderとElectron Forgeはどちらも内部でこれを呼びます。手作業の`codesign`は、署名が落ちている箇所を特定するときの調査手段として使うのが実際的です。

## notarytoolの実コマンド：認証情報の保存から提出・ログ確認まで

Appleは「Starting November 1, 2023, the Apple notary service no longer accepts uploads from `altool` or Xcode 13 or earlier.」として、2023年11月1日にaltool経由の受付を停止しました。現在の提出手段はコマンドラインの`xcrun notarytool`のほか、Xcodeの画面操作、REST APIのNotary APIがあります。ここではCIに載せやすいnotarytoolを扱います。手元のmacOS 26.6.2で`xcrun --find notarytool`を実行すると`/Library/Developer/CommandLineTools/usr/bin/notarytool`が返り、バージョンは`1.1.2 (41)`でした。この環境にはXcode.appを入れておらず`xcode-select -p`もCommand Line Toolsを指しているため、公証コマンドを動かすだけならXcode本体は必須ではありません。ただし証明書の取得とインストールにはXcodeを使う手順が公式に案内されています。

### 認証情報の3方式と使い分け

`notarytool submit --help`が受け付ける認証オプションは3系統に分かれます。いずれか1つを選びます。

| 方式                      | 渡す値                                   | 向く場面                        |
| ----------------------- | ------------------------------------- | --------------------------- |
| App Store Connect APIキー | `--key` `--key-id` `--issuer`         | CI。チーム用キーは個人のApple IDに紐づかない |
| Apple ID                | `--apple-id` `--password` `--team-id` | 手元での初回確認                    |
| キーチェーンプロファイル            | `--keychain-profile`                  | 開発機での反復実行                   |

`--issuer`の扱いには注意点があります。`--help`の逐語は「App Store Connect API Issuer ID, UUID format. Required for Team API Keys. Do not provide for Individual API Keys.」で、チーム用キーでは必須、個人用キーでは渡してはいけません。渡すと401が返ります。

`--password`に渡すのはApple IDのパスワードではなく、appleid.apple.comで発行するApp用パスワードです。開発機で繰り返し実行するなら、資格情報をキーチェーンに保存しておくと毎回渡さずに済みます。

```
xcrun notarytool store-credentials "issoh-notary" \
  --key ./AuthKey_XXXXXXXXXX.p8 \
  --key-id XXXXXXXXXX \
  --issuer 00000000-0000-0000-0000-000000000000
```

### 提出からstapleまでのコマンド列

署名済みのアプリを公証に通す手順は次の3ステップです。`.app`はディレクトリなのでそのままでは提出できず、ZIPに固める必要があります。Appleの公式サンプルスクリプトは「Create a ZIP archive suitable for notarization.」というコメント付きで`ditto`を使っており、ここに合わせます（@electron/notarizeは`--sequesterRsrc`も付けています）。`ditto`はバンドルのシンボリックリンクや拡張属性を保ったまま固められるため、署名済みバンドルの受け渡しに向きます。

```
# 1. 提出用ZIPを作る
ditto -c -k --sequesterRsrc --keepParent \
  "dist/mac-arm64/MyApp.app" "dist/MyApp-submit.zip"

# 2. 公証へ提出して処理完了まで待つ
xcrun notarytool submit "dist/MyApp-submit.zip" \
  --keychain-profile "issoh-notary" --wait

# 3. チケットをアプリ本体へ貼り付ける
xcrun stapler staple "dist/mac-arm64/MyApp.app"
```

提出用ZIPと配布用ZIPは別物です。チケットが貼られるのは手順3の`.app`であって、手順1で作ったZIPの中身は公証前の状態のままです。ZIPで配布するなら、stapleのあとに`ditto`をもう一度実行して配布用アーカイブを作り直します。DMGで配る場合は、署名済みの`.app`をDMGに入れてDMGにも署名し、最外層のDMGを公証・stapleします。Appleの「[Packaging Mac software for distribution](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution)」は「If you distribute your product using nested containers, only notarize the outermost container.」としており、中の`.app`とDMGを別々に提出する必要はありません。

`--wait`の役割は「提出を待つ」ことではなく「公証処理の完了を待つ」ことです。Appleの説明は「Use the `wait` flag to tell `notarytool` to exit only after the Notary service finishes processing the submission. This eliminates the need to poll the service for status.」で、省略した場合はアップロードと受付までで戻り、結果は`info`や`wait`で別途確認することになります。`--help`の既定値も`--wait/--no-wait ... (default: false)`です。

所要時間についてAppleは「Notarization completes for most software within 5 minutes, and for 98 percent of software within 15 minutes.」と公称しています。超過したときはまず`notarytool info`で提出が受理されているかを確認します。そのうえで処理自体が長引いているなら、原因候補の1つがファイル数です。Appleは長時間化を避ける方法の筆頭に「Minimize the total number of files, even when the individual files are small.」を挙げています。前述のForge雛形では`node_modules`の10,022ファイルが`.app`では258ファイルに収まっていました。asarを外したり、依存を`app.asar.unpacked`へ大量に逃がしたりすると、この数が一気に増えます。長時間ブロックしたくないCIでは`--webhook`に公開URLを渡して完了通知を受ける方法もあります。

### 提出前の点検と、失敗時のログ取得

提出して落ちてから直すより、ローカルで先に検査したほうが速く終わります。本記事のmacOS 26.6.2環境には`syspolicy_check`が標準で入っており、manページの説明は「syspolicy\_check combines checks from multiple parts of macOS, including the frameworks and subsystems exposed by the existing codesign, spctl, and stapler commands.」です。サブコマンドは2つあり、`notary-submission`は「Runs the same checks on an app as the Apple notary service」、`distribution`は「Runs the same checks on your app as macOS does when determining if your app can be executed. This includes Gatekeeper checks, XProtect checks, provisioning profile checks, and more.」と説明されています。

```
# 提出前：公証サービスと同じ検査をローカルで実行
syspolicy_check notary-submission "dist/mac-arm64/MyApp.app"

# staple後：macOSが起動時に行う判定と同じ検査
syspolicy_check distribution "dist/mac-arm64/MyApp.app" --json
```

署名前のForge雛形（x64）にかけた結果は「App has failed one or more pre-notarization checks.」で、指摘は17件でした。うち15件が`Severity: Fatal`の「File is not signed at all.」で、本体の実行ファイル、4つのヘルパー、`libvk_swiftshader.dylib`と`libffmpeg.dylib`、Squirrel・Mantle・ReactiveObjCの各フレームワークが並びます。残りは「This app is adhoc signed. While it may run locally, adhoc signed apps are not suitable for distribution.」と、Squirrel.frameworkの`Resources/ShipIt`に対する「Resources directory contains Mach-o binaries.」の警告2件です。universalの出力でも同じ17件でした。署名ツールを通した後にこの一覧が空になるかを見れば、提出前に署名漏れを拾えます。

それでも提出が`Invalid`で返ったときに原因を特定できるのは`log`サブコマンドです。`submit`が返すsubmission IDを渡します。

```
# 過去の提出一覧
xcrun notarytool history --keychain-profile "issoh-notary"

# 失敗理由の詳細（JSONで返る）
# SUBMISSION_IDにsubmitが返したIDを設定してから実行する
xcrun notarytool log "${SUBMISSION_ID:?submission IDを設定してください}" \
  --keychain-profile "issoh-notary" notary-log.json
```

ログの`issues`配列には、署名されていない実行ファイルのパスやHardened Runtimeが無効なバイナリが個別に並びます。多いのは`node_modules`配下に紛れ込んだ実行属性付きのバイナリやヘルパーです。署名そのものの整合性は`codesign`で、チケットが貼れているかは`stapler`で確認します。ここでの`--deep`は検証用途なので問題ありません。

```
codesign -vvv --deep --strict "dist/mac-arm64/MyApp.app"
xcrun stapler validate "dist/mac-arm64/MyApp.app"
```

従来よく使われてきた`spctl --assess`も動きますが、Appleは`codesign`と`spctl`だけではGatekeeperの判定を完全には再現しないとしています。`syspolicy_check`はその3コマンドが露出しているフレームワークをまとめて呼ぶため、実際の起動時判定に近い結果が得られます。electron-builderが`gatekeeperAssess`の既定値を`false`にしている（型定義の逐語は「Whether to let `@electron/osx-sign` validate the signing or not. @default false」）のも、ビルド中の`spctl`実行を前提にしない設計です。

## electron-builderとElectron Forgeの署名・公証設定

ビルドツールを使うと、署名・ZIP化・提出・stapleが1回のビルドコマンドで走ります。ただし有効化に必要な設定は明示的に書く必要があり、その書き方は世代によって変わっています。

### electron-builder：notarizeは真偽値、資格情報は環境変数

2026年10月23日にnpmレジストリを引いたところ、`npm i electron-builder`で入るのは`latest`タグの**26.15.3**（2026年6月9日公開）のままでした。26系の新しい版は`v26`タグに割り当てられており、現在は**26.18.0**（2026年10月20日公開）です。`latest`には4か月以上新版が乗っていません。26.16以降を使いたい場合はバージョンを明示するか`electron-builder@v26`を指定します。

26系の`mac.notarize`は真偽値です。26.18.0の[macOptions.ts](https://raw.githubusercontent.com/electron-userland/electron-builder/electron-builder@26.18.0/packages/app-builder-lib/src/options/macOptions.ts)でも`readonly notarize?: boolean`で、`teamId`のようなサブプロパティを書く形ではありません。資格情報は環境変数から読む設計で、型定義に3組が列挙されています。

```
{
  "build": {
    "appId": "jp.co.example.myapp",
    "mac": {
      "target": ["dmg", "zip"],
      "hardenedRuntime": true,
      "entitlements": "build/entitlements.mac.plist",
      "entitlementsInherit": "build/entitlements.mac.inherit.plist",
      "notarize": true
    }
  }
}
```

```
# 次の3組のうち、いずれか1組だけを設定する（併用しない）

# 1. App Store Connect APIキー（推奨）
export APPLE_API_KEY=./AuthKey_XXXXXXXXXX.p8
export APPLE_API_KEY_ID=XXXXXXXXXX
export APPLE_API_ISSUER=00000000-0000-0000-0000-000000000000

# 2. Apple ID
# export APPLE_ID=dev@example.co.jp
# export APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx
# export APPLE_TEAM_ID=TEAMID

# 3. キーチェーンプロファイル
# export APPLE_KEYCHAIN=login.keychain-db
# export APPLE_KEYCHAIN_PROFILE=issoh-notary
```

どれを選ぶかについて、electron-builderは型定義の中で「For security reasons it is recommended to use the first option」と1番目のAPIキー方式を名指しで推奨しています。Apple IDとApp用パスワードの組は、Apple IDのパスワードを変更するたびにApp用パスワードの再生成が要る点でも運用が重くなります。方式を切り替えるときは前の方式の環境変数を残さないでください。`entitlements`と`entitlementsInherit`を省略した場合は`build/entitlements.mac.plist`と`build/entitlements.mac.inherit.plist`が既定で参照されます。

### Electron Forgeの署名・公証設定：osxSignとosxNotarize

Forgeは`packagerConfig`の下に署名用と公証用の設定を別々に持ちます。公式ガイドは`osxSign`について「object must exist even if empty」とし、空オブジェクトから始めることを勧めています。前述のuniversalビルドの失敗を避ける意味でも、この1行は先に書いておきます。公証側は認証情報を環境変数から読ませます。次の例は雛形の`forge.config.mts`の`packagerConfig`だけを書き換えたもので、ファイル全体を置き換えると雛形の`makers`・`plugins`（Viteのビルドとfuses）が消えます。`APPLE_PASSWORD`にはApp用パスワードを入れます（Forgeのガイドの変数名に合わせています）。

```
import type { ForgeConfig } from '@electron-forge/shared-types';

// forge.config.mts の packagerConfig 部分（import・makers・plugins は雛形のまま残す）
const config: ForgeConfig = {
  packagerConfig: {
    asar: true,
    osxSign: {},
    osxNotarize: {
      appleId: process.env.APPLE_ID!,
      appleIdPassword: process.env.APPLE_PASSWORD!,
      teamId: process.env.APPLE_TEAM_ID!,
    },
  },
  // makers・plugins は雛形のまま
};

export default config;
```

### @electron/notarize 3.1.2で直ったstapleの再試行

Forgeと@electron/packagerが内部で呼ぶのは`@electron/notarize`です。現行版は**3.1.2**（2026年10月9日公開）で、Forge 8.0.1の雛形を作った時点で`node_modules`に入っていたのもこの版でした。v3.0.0でaltool用のLegacy strategyが削除され、Node.js 22.12.0以上・ESM専用になっています。

3.1.1と3.1.2のパッケージを展開して比べると、処理の違いは`lib/index.js`の1行だけでした。どちらも`notarytool`での提出後に`promise-retry`で`stapleApp()`を`retries: 3`付きで呼びますが、3.1.1は`retry(() => stapleApp({ appPath }), …)`、3.1.2は`retry((retry) => stapleApp({ appPath }).catch(retry), …)`です。`promise-retry`は渡された`retry`関数が呼ばれたときだけやり直すため、3.1.1の書き方では失敗しても再試行しません。毎回失敗する関数を両方の書き方で渡して数えると、3.1.1の形は1回、3.1.2の形は4回（初回＋再試行3回）実行されました。リリースノートの表題も「Retry failed stapling after notarization」です。公証は通ったのにstapleだけが一時的なネットワーク失敗で落ちてビルドが止まる、という症状が3.1.1で出ていたなら、3.1.2へ上げるのが対処です。

引数の検証は3.1.1から変わっていません。`tool`引数は型定義から消えており、TypeScriptでは型エラーになり、JavaScriptで書いても実行時は単に無視されます。`tool: 'notarytool'`だけを頼りにしている設定は認証情報の不足として落ちます。`validateAuthorizationArgs`へ実際に渡して確かめた結果が次のとおりです。

| 渡した引数                             | 挙動                                        |
| --------------------------------- | ----------------------------------------- |
| `{ tool: 'notarytool' }` のみ       | 例外「No authentication properties provided」 |
| `tool` \+ Apple ID一式              | 通る（`tool`は無視される）                          |
| `appleId` \+ `appleIdPassword`    | 例外「The teamId property is required」       |
| `appleApiKey` \+ `appleApiKeyId`  | 通る（`teamId`も`appleApiIssuer`も不要）          |
| `appleId` と `keychainProfile` の併用 | 例外「Cannot use … at once」                  |

`teamId`が必須なのはApple ID方式のときだけで、APIキー方式とキーチェーン方式では要りません。この違いは移植時に踏みやすい落とし穴です。Electron本体のドキュメント（v45.0.0タグの[code-signing.md](https://raw.githubusercontent.com/electron/electron/v45.0.0/docs/tutorial/code-signing.md)）に載っている@electron/packagerの例は、今も`osxNotarize`に`appleId`と`appleIdPassword`だけを書いており、`teamId`がありません。@electron/packager 20.3.0の`createNotarizeOpts`は受け取ったオブジェクトをそのまま返すだけで`teamId`を補わないため、この例をコピーすると上表の3行目で止まります。Forge側のガイドが`appleId`・`appleIdPassword`・`teamId`の3つを表に載せており、そちらが現行の正しい書き方です。

`notarize()`の処理順は「署名の検証（`codesign -vvv --deep --strict`）、`.app`なら`ditto -c -k --sequesterRsrc --keepParent`でZIP化、`notarytool submit --wait --output-format json`、`xcrun stapler staple`」で、`.dmg`と`.pkg`はZIP化せずそのまま提出します。ビルドツール経由なら`xcrun stapler staple`を後続ステップに書き足す必要はありません。

## 署名しないと壊れるElectron API：バグに見えて原因が署名であるケース

自動更新やキーチェーン連携を使うアプリでは、署名と公証を配布直前の作業に回すべきではありません。Electron公式ドキュメント（v45.0.0タグのcode-signing.md）は「macOS APIs that require code signing」という節を設け、署名済みでないと正しく動かないAPIを名指ししています。同じ節は「an unsigned or ad-hoc signed app may behave inconsistently, and issues that look like Electron bugs are often resolved by properly signing (and notarizing) your app」とも書いており、これらのAPIについては、未署名ビルドでの確認だけでは配布時の動作を判断できない、という話です。

- **`safeStorage`** … 逐語で「Without a valid, consistent code signature, macOS may be unable to tell that two builds of your unsigned app are "the same app", which can cause the Keychain to prompt the user for permission again after every update.」。アップデートのたびにキーチェーンの許可を求められる場合、暗号化処理の実装より先に、署名の有無とビルド間で署名が一貫しているかを確認する価値があります。
- **`app.setLoginItemSettings()`** … パッケージ化・署名・公証がされていないと、ログイン項目の登録が無言で失敗することがあります。エラーが出ないため実装ミスと誤認しやすい挙動です。
- **`cookieEncryption` fuse** … Cookie暗号化は`safeStorage`と同じOSレベルのキーチェーンアクセスを使うため、同じ署名要件がかかります。Forge 8.0.1の雛形の`forge.config.mts`は`[FuseV1Options.EnableCookieEncryption]: true`を最初から書いているので、雛形から始めたアプリは何もしなくてもこの要件の対象です。
- **`autoUpdater`** … 逐語で「`Squirrel.Mac` requires the app to be signed for automatic updates to work at all」。自動更新は署名なしでは端的に動きません。

逆に、これらのAPIを一切使わない単機能の社内ツールなら、開発中はad hoc署名（`identity: "-"`）で進める判断が成り立ちます。ただしad hoc署名にも条件があります。electron-builderの型定義は「When using ad-hoc signing (`identity: "-"`), hardened runtime enforces library validation which will reject pre-signed Electron frameworks that carry a different Team ID.」と書いており、`hardenedRuntime`を既定の`true`のままad hoc署名すると、別のTeam IDで署名済みのElectronフレームワークがライブラリ検証で弾かれます。回避するには`hardenedRuntime: false`にするか`com.apple.security.cs.disable-library-validation`を足すことになりますが、どちらも本番構成としては採るべきではありません。開発用と配布用で設定を分ける前提の話です。

## CIで署名と公証を通す：秘密情報の受け渡しとネットワーク要件

codesignもnotarytoolもmacOSのシステムコマンドなので、CIではmacOSランナーが必要です。GitHub Actionsなら`runs-on: macos-latest`を指定します。ワークフローの組み立て自体は[GitHub Actionsでビルド・自動テストを設定する方法](/tech/details/2497/)で扱っている手順と同じで、macOS固有なのは証明書と資格情報の渡し方です。

証明書はBase64化してリポジトリのSecretsに入れ、ジョブの中で一時キーチェーンへ取り込みます。`.p8`のAPIキーも同様にSecretsから復元します。

```
- name: 証明書を一時キーチェーンへ取り込む
  env:
    MAC_CERT_P12: ${{ secrets.MAC_CERT_P12 }}
    MAC_CERT_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
    KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
  run: |
    echo "$MAC_CERT_P12" | base64 --decode > /tmp/cert.p12
    security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
    security default-keychain -s build.keychain
    security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
    security import /tmp/cert.p12 -k build.keychain \
      -P "$MAC_CERT_PASSWORD" -T /usr/bin/codesign
    security set-key-partition-list -S apple-tool:,apple: \
      -s -k "$KEYCHAIN_PASSWORD" build.keychain
```

`set-key-partition-list`を省くと、`codesign`実行時にキーチェーンのパスワード入力ダイアログを待ってジョブがタイムアウトします。CIで「署名が固まる」場合はここを疑います。electron-builderに証明書の取り込みを任せている場合も同じ箇所で、26.16.1のリリースノートには「fix(mac): use keychain password for set-key-partition-list」という修正が入っています。`latest`の26.15.3を使っているCIはこの修正を含みません。環境変数とシークレットの扱い全般は[環境変数とは？Linux・Windowsの設定方法とDocker・CIでの受け渡し](/tech/details/17616/)にまとめています。

見落とされやすいのがネットワーク要件です。secure timestampの取得にはインターネット接続が必須で、Appleは「macOS accepts only one secure timestamp server, namely `timestamp.apple.com`」と明記し、IPv4は`17.32.213.0/24`・`17.157.80.0/24`・`17.179.249.0/24`、IPv6は`2620:149:980:500::/64`・`2620:149:981:603::/64`・`2620:149:982:404::/64`の範囲を挙げています。2026年10月23日に手元から確認したところ、このホストは名前解決で`17.157.80.35`（上記IPv4範囲内）を返し、TCP 80番のHTTPには応答する一方、443番は接続できませんでした。タイムスタンプ取得はHTTPで行われるため、公証アップロード用のHTTPSとは別に80番の疎通を確保する必要があります。`man codesign`の記述も「If the timestamp authority service cannot be contacted over the Internet, or it malfunctions or refuses service, the signing operation will fail.」で、警告ではなくエラーです。

署名済みバイナリにタイムスタンプが入ったかは`codesign -dvv`で確認できます。Appleの説明では、出力に`Timestamp`と日付が出れば secure timestamp 付き、`Signed Time`が出ている場合は付いていません。

切り分けの順序も決めておきます。ローカルで`syspolicy_check notary-submission`が通るかを先に確認し、通ったうえでCIで落ちるならネットワークと資格情報を疑う。この順を守らないと、署名の不備をCI設定の問題として追いかけることになります。ログにAPIキーやApp用パスワードが混ざらないよう、`--verbose`を常用しない運用にしておきます。

## よくある質問

### altoolはまだ使えますか？

公証の用途では使えません。Appleは2023年11月1日にaltoolからのアップロード受付を停止しており、テクニカルノートでも公証目的でのaltoolを非推奨としています。ただしApp Storeへの提出など他の用途では引き続き使えます。@electron/notarizeもv3.0.0でaltool用の実装を削除しました。

### Developer ID Application証明書の有効期限が切れたらどうなりますか？

配布済みのアプリは動き続けます。Appleは「If your certificate expires, users can still download, install, and run versions of your Mac applications that were signed with this certificate. However, you’ll need a new certificate to sign updates and new applications.」としており、期限切れで止まるのは新しいビルドへの署名だけです。ただし証明書が失効（revoke）された場合は別で、その証明書で署名したアプリはインストールできなくなります。期限切れと失効では結果が大きく違うため、鍵の取り扱いを見直すときも安易にrevokeを選ばないでください。

### App Store Connect APIキーはどこで発行しますか？

App Store Connectにサインインし、「ユーザーとアクセス」からIntegrationsのキー管理画面で発行します。発行時にダウンロードできる`.p8`ファイルは再取得できないため、その場で安全な場所へ保管します。あわせてKey ID（10文字前後の英数字）とIssuer ID（UUID形式）を控えます。チーム用キーではIssuer IDが必須ですが、個人用キーでは指定してはいけません。CIではこの3つを環境変数`APPLE_API_KEY`・`APPLE_API_KEY_ID`・`APPLE_API_ISSUER`に入れて渡します。

### Macアプリが「開けません」と表示されるのは署名が原因ですか？

Gatekeeperの警告文で切り分けられます。「開発元を確認できないため開けません」は署名または公証の不備です。「アプリケーションが壊れているため開けません」は、ファイルの改変や証明書の失効を検出した場合に出ます。前者であればユーザー側の暫定回避はシステム設定のプライバシーとセキュリティにある「このまま開く」ですが、恒久的な解決は開発者側で公証を通すことです。

### 法人でApple Developer Programに登録するには何が必要ですか？

組織登録では、政府機関を除きD-U-N-S番号と法人格の確認が必要です。申請担当者には、組織を法的に拘束できる立場か、上級社員から権限を付与されていることが求められます。屋号や支店名義では登録できません。

## 関連記事

- [Electronとは？Web技術でデスクトップアプリを作る仕組みと始め方【v44で実測】](/tech/details/3322/)
- [ElectronのUIをWeb技術で作る実践ガイド｜React・Vue連携から最新v43・軽量化・将来性まで](/tech/details/11692/)
- [GitHub Actionsでビルド・自動テストを設定する方法｜CI/CDワークフローの作り方](/tech/details/2497/)
- [環境変数とは？Linux・Windowsの設定方法とDocker・CIでの受け渡し](/tech/details/17616/)

---

出典: [Electron製Macアプリのビルド・コード署名・Apple公証｜Electron 45とnotarytoolで実測した手順](<https://www.issoh.co.jp/tech/details/6874/>)（株式会社一創）
