# セキュリティアーキテクチャ

> LatchkeyがすべてのCIジョブをどのように隔離するか: 専用の使い捨て仮想マシン、インバウンドネットワークなし、エフェメラルな認証情報、保存時および転送中の暗号化、そしてセルフヒーリングエージェントが動作する際の制約。

Source: https://latchkey.dev/ja/documentation/security-architecture

## Summary

- すべてのジョブが**専用のEC2仮想マシン**を取得します。このマシンは正確に1つのジョブだけを実行し、その後、暗号化されたディスクとともに破棄されます。
- ランナーは**パブリックIPを持たず、インバウンドルールがゼロのプライベートサブネット**に配置されます: ランナーに接続できるものは存在しません。
- ランナーの認証情報は**ジャストインタイムかつ単一使用**であり、ランナーが読み取った瞬間にParameter Storeから削除されます。
- セルフヒーリングエージェントは、1エントリのみのsudo許可リストの下で**非rootユーザーとして**動作し、そのAIツールは**デフォルト拒否のサンドボックス**内で実行されます。

このページはセキュリティレビュー向けに書かれています。ランナープラットフォームが実際にどのように構築されているか、つまり隔離境界、マシンから外部に出るもの、認証情報の取り扱い方、そしてセルフヒーリングエージェントに許可されていること・いないことを説明します。GitHub Appがあなたのリポジトリで読み取り・書き込みできる内容については、[セキュリティとGitHubの権限](/documentation/security-and-permissions)を参照してください。

## 隔離: 1ジョブ、1マシン

Latchkeyはジョブ間でマシンを共有しません。各ジョブは、そのジョブのために起動された専用のEC2仮想マシン(m6aクラス、Ubuntu 24.04 LTS)上で実行されます。あなたのジョブは、他のテナントとカーネルを共有するコンテナではなく、長期稼働する共有ホスト上のプロセスでもありません。

単一使用は慣習によってではなく、GitHubの登録レイヤーで強制されます。コールドスタートのランナーは**ジャストインタイム構成**で登録され、GitHubはこれを正確に1つのジョブに対してのみ受け入れます。ウォームプールのランナーは `--ephemeral` フラグで登録され、同じ動作になります。いずれの場合も、インスタンスはシャットダウン時に終了するよう構成されているため、何らかの問題が発生しても再利用されることはありません。

| 境界 | どのように強制されるか |
| --- | --- |
| マシンあたり1ジョブ | GitHubのジャストインタイム構成(コールドスタート)または `--ephemeral` 登録(ウォームプール) |
| マシンは再利用できない | インスタンス起因のシャットダウン動作が終了(terminate)に設定されています。起動テンプレートと直接起動パスの両方で設定されています |
| ディスクは残らない | ルートEBSボリュームは暗号化され、終了時に削除(delete-on-termination)としてマークされています |
| 暴走したジョブは回収される | クリーンアッププロセスが、4時間のジョブ上限(ハードキャップは8時間)を超えたインスタンスを終了させます |
| ランナーは1つのworkspaceに紐付く | インスタンスには組織IDがタグ付けされ、単一の組織ランナーグループに登録されます |

ランナーグループは `visibility: selected` と明示的なリポジトリリストで作成されるため、あなたのworkspace向けに登録されたランナーには、有効化したリポジトリのジョブのみが割り当てられます。

> ****
> 隔離境界は仮想マシンです: ジョブごとに専用のEC2インスタンスが割り当てられ、独自のカーネル、独自の暗号化ディスクを持ち、他のテナントへの経路は一切ありません。コンプライアンス要件に固有のインフラ要求がある場合は、オンボーディング前にご相談ください。一緒に検討します。

## 何がrootとして動作し、なぜそれが封じ込められているのか

自身のVM内では、あなたのジョブは完全な制御権を持ちます: `runner` ユーザーはパスワードなしのsudoとDockerアクセスを持ちます。これはCIとしては通常のことであり(ビルドはパッケージをインストールし、コンテナを実行します)、そして安全である理由はまさに、影響範囲がインバウンドネットワークを持たず、ジョブの終了時に破棄される短命なマシン1台に限られているからです。存在しないサンドボックスをほのめかすよりも、これを率直に述べることを選びます。

## ネットワーク

## 認証情報とシークレット

1. **ランナーの認証情報はジョブごとに発行されます** ジャストインタイムのランナー構成が単一のジョブ用に生成され、暗号化されたSecureStringとしてAWS Systems Manager Parameter Storeに書き込まれます。スコープはそのインストールとジョブに限定されます。
2. **ランナーは一度読み取り、その後削除します** 起動時にインスタンスはそのパラメーターを取得し、鍵素材を含むため直ちに削除します。同じパターンがウォームプールの登録トークンにも適用され、これらは利用可能な55分間のみキャッシュされます。
3. **GitHubへのアクセスは短命です** GitHub Appは秘密鍵でJWTに署名し、操作ごとに短命なインストールアクセストークンと交換します。ランナー上に長期間有効なGitHubトークンが置かれることはありません。
4. **プラットフォームのシークレットはランナーから隔離されています** GitHub Appの秘密鍵はParameter StoreにSecureStringとして保存されます。ランナーが読み取れるシークレットは意図的に最小限であり、プラットフォーム構成のシークレットとは分離されているため、侵害されたインスタンスロールがプラットフォームの認証情報を読み取ることはできません。

デプロイはGitHub OIDCによるロール引き受けでAWSに認証します。CIに長期間有効なAWSアクセスキーは存在しません。MCP連携用のAPIキーはSHA-256ハッシュとしてのみ保存され、平文のキーは作成時に一度だけ表示され、永続化されることはありません。

## 暗号化

| データ | 保存時 | 転送中 |
| --- | --- | --- |
| ランナーのルートディスク | 暗号化されたEBS(gp3)、終了時に削除 | 該当なし |
| ビルドキャッシュ(S3) | SSE-S3(AES-256)、14日で失効 | TLS |
| アプリケーションデータベース | 暗号化されたストレージ、パブリックにアクセス不可 | TLS |
| キャッシュ / キューレイヤー | 保存時および転送中の暗号化を有効化 | TLS |
| ランナー構成とアプリの鍵 | Parameter StoreのSecureString | TLS |
| APIトラフィック | 該当なし | APIドメインでTLS 1.2以上 |

## ビルドキャッシュの隔離

キャッシュオブジェクトは、あなたの組織IDから導出されたキープレフィックスの下にS3へ保存され、テナントIDはジョブリクエストからコントロールプレーンによって注入されます: ジョブが環境変数を設定して別のテナントを名乗ることはできません。オブジェクトは14日後に失効します。

> ****
> すべてのキャッシュの読み取りと書き込みはキャッシュプロキシを経由します。キャッシュプロキシは、コントロールプレーンがそのランナーに割り当てたworkspaceからテナントプレフィックスを解決します。ジョブが提供する情報からは解決しません。キャッシュオブジェクトはworkspaceごとに名前空間が分離され、14日後に失効します。

## ランナーから外部に出るもの

2種類のデータがマシンから外部に出ます。2つ目については正確に述べる価値があります。

- **実行およびジョブのメタデータ**: 名前、タイミング、ステータス、ラベル、そしてワークフローYAML。これらが分析機能と最適化機能を支えます。
- **失敗したステップの出力。** ステップが失敗すると、セルフヒーリングのパイプラインがそのステップの標準出力と標準エラー(それぞれ最大1 MiB)を取得して保存し、失敗の診断と、必要に応じた修正案の作成に使用できるようにします。保持期間は90日です。

> ****
> 取得されるのは失敗したステップの出力のみであり、しかもステップが失敗したときだけです。GitHub Actionsは、Latchkeyが目にする前に、登録済みのシークレットをログ出力内でマスクします。また、ログがエスカレーションバンドルに提供される際に、シークレットは再度秘匿化されます。あなたのworkspaceで取得範囲を変更したい場合はお知らせください。設定いたします。

## セルフヒーリングエージェントに課された制約

セルフヒーリングは、実行中のジョブを変更する唯一のコンポーネントであるため、レビュアーが最も強く精査すべき部分です。ここでは、それが何をできるのかを正確に説明します。

- systemdサービスとして、**rootではなく `runner` ユーザー**で動作します。
- グループに制限されたパーミッションを持つ**Unixドメインソケット**でリッスンします。TCPリスナーもネットワーク面の攻撃対象もありません。
- ランナーエージェント自身のステップスクリプト**のみ**を横取りするシムを介して呼び出されます。それ以外のコマンドはすべて、手を加えられずにそのまま通過します。
- **フェイルオープン**します: エージェントがタイムアウト、拒否、またはエラーになった場合、コマンドはLatchkeyがない場合とまったく同じように実行されます。壊れたヒーラーがあなたのビルドを壊すことはできません。

実行できる特権操作はフィルタリングではなく、許可リスト方式です:

| 制御 | その意味 |
| --- | --- |
| sudo許可リスト | 許可されるのはアンカーされたパターン1つだけです: 単一パッケージの非対話的な `apt-get install` です。`sudo` で始まるそれ以外のものは、プロセスが生成される前に拒否されます。 |
| パッケージ許可リスト | インストール可能なパッケージはコンパイル時のリストから取られ、2つの段階で独立して強制されます。一方の段階は信頼できないビルド出力からコマンドを組み立てるためです。 |
| 環境の安全下限 | `LD_PRELOAD`、`LD_LIBRARY_PATH`、`PATH`、`BASH_ENV`、`ENV` の設定は拒否されます。シェルのメタ文字とコマンド置換も同様です。 |
| 機密パスの拒否 | `/etc`、`/root`、`/proc`、`/sys`、`/var/log`、`~/.ssh`、`~/.aws`、`~/.config/gh` 配下の読み取りは拒否されます。 |
| AIツールのサンドボックス | ステージ3のツールは、bubblewrapベースのサンドボックス内で実行されます。このサンドボックスはデフォルト拒否(ネットワークなし、書き込みパスなし)から始まり、ツールごとに必要最小限の範囲まで緩和されます。 |
| 常駐AIキーなし | 推論ステップはインスタンスロールを介してAWS Bedrockに認証するため、あなたのコードを実行するマシン上に長期間有効なモデルAPIキーが置かれることはありません。 |
| キルスイッチ | セルフヒーリングはworkspaceごとに無効化できます。 |

> ****
> 単純な修復アクション(環境変数の設定、許可リストにあるパッケージのインストール、ディスクの解放、リトライ)はエグゼキューターを介して実行され、そこでは上記の許可リストが強制の境界となります。AIによる推論ステージは、加えてデフォルト拒否のサンドボックス内で実行されます。

## GitHub Appがアクセスできるもの

以下のすべての権限は、特定の機能を支えるために存在します。付与の理由がすぐ隣で分かるよう、フラットなリストではなく用途ごとにグループ化しています。

| 目的 | アクセス | 権限 |
| --- | --- | --- |
| Latchkeyランナーでジョブを実行する | 読み取りと書き込み | 組織のセルフホストランナー、管理、組織管理 |
| パイプラインが何をしたかを確認する | 読み取り | Actions、アーティファクトのメタデータ、メタデータ |
| ジョブの発生に応じて反応する | 読み取りと書き込み | 組織のフック、リポジトリのフック |
| ワークフローを読み取り、修正をPRとして提案する | 読み取りと書き込み | ワークフロー、コード、プルリクエスト |
| GitHubのコストを正確にモデル化する | 読み取り | 組織のプラン |
| 組織のメンバーを確認する | 読み取り | メンバー |
| セキュリティの検出結果をダッシュボードに表示する | 読み取り | Dependabotアラート、シークレットスキャンのアラート、セキュリティイベント、Dependabotのシークレット |
| あなたのスタックに合わせたランナーイメージをビルドする | 読み取り | 組織のランナーカスタムイメージ |

> ****
> このうち2つには平易な説明が必要です。**コード**の書き込みアクセスは、修正をブランチ上のプルリクエストとして開けるようにするために存在します: デフォルトブランチへのプッシュに使われることは決してなく、書き込み経路は3つの方法でゲートされています(後述)。**コード**の読み取りアクセスは、診断ステップが、ステップが失敗した原因を突き止める際に `package.json`、`go.mod`、Dockerfile などのビルドマニフェストを参照できるようにするためのものです。LatchkeyはあなたのActionsのシークレットの値へのアクセスを要求しませんし、GitHubもそれらをアプリに公開しません。

## リポジトリへの変更

Latchkeyがデフォルトブランチにプッシュすることは決してありません。すべての変更はあなたがレビューするプルリクエストであり、書き込み経路は3つの方法でゲートされています:

1. ファイルパスは、リポジトリごとのマニフェストに照らして編集可能と解決される必要があります。一致しないパスはデフォルトで**編集不可**となります: このチェックはフェイルクローズです。
2. フォークを起点とするプルリクエストは拒否されます。また、フォークかどうかのシグナルが欠落している場合も、安全であると仮定せずに拒否されます。
3. 提案された修正は、取得済みのログ内の失敗シグネチャと一致する必要があり、一致しない場合は抑制されます。

## 運用上のセキュリティ

- ランナーイメージは**毎週再ビルド**されるため、OSのセキュリティパッチが計画的にフリートへ適用されます。
- ランナーのインスタンスロールは最小権限であり、AWSマネージドポリシーのアカウント全体にわたるParameter Storeへのアクセスを、Latchkey自身のランナーパスに絞り込む明示的な拒否を含みます。
- 変更を伴うすべての管理操作は、操作者、アクション、対象、パラメーター、結果とともに**監査ログ**に記録されます。
- ダッシュボードとAPIへのアクセスは、APIゲートウェイのJWTオーソライザーによって認証されます。workspaceのロールはOwner、Admin、Memberであり、オーナーは削除や降格から保護されています。

## データの保持

Latchkeyが保存するものと、その保持期間です。

| データ | 保持期間 |
| --- | --- |
| ランナー上のあなたのソースコード | ジョブ終了時にインスタンスとともに破棄されます |
| ビルドキャッシュのオブジェクト | 最終書き込みから14日 |
| 診断のために取得された失敗ステップの出力 | 90日 |
| セルフヒール試行の記録 | 90日 |
| 修正案の記録 | 365日 |
| ダッシュボードを支える実行およびジョブのメタデータ | リポジトリが有効なままである間、保持されます |

リポジトリを無効化すると、そのリポジトリの収集が停止します。workspaceを削除すると、そのデータが削除されます。組織として特定の保持期間が必要な場合は、お問い合わせいただければご相談に応じます。

## 脆弱性の報告

**security@latchkey.dev** までメールでご連絡ください。セキュリティ報告のためにパブリックなGitHub issueを作成しないでください。

### ジョブの後、ソースコードはランナー上に残りますか?

いいえ。ジョブが終了すると、インスタンスとその暗号化されたルートボリュームは破棄されます。明示的に利用を選択したビルドキャッシュのエントリ(14日後に失効します)を除き、チェックアウトしたものは何も残りません。

### 他の顧客のジョブが自分のジョブを見ることはできますか?

いいえ。各ジョブは独自の仮想マシン上で実行され、単一のworkspaceに登録され、その1つのジョブの後に破棄されます。ランナーにはインバウンドのネットワークアクセスがありません。

### セルフヒーリングエージェントは、自分のビルド上で任意のコマンドを実行できますか?

いいえ。特権的なアクションはフィルタリングではなく許可リスト方式です: 許可されたsudoパターンは1つのみ、パッケージリストは固定、コード注入を可能にする環境変数は拒否、AIツールのステージはデフォルト拒否のサンドボックスです。エージェントが何らかの理由で失敗した場合、あなたのコマンドはエージェントがない場合とまったく同じように実行されます。

### 自分のビルドのシークレットはLatchkeyから見えますか?

いいえ。あなたのGitHub Actionsのシークレットは、GitHubホストランナーの場合とまったく同じように、実行時にGitHubによってジョブへ直接注入されます。Latchkeyがその値を要求したり保存したりすることは決してありません。GitHubは、ログ出力が当社に届く前に、登録済みのシークレットをマスクします。

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
