workspace 隔離なしの self-hosted runner は丸裸 — CI/CD セキュリティの落とし穴

2026 基準:one job, one workspace — いま業界標準

Cloud Mac AI Stack · L1  ·  2026.06.09  ·  約11分  ·  runbook · コピペ設定付き

self-hosted runner の CI/CD セキュリティ隔離と one job, one workspace 基準

多くのチームは Cloud Mac や Mac mini に self-hosted GitHub Runner を載せ、「キューなし、xcodebuild が動く」で十分だと思いがちです——キューと TCO の記事が扱うのはその層です。

本当の罠は次の層:共有 workspaceDerivedData、グローバル依存キャッシュ、CI と Agent 兼用の PAT——メンテナだけが workflow を触るなら数分短縮に過ぎません。fork PR、悪意ある post step、Claude Code / OpenHands による .github/workflows 自動編集が加わると、隔離なし self-hosted runner は丸裸:ジョブ A のシークレットをジョブ B が読めます。

2026 年の業界フロアもここに揃います:one job, one workspace——ジョブごと専用ディレクトリ、終了時クリーンアップ、token ローテーション。本記事は Cloud Mac AI Stack · L1 第3回(前提:① 実行エンジン · ② キューとトレードオフ):CI/CD セキュリティ罠の発生源とコピペ runbook。シリーズ目次は § L1 シリーズ

読む前に · L1 シリーズと Stack 入口

L0 基盤Cloud Mac を買うか借りるか · AI ワークステーションをクラウドへ

L1 シリーズ(順読推奨)① 実行エンジン② キューと TCO③ 本記事 · CI/CD セキュリティと one job, one workspace

同じ Stack でよく並ぶ(L3–L5):Claude Code ワークフロー · MCP セットアップ · Ollama 並列スケジューリング · OpenClaw パイプライン

比較:旧構成 vs 2026 基準 · 罠はどこに

Hosted macos-latest から self-hosted へ移行するチームの多くは、「キューなし」だけ継承L1 ②)し、Hosted の「ジョブごと新ディスク」を継承しません。下の表と図は、当時「便利」に見え、今は「丸裸」に見える箇所を示します。

必読 · 核心比較

昨日の便利 = 今日の露出 — 7行で一覧

旧来の一般的なやり方 2026 業界基準 罠の結果

観点 旧来の一般的なやり方(2024 前後) 2026 業界基準 罠の結果
ジョブ作業ディレクトリ 複数ジョブが _work を共有、run ディレクトリ未削除 one job, one workspace、ジョブ終了でクリア ジョブ B がジョブ A の .env、埋め込みスクリプトを読む
ビルドキャッシュ 共有 DerivedData / グローバル actions/cache key リポジトリスコープ cache key + 定期 prune キャッシュ汚染;fork PR がグローバルキャッシュをスキャン
認証情報 CI + Agent 兼用 PAT;secrets はローテするがディスク未消去 CI / MCP 別 token;30–90 日ローテ + workspace 消去 ディスク上の PAT コピーが有効なまま
署名素材 $HOME の一時 keychain、post 削除なし ジョブスコープ keychain、if: always() で破棄 次ジョブや悪意 step が証明書をエクスポート
workflow を編集する主体 デフォルト:メンテナ 2–3 名のみ メンテナ + fork PR + Agent による CI 編集 攻撃面が「人」から「半自律プロセス」へ拡大
Runner セグメント 1 台の Mac が全リポ・全 PR 本番 / ステージング / fork で別 label 低信頼 workflow が本番署名環境に触れる
Hosted vs self-hosted の誤解 「self-hosted = 自分のマシン、速さ最優先」 self-hosted = 自分でセキュリティ境界を引く(Hosted VM は自動隔離) self-hosted の方が安全と誤解;実際はより露出

旧方式を許容できた理由は? プライベートリポ、fork PR なし、日次 CI は内部ジョブのみ——共有ディスクは 3–8 分短縮に過ぎません。2026 に変えなければならない理由は? 同じ Cloud Mac で Agent、MCP、複数リポジョブが動く——旧習慣のどれかが「便利」をセキュリティインシデントに変える。

図 · 以前:共有 workspace の漏洩(ジョブ間)
  同一 self-hosted runner(同一 macOS ユーザー)

  Job A · nightly 署名
       │
       ├─► *.mobileprovision 展開、一時 keychain
       ├─► ~/Library/Developer/Xcode/DerivedData へ書き込み   ← 「速さ」と感じた
       └─► PAT を ~/.netrc へ(手抜きスクリプト)                ← 「便利」と感じた
                │
                │  未クリーン · _work とキャッシュ残留  Job B · 日中 fork PR「ドキュメント修正」
       │
       └─► post step が _work / DerivedData / .netrc をスキャン  ← 罠:Job A 残留を読む

       結果:self-hosted runner 丸裸(Hosted macos-latest はジョブごと VM 全体を破棄)
図 · 現在:one job, one workspace(2026 基準)
  Job A  ──►  workspace A (_work/.../run-id)  ──►  if: always() クリーンアップ  ──►  ✓
  Job B  ──►  workspace B(新規ディレクトリ)    ──►  if: always() クリーンアップ  ──►  ✓
                │
                ├─ 機密キャッシュ:repo 接頭辞付き cache key、または cron prune
                ├─ PAT:CI と MCP で別 token、定期ローテーション
                └─ 高信頼 / 低信頼ジョブ:別 runner label(必要なら 2 台目の Cloud Mac)

       結果:監査可能な Fact 層;「Claude Code が Diff、Runner が Fact」を生む」と整合

一覧 · 以前 vs 現在

  • 旧罠:self-hosted を「より速いサーバー」とみなし、共有可能なものはすべて共有
  • 2026 基準:self-hosted を「自分で消毒する実行環境」とみなす
  • 最大の誤解macos-latest から移行し、CPU だけ移し隔離は移さない

丸裸の起点:self-hosted runner でよくある CI/CD セキュリティ罠3つ

Hosted macos-latest はジョブごとに新ディスクに近いため、初めて self-host するチームは「自分のマシン、便利優先」と誤解しがちです。self-hosted の本当のコストはセキュリティ境界を自分で引くこと——L1 第2回 の「キューなし」以外の隠れコストです。

以下の3つの誤設定は Cloud Mac 導入現場で繰り返し見られます。2つ当たれば、すぐ one job, one workspace を採用すべきです。

2026 が one job, one workspace を全面推進する理由

GitHub が新ルールを出したからではなく、攻撃面が拡大したからです:workflow はメンテナ、fork PR、サプライチェーンスクリプト、同一ホストの AI Agent から実行されます。「ディスク共有で速く」は 2026 では成立しません。

1. ジョブ間汚染:ジョブ A のシークレットをジョブ B が読める

self-hosted の _work はジョブ間で自動消毒されません。ジョブ A で展開した署名証明書、スクリプトが書いた .envpost step の .netrc——ジョブ B は相対パスやシンボリックリンクで読めます。2026 では Claude CodeOpenHands が同一マシンで .github/workflows を編集——diff だけでなくCI 変更時にディスクに何が残るかも監査が必要です。

2. グローバルキャッシュ汚染:DerivedData / npm キャッシュが共有バックドアに

~/Library/Developer/Xcode/DerivedData や広い actions/cache key の共有は閉じた内部リポでは動くこともあります。fork PR が来ると、悪意ある post step がグローバルキャッシュをスキャン——Hosted VM は破棄、クリーンアップなし self-hosted は持続的攻撃面。典型的な iOS 失敗:nightly 署名ジョブと日中「ドキュメント修正」PR が同一 runner。

3. ディスク上の長期 PAT:UI で secrets をローテしても足りない

多くの Cloud Mac Stack で 1 つの GitHub PAT が MCP リポアクセスと Runner アーティファクト push の両方に使われます。Agent 側で漏洩すれば CI も落ちます。GitHub UI で secrets をローテして workspace を消さないのは、錠を変えてディスク上のコピーを残すのと同じです。

Stack の役割 · Fact 層は丸裸で走れない

シリーズスローガン(L1 冒頭より):Claude Code が Diff を生み、GitHub Runner が Fact を生む。 きれいな diff でも Fact が汚れた workspace で緑なら意味がありません。L4 MCP 最小権限 が Agent token、L1 本記事がディスクとジョブ境界——両方必要、どちらか一方では不十分。

3
よくある CI/CD セキュリティ罠
1
ジョブレベル workspace 境界
2026
基準年

モデルを揃える:GitHub Actions が runner に残すもの

多くの人は「workspace」を checkout した git ツリーと同一視しますが、それは一部に過ぎません。macOS ジョブ後、ディスクにはまだ残る可能性があります:

パス / オブジェクト 典型的な内容 未クリーン時のリスク
_work/<repo>/<run-id>/ checkout、ビルド成果物、テスト出力 次ジョブが生成物やソース外の悪意スクリプトを読む
~/.npm~/Library/Caches 依存関係・ツールキャッシュ キャッシュ汚染、リポ間依存関係の混乱
DerivedData.swiftpm Xcode / Swift ビルドキャッシュ シンボル漏洩、古い署名設定の埋め込み
一時 keychain、*.mobileprovision 署名素材 高リスク:次ジョブや悪意 step が証明書をエクスポート
env 注入ファイル、.netrc CI スクリプトが書いた認証情報 平文 PAT が残存

Hosted runner はジョブ終了時にディスク全体を破棄;self-hosted はしない。Agent Stack ではさらに悪化:Claude Code セッションファイル、Ollama 重み、Runner _work が同一ユーザーホームを共有(同機スケジューリングは L2 並列スケジューリング)——one job, one workspace とは:開始時に Agent や他ジョブで汚染済みと仮定し、終了時にジョブをまたぐ層は残さない(通常はゼロ)。

業界基準の実装:one job, one workspace の意味

4 層、易しい順:

  1. ディレクトリ隔離:各ジョブは専用 RUNNER_TEMP / run ディレクトリ;/tmp/shared やリポ外「チーム共有」フォルダへの書き込み禁止。
  2. プロセス境界:1 runner プロセスがジョブを直列実行しても、ジョブ間で未クリーンのグローバル状態(例:~/.zshrc に export した API key)を共有してはならない
  3. 認証境界:署名用一時 keychain はジョブ post で削除;secrets は env のみ、ディスク不可——ディスク必須なら run ディレクトリ内でジョブと共に削除。
  4. 運用境界:高リスクリポは信頼 workflow 専用 runner(label 分離)、fork PR runner と物理分離——Cloud Mac では通常2 台目ノード、1 台でクリーンアップスクリプトが永遠に正しいと賭けない。

ephemeral runner との関係

GitHub Enterprise の ephemeral self-hosted runner各ジョブ後にプロセス終了し新インスタンス——one job, one workspace を自動化。常駐 runner(Cloud Mac で一般的)ならスクリプト + workflow 規約で同等効果を目指す。

token ローテーション:ディレクトリ消去だけでは足りない

workspace クリーンアップはファイル残留を解決;token ローテーションはコピーが漏洩しても期限切れにする。最低3種類をローテ:

  • Runner 登録 token:runner を削除・再登録(または組織ポリシーでローテ)、旧マシンの登録悪用を防ぐ。
  • CI 用 GitHub PAT / App:最小スコープ(リポ読取 vs packages 書込)、MCP PAT ポリシーと分離——Agent + CI 兼用 token を避ける。
  • Apple 署名とサードパーティ API key:短期認証またはジョブごと secrets 注入;runner ホーム plist への書き込み禁止。

銀の弾丸の頻度はない:プライベートリポ、fork PR なし、workflow はメンテナのみ編集——90 日で足りることが多い;オープン contributor や Agent 自動 workflow 提出なら 30 日に短縮、インシデント後は即座に runner 削除・再登録。

Runbook:one job, one workspace を workflow に組み込む

既存パイプラインに以下を貼り付け、基準を監査可能に。macOS self-hosted runner、デフォルト _work レイアウト想定。同一 Cloud MacClaude Code も動かすなら Runner は専用 macOS ユーザーを推奨。MCP 配線:セットアップガイド

workflow · ジョブ終了クリーンアップ(例)
# .github/workflows/ios-ci.yml スニペット
jobs:
  build:
    runs-on: [self-hosted, macos, cloud-mac]
    steps:
      - uses: actions/checkout@v4
      - name: Build iOS
        run: xcodebuild -scheme App -destination 'platform=iOS Simulator,name=iPhone 16' build
      - name: Scrub workspace (always)
        if: always()
        run: |
          rm -rf "$RUNNER_TEMP"/*
          rm -rf "$GITHUB_WORKSPACE"/build
          security delete-keychain ci_temp.keychain-db 2>/dev/null || true
#!/usr/bin/env bash
# /usr/local/bin/runner-prune-work.sh · 毎日 03:00 cron
set -euo pipefail
WORK_ROOT="${HOME}/actions-runner/_work"
# 48h 超の run ディレクトリを削除(mtime)
find "$WORK_ROOT" -mindepth 3 -maxdepth 3 -type d -mtime +2 -exec rm -rf {} +
# 任意:7 日超の DerivedData エントリを prune
find ~/Library/Developer/Xcode/DerivedData -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + 2>/dev/null || true

検証:連続2ジョブで ls -la "$GITHUB_WORKSPACE/.." と主要キャッシュパスを出力;2番目のジョブが1番目のマーカーファイルを見られないこと(例:post で touch /tmp/job-marker-$GITHUB_RUN_ID し残留確認)。

よくある誤設定

actions/cache未署名サードパーティバイナリをグローバル cache key でキャッシュしリポスコープなし——runner 上にジョブ・リポ横断の共有層を作る。cache key とブランチを絞るか、キャッシュディレクトリを prune スクリプトに含める。

Cloud Mac 同居:Agent + Runner セキュリティ境界

典型的な Cloud Mac AI StackClaude Code(Diff)+ Runner(Fact)+ 任意 Ollama が同一ホスト。キューと git pull を節約するが、消せないグローバルディレクトリを共有すると丸裸 CI が Agent セッションを巻き込む

  • ユーザー分離:Runner は runner ユーザー、Agent は開発者ユーザー;ANTHROPIC_API_KEY と署名鍵を同一 ~/.zshrc に混在させない。
  • Agent workspace ≠ CI workspace:Claude Code プロジェクト dir を Runner _work に向けない;Agent パッチは git 経由、CI キャッシュツリーへの直接書き込み禁止。
  • メモリ競合 ≠ ディスク共有:Ollama vs Runner メモリは 並列スケジューリング;高 Swap でも DerivedData 共有の言い訳にしない。
  • 出口 IP と label:内部ステージングへ届く runner は fork PR も受けない;Agent 提出 workflow はまず低権限 label、本番 runner は人間昇格後。

後回しできる場合(今すぐ必要な場合)

シナリオ 後回し可? メモ
プライベート monorepo、メンテナ 2–3 名のみ、fork PR なし 短期は可 月次 manual prune + 四半期 token ローテは推奨
オープンソースが外部 PR で Actions 実行 不可 専用 runner または Hosted macOS へ戻す
Claude Code / OpenHands / MCP がリポに書き込み 不可 デフォルト one job, one workspace;機密キャッシュ共有禁止
署名証明書を CI で復号 不可 ジョブスコープ keychain + post 削除必須

本番前チェックリスト(印刷可)

  • 全ジョブに if: always() クリーンアップ step または同等ホスト prune
  • 一時 keychain / 署名ファイルを永続 $HOME パスに置かない
  • 高リスクリポと低信頼 workflow は別 runner label
  • Runner 登録 token と CI PAT にローテカレンダー(30–90 日推奨)
  • 新 contributor の初 workflow PR は人間レビュー、本番 runner 直撃禁止
  • Agent と CI は別 PAT / App——MCP と Runner 兼用 token 禁止
  • L1 第2回と照合:② は「遅い」、③ は「self-hosted が丸裸か」

L1 シリーズ · Stack 各層の接続

本記事は L1(Fact 層) セキュリティ線を締める:Runner がなぜ必要か → self-hosted は価値あるか → 2026 one job, one workspace 基準の着地。表順に読む;縦に L0、横に L3–L5。

トピック 状態
· 実行エンジンRunner が Cloud Mac AI Stack L1 である理由(Diff → Fact)公開済
· キューと TCOmacOS CI キュー時間 · self-hosted vs macos-latest公開済
· 本記事self-hosted runner セキュリティ · one job, one workspace 基準公開済
· OpenClaw パイプラインRunner が step 実行 · OpenClaw がトリガーとレシートを編成(L1 拡張)公開済

Stack 縦リンク(層ごと1入口):

L1 三部作後、Agent と CI が同一 Cloud Mac なら次は通常 L3 Diff 層の判断(Claude Code が従来 IDE を置き換える理由——vs Cursorワークステーション記事とは別役割)——その先は L6 エンドツーエンド地図(予定)。

FAQ

one job, one workspace で CI は遅くなる?
コールドスタートは遅くなる——共有ディスクの理由です。2026 のバランス:機密成果物とジョブ dir だけ削除;再生可能キャッシュは repo 接頭辞付き key、prune しないグローバル DerivedData ではない。

self-hosted runner が「丸裸」とはいつ?
複数ジョブ/リポが署名素材、.netrc、広いグローバルキャッシュを同一ユーザーホームで共有し if: always() クリーンアップなし——特に fork PR や Agent 編集 workflow 時。

MCP 最小権限だけで runner ディスク共有は直る?
いいえ。MCP 最小権限 は Agent ツール呼び出し;Runner はディスク上のファイルを管轄。悪意 fork workflow は MCP を迂回し _work 残留をスキャンできる。

GitHub にワンクリック切替はある?
Hosted runner は近似「ワンクリック」;self-hosted は workflow post、ホスト cron、任意 ephemeral の組み合わせ。actions/checkout 1 フラグでは境界全体を置き換えられない。

OpenClaw 編成層との分担は?
OpenClaw はトリガー順序とレシート;Runner は step 実行。隔離は workflow とホストに書く——OpenClaw がディスクを消すと仮定しない(L1 ④ · OpenClaw パイプライン)。

L1 シリーズはどこから?
推奨:① 実行エンジン② キュー③ 本記事。全表は § L1 シリーズ

L1 三部作完了 · 次は Diff 層

Fact 層を固めたら Claude Code へ

L1 は CI がどこで Fact を走らせるか。次は通常 L3:Cloud Mac 上の Claude Code ワークフローと IDE 置換ロジック(vs Cursor 深掘りとは別)。

Claude Code ワークフローを読む
Cloud Mac M4 プランを見る