コンテンツにスキップ

サンドボックス 学習メモ(仕組み解説 + 用語集)

README.md が「何を・どの順で写経するか」なら、こちらは「仕組みの理解と用語の整理」。混乱の原因はほぼ用語のすれ違い(特に "sandbox" という語が複数の意味で使われる)なので、まず用語集で立て直せるようにする。


1. 一番大事な分かれ目:「sandbox」は 1 語で 3 つの別物を指す

sandbox という言葉が出てきたら、まずどの意味かを確認する。これを混同すると議論が噛み合わない。

意味 何のための隔離か 本フォルダの該当 典型例
セキュリティ sandbox untrusted コードの被害を封じる ex01〜07 の主題 Seatbelt / Docker / microVM
決定性 sandbox 再生(replay)で同じ結果を保証する ex08 で対比 Temporal の workflow sandbox
再現性 sandbox 評価をhermetic に再現する (言及のみ) SWE-bench の Docker 3層イメージ

特に ex08 は、Temporal の「決定性 sandbox」(時刻・乱数・I/O を禁じてワークフローを決定的に保つ)の中で、untrusted コード実行を「セキュリティ sandbox」(Seatbelt/Docker)に押し込む、という二重構造。層が違うので両方同時に成立する。


2. 信頼境界(trust boundary)— sandbox を理解する唯一の軸

sandbox の設計はすべて「信頼境界をどこに引くか」に還元できる。境界の内側(信頼しない)と外側(信頼する)を分け、内側から外側への操作を仲介・検査・遮断する。

flowchart LR
    subgraph Untrusted["信頼境界の内側(untrusted)"]
        Code["LLM 生成コード"]
    end
    subgraph Trusted["信頼境界の外側(trusted)"]
        FS["ファイルシステム"]
        Net["ネットワーク"]
        Env["環境変数・秘密"]
    end
    Code -.遮断/検査.-> FS
    Code -.遮断/検査.-> Net
    Code -.遮断/検査.-> Env

境界をどの層に置くかで技術が決まる:

  • 言語ランタイム内(WASM linear memory / V8 isolate)
  • カーネルの強制機構(seccomp / Landlock / Seatbelt)← OS は共有
  • namespace で別世界(Docker)← カーネルは共有
  • ハードウェア仮想化(microVM)← カーネルも別

「カーネルを共有するか」が最大の分水嶺。共有する層(言語〜コンテナ)はカーネル脆弱性 1 つで全境界が崩れる(Dirty COW, runc CVE-2019-5736)。共有しない層(gVisor の Sentry / microVM)はそこが原理的に強い。


3. 各 ex が「何を遮断できて何を遮断できないか」の地図

プローブ(_probes.py)の 6 探査が、層を上げるごとにどう潰れていくか。これがこのフォルダの背骨

探査 ex01 なし ex02 rlimit ex03 Seatbelt ex04 Docker(lock) ex06 microVM
秘密ファイル読取 ✅漏れる ✅漏れる 🚫遮断 🚫(存在せず) 🚫(存在せず)
~/.ssh 列挙 ✅漏れる ✅漏れる 🚫遮断 🚫(存在せず) 🚫(存在せず)
環境変数スキャン ✅漏れる ✅漏れる △env次第 △渡した分 △渡した分
外部ネットワーク ✅漏れる ✅漏れる 🚫遮断 🚫(none) △既定有効
ホームへ書込 ✅漏れる ✅漏れる 🚫遮断 🚫(ro) 🚫(別FS)
CPU/メモリ爆弾 ✅暴走 🚫CPUは殺せる (対象外) 🚫cgroups 🚫VM内

読み取れること:

  • rlimit は「壊す」しか止められない(可用性専用)。「盗む」(機密)は 1 つも止まらない。RLIMIT_ASmacOS では効かない(移植性が低い)
  • Seatbelt は FS・network の能力を剥がすが、env は剥がせない(親から継承済み)。だから「起動前に env を scrub」とセットで使う
  • Docker は namespace で別世界を作るので「~/.ssh が存在しない」。env は明示的に渡した分だけ。--network none で外部送信を構造的に切断
  • microVM は実行を別マシンに外出し。ホストの秘密も env も原理的に到達不能。ただし egress はデフォルト有効で、周辺(DNS/メタデータ)が穴になりうる

4. macOS Seatbelt の .sb プロファイルの読み方(ex03 の肝)

4.0 Seatbelt とは何か(TrustedBSD MAC 基盤)

一言で: Seatbelt は「このプロセスは何ができないか」をカーネルに宣言して、能力そのものを root でも戻せない形で剥がす仕組み。ファイルの所有権・パーミッション(rwx)とは別レイヤーで網をかける。

MAC vs DAC が核心:

DAC(普通の Unix パーミッション) MAC(Seatbelt が乗る層)
正式名 Discretionary Access Control(任意) Mandatory Access Control(強制)
誰が決める ファイルの持ち主が許可を配れる ポリシーが一律に決める。持ち主でも緩められない
帰結 自分のファイルなら自分のプロセスは当然読める (deny file-read* (subpath "~/.ssh")) と書けば、自分のユーザー権限のプロセスでも読めなくなる

「自分の権限で動くプロセスを、自分自身から守る」のが MAC の役割。これが ex02 rlimit(量の制限)では届かなかった「能力の剥奪」層。

3 つの言葉の関係(質問された「TrustedBSD MAC 基盤」の正体):

  • TrustedBSD = カーネル(XNU)の syscall 入口に MAC フックを差し込む土台。元は FreeBSD のセキュリティ拡張で、Apple が macOS に取り込んだ
  • Seatbelt(別名 Sandbox / sandboxd) = その検問所が参照するポリシーエンジン
  • .sb プロファイル = あなたが書くルールブック本体(下の S 式)
あなたのコード(probe.py)
   ↓ システムコール(open, connect, ...)
┌──────────────────────────────┐
│ XNU カーネル                  │
│  ┌────────────────────────┐  │
│  │ TrustedBSD MAC フック   │ ← syscall の入口に置いた検問所(土台)
│  │   ↓ 問い合わせ          │  │
│  │ Seatbelt ポリシー       │ ← .sb の (deny network*) 等を解釈(エンジン)
│  └────────────────────────┘  │
└──────────────────────────────┘
   ↓ allow なら通す / deny なら EPERM を返す

検問所は syscall を見張るので FS/network は縛れるが、env var は syscall 経由でなくメモリに既に乗っているため剥がせない(→ §3・ex03 [A]/[B] の対比)。

4.1 .sb の読み方

.sb は Scheme 風の S 式 DSL。default deny → 必要分だけ allow が定石。denyallow より強い。

(version 1)
(deny default)                              ; ① まず全部拒否
(allow process-exec*)                       ; ② python の起動は許す
(allow file-read*)                          ; ③ 読み取りは原則許可するが…
(deny file-read* (subpath "/Users/me/.ssh")); ④ 秘密だけ穴を塞ぐ(deny が勝つ)
(allow file-write* (subpath "/private/tmp")); ⑤ 書き込みは一時領域だけ
(deny network*)                             ; ⑥ ネットワーク全面禁止(trifecta の外部送信辺を切断)
  • literal(完全一致)/ subpath(配下全部)/ regex でパス指定
  • file-read** は「file-read-data file-read-metadata …をまとめて」の意
  • 実行は sandbox-exec -f profile.sb <command>
  • deprecated だが現役: Claude Code・OpenAI Codex CLI・Chrome・Bazel が macOS で実際に使う。公式代替が無いため各社が使い続けている。仕様は Apple 非公開で、/System/Library/Sandbox/Profiles/*.sb と Chromium のプロファイルがデファクトの参考資料

5. Docker の隔離の仕組み(ex04 の肝)

5.0 一言で:Seatbelt は「能力を剥がす」、Docker は「別世界に隔離する」

ex03 と ex04 はアプローチが逆。これを掴むのが ex04 の本質。

Seatbelt(ex03) Docker(ex04)
やり方 ファイルは在るが読めなくする(能力剥奪) ファイルがそもそも存在しない世界を作る(隔離)
~/.ssh の止まり方 PermissionError(在るが拒否) FileNotFoundError(無い)
比喩 金庫に鍵をかける そもそも別の家に住まわせる

ex04 の出力で ~/.sshex03 は PermissionError・ex04 は FileNotFoundError なのが、この違いの動かぬ証拠。だから Docker はデフォルトでも FS 隔離が効く(コンテナにホームが無い)。Seatbelt は .sb を書かないと素通りだった。

5.1 Docker を支える3つのカーネル機能

Docker は魔法ではなく、Linux カーネルの3機能の組み合わせ。それぞれが「何を」縛るかが違う。

機能 何を縛るか ex04 での現れ 一言
namespaces 「見える資源」を別名前空間に ~/.ssh が存在しない、/ がコンテナ専用 別世界(mount/PID/net/user…)
cgroups 資源の「量」(CPU/メモリ/PID数) --memory 256m --cpus 0.5 --pids-limit 128 量の上限(ex02 rlimit のコンテナ版)
capabilities root 権限を細かい能力に分割 --cap-drop ALL 能力の剥奪(rawソケット等を落とす)
  • namespaces が隔離の主役。mount ns でファイルツリーを、net ns でネットワークを、PID ns でプロセス番号を、user ns で UID を、それぞれ「コンテナ専用の別世界」に差し替える。~/.sshFileNotFoundError なのは mount ns の効果
  • cgroups は ex02 で見た rlimit の進化版。プロセス単位で量を縛る(メモリ爆弾・fork爆弾を止める)
  • capabilities は「root を all-or-nothing でなく能力の束に分解」する仕組み。cap-drop ALL で全部落とし、必要な能力だけ足す(default deny の発想)

5.2 「既定の docker run は緩い」が教材の肝

ex04 は 2回 実行して対比する。既定(docker run だけ)は 4/6 成功=隔離されてるつもりで穴だらけ:

既定: コンテナ内 root(uid=0)/ ネットワーク開放 / ルートFS書込可
  • コンテナ内 root-u 未指定だと uid=0。ホスト root とは違うが、脱出 CVE と組むと危険
  • ネットワーク開放 — egress が生きている=lethal trifecta の「外部送信」辺が繋がったまま
  • ルートFS書込可/ に書ける

「Docker 使ってるから安全」ではなく、ロックダウンのフラグを盛って初めて安全。これが §3 の「ex04 Docker(lock)」列の意味。

5.3 ロックダウンのフラグ早見表(どの旗が何を潰すか)

docker run --rm \
  --network none \            # ① egress 遮断 → trifecta の外部送信辺を切断
  --read-only --tmpfs /tmp \  # ② ルートFS 読み取り専用(書けるのは /tmp だけ)
  --memory 256m --cpus 0.5 \  # ③ cgroups で量の上限(暴走を止める)
  --pids-limit 128 \          # ④ fork 爆弾対策
  --cap-drop ALL \            # ⑤ Linux capabilities 全剥奪
  --security-opt no-new-privileges \  # ⑥ setuid 等での権限昇格を封じる
  -u 1000:1000 \              # ⑦ 非 root 実行(uid を user ns で 1000 に)
  python:3.12-slim ...
フラグ 潰す探査 対応する3機能
--network none 外部ネットワーク net namespace
--read-only ルートFS書込 mount namespace
--memory / --cpus / --pids-limit CPU/メモリ/fork 爆弾 cgroups
--cap-drop ALL / no-new-privileges 権限昇格経路 capabilities
-u 1000:1000 コンテナ内 root user namespace

これで networkwrite_root が遮断、whoami も非 root に落ちて 2/6 まで下がる。

5.4 残る限界:カーネルは共有している

ここが ex05/ex06 への伏線。Docker は namespace で世界を分けるが、カーネルは1つをホストと共有する

  • だからカーネル脆弱性1個で全コンテナの境界が崩れる(runc CVE-2019-5736 = コンテナから runc バイナリを上書きしてホスト権限を取る脱出)
  • env も「明示的に渡した分だけ」漏れる(ex04 で FAKE_API_KEY が見えるのは、親から渡しているから)

判定: 自分のコード/単一テナントなら Docker ロックダウンで十分。他人の untrusted を同居で大量に回すならカーネル非共有(gVisor / microVM)が要る(→ §6 ex05 の逆算、§9 用語集の「カーネル非共有」行、ex06 E2B)。

5.5 コンテナ脱出の仕組みと seccomp(syscall = 共有カーネルへの攻撃面)

「Docker から syscall で他システムに影響できる?」への正確な答え: 普通の syscall は namespace が止める。危ないのは syscall を入口に共有カーネルのバグを突く脱出。

普通の syscall は届かない:

コンテナA: open("/etc/passwd") ─syscall→ カーネル「Aの mount ns の中で解決」← Aの世界に閉じる
namespace が「見える世界」の中で解決するので、隣のコンテナやホストには普通は届かない。

危ないのは脱出経路:

コンテナA ─┐
コンテナB ─┼─ syscall ─→ [共有カーネル(1個)] ← ここにバグがあると…
コンテナC ─┘
syscall が突き刺さる先のカーネルは全コンテナで1個の共有物。ある syscall(や特殊引数)でカーネルバグを踏むと、攻撃者コードがカーネル権限(ring 0)で動く=脱出成立 → ホスト権限 → 他の全コンテナもホストも丸見え。実例: runc CVE-2019-5736 / Dirty COW (CVE-2016-5195)。

seccomp = 打てる syscall を減らして攻撃面を削る:

capabilities (--cap-drop) seccomp
root 権限を能力の束に分割して剥奪 プロセスが打てる syscall を allowlist で制限(seccomp-bpf)
粒度 粗い(部署ごと剥奪) 細かい(syscall 1個単位)
Docker 既定 一部 cap を保持 危険な約44 syscall を無効化
  • 触れる syscall を減らす=そこにあるカーネルバグに到達できない(attack surface を縮める)
  • ただしコンテナが動くのに必要な syscall は開ける → 残った所にバグがあれば破られうる(確率を下げるだけ・ゼロにはできない・一方向で昇格不可)
  • microVM はこの経路を構造的に断つ: syscall の刺さる先を「VM専用ゲストカーネル」にするので、脱出してもそのVM止まり(→ §9「カーネル非共有」)

5.6 「重ね合わせ」で危険になる — 2種類

各々は無害なのに重なると危険、という構図が sandbox には2つある。防御は全部を完璧にではなく「重なりを1辺崩す」

重ね合わせ 成立条件(全部そろうと危険) 崩し方
lethal trifecta(データ窃取) ①機密を読める + ②untrusted を取り込む + ③外部送信できる FS隔離で①、network遮断で③を断つ(§9 trifecta 表)
コンテナ脱出(権限奪取) untrusted コード実行 + 到達できる syscall + カーネルバグ seccomp で syscall を削る/microVM でカーネルを分ける

どちらも「個々が正しくても合成で壊れる」。trifecta は §9 用語集に詳細。injection(②)は確率でしか防げないので、①③を構造で断つのが sandbox の本丸。


6. 脅威モデルから層を逆算する(ex05 の設計練習)

過剰隔離はコスト、過少隔離は事故。最小コストの境界を選ぶのが設計。判定の骨子:

untrusted コードを実行する?      → YES なら最低でも OS sandbox(層3)
別テナントと同居する?             → YES ならカーネル非共有(層6 microVM)が必要
ネイティブ依存(numpy 等)がある?  → 言語/WASM 制限(層1-2)は不可、コンテナ以上
外部ネットワークは業務上必要?      → 不要なら network 全面 deny で trifecta を断つ
                                  → 必要ならドメイン allowlist プロキシを併設(srt 方式)

例: - 社内データ分析ボット(自分専用) → 層3 OS sandbox(Seatbelt)で足りる。microVM は過剰 - SaaS のコード実行 API(他人のコード) → 層6 microVM(E2B)が要る(マルチテナント) - ブラウザ内の軽量 JS → ネイティブ不要・マルチテナント → V8 isolate / gVisor


7. 本番運用の隔離方針(untrusted コードを production で回す)

ex05 が「どの層を選ぶか」なら、こちらは「選んだ層を本番でどう運用するか」。脅威モデルで層を決めた後の操作方針。

7.0 大原則:単なる別プロセスでは不十分、別コンテナ(以上)

「隔離 = 別プロセスで起動すればいい?」への答えは No。効くのはプロセス分離ではなく namespace 隔離。

やり方 隔離 untrusted を本番で?
同一プロセス exec(code) ゼロ(ex01) ❌ アプリ本体が乗っ取られる
別プロセス subprocess ほぼゼロ(ex02) ❌ 同じユーザー権限で全部できる
別コンテナ/VM 構造(ex04/06) ✅ 本番の最低ライン

コンテナは結果的に別プロセスだが、守っているのは namespace。「別プロセスにする」で止めない。

7.1 複数コンテナの隔離境界(何が分かれ・何が共有か)

コンテナを複数立てたとき、分かれるものと共有されるものを混同しない。

観点 複数コンテナでどうなるか
FS / プロセス 互いに見えない(mount/PID ns が別)
ネットワーク 既定では繋がる(同じ docker0 ブリッジ)。要 --network 分離 or none
資源 cgroups で各自上限。ただし I/O 等で騒がしい隣人は残る
カーネル 全員で1個を共有 ← 脱出 CVE 1個で全コンテナ巻き添え

→ 「コンテナで隔離したから他テナントと混ざらない」は誤り。ネットワークは既定で地続き、カーネルは常に共有。

7.2 本番の2つの設計問い

① 粒度(誰ごとに分けるか)= 信頼境界をまたぐ単位で必ず分ける

1リクエスト / 1タスク / 1テナント ごとに 1サンドボックス
   ↑ 1コンテナを複数テナントで共有しない(同一コンテナ内は隔離ゼロ)

各社実装もこれ: Managed Agents=セッションごとにコンテナ、E2B=セッションごとに microVM、Code Interpreter=会話ごと。

② ライフサイクル = 使い捨て(ephemeral)が基本

タスク到着 → サンドボックス起動 → 実行 → 結果回収 → 破棄 → 次は新品
  • テナントをまたいで使い回さない — 前テナントが /tmp・メモリ・env に残したものが次に漏れる(状態リーク)
  • 同じテナントの連続タスクなら使い回し可。境界はテナント単位

7.3 速度はウォームプールで吸収

ephemeral の代償=コールドスタート。本番は空のサンドボックスを数個温めて割り当て→使ったら破棄して補充。E2B/Firecracker の「125ms・<5MiB」は使い捨てを高速に量産するための数字。

7.4 ワーカーをアプリ本体から分離

untrusted をアプリ本体のホストで走らせない。別ホスト/プールに隔離し、脱出されてもアプリ DB に届かない距離を置く。

[APIサーバ(信頼)] ──ジョブ投入──> [サンドボックスワーカー群(隔離専用)]
                                    ├ microVM/コンテナ(使い捨て)
                                    └ egress プロキシ(宛先 allowlist)

Managed Agents の self-hosted sandbox(自社 VPC のワーカーで tool 実行)や E2B の専用ワーカー群がこの構図。

7.5 判定(1問で)

「このコードと次のコードは同じ信頼境界か?」→ No なら新品のサンドボックス。 同じテナントの連続作業だけ使い回す。マルチテナントなら microVM(カーネル非共有)+ ephemeral + ワーカー分離。


8. Temporal × sandbox の責務分離(ex08)

sequenceDiagram
    participant WF as Workflow(決定性sandbox内)
    participant Gen as generate activity
    participant Run as run_in_sandbox activity
    participant Sbx as security sandbox

    Note over WF: 時刻/乱数/IO を直に呼ばない(決定的)
    WF->>Gen: execute_activity(リトライ付き)
    Gen->>Gen: LLM 呼び出し(非決定的・副作用OK)
    Gen-->>WF: 生成コード
    WF->>Run: execute_activity(リトライ付き)
    Run->>Sbx: Seatbelt/Docker で隔離実行
    Sbx-->>Run: 計算は通り注入は遮断
    Run-->>WF: 結果
  • ワークフロー本体は純粋なオーケストレーション(Temporal の決定性 sandbox 内)
  • 副作用(LLM 呼び出し)と untrusted コード実行は activity に押し込む(決定性 sandbox の外)
  • その activity の中で security sandbox が被害を封じる
  • 役割分担: リトライ/タイムアウト/再開 = Temporal、被害の封じ込め = sandbox

9. 用語集(混同しやすい同系語を対比で)

隔離技術の階層

用語 一言で カーネル 具体例(どのexの何)
eval / exec 隔離ゼロ。直接実行 共有 ex01。6/6 探査成功
rlimit (setrlimit) 資源の「量」上限。可用性専用 共有 ex02。CPU は殺せるが機密は守れない
seccomp-bpf syscall を allowlist で検査。Linux 3.5〜・一方向 共有 Docker のデフォルト(約44 syscall 無効化)
Landlock 非特権プロセスが自分を縛る LSM。Linux 5.13〜 共有 (Linux の OS sandbox)
Seatbelt (sandbox-exec) macOS の MAC ベース sandbox。.sb プロファイル 共有 ex03。FS/network を剥奪
bubblewrap (bwrap) Linux の非特権 sandbox ランチャ 共有 Claude Code の Linux 実装
namespaces プロセスから見える資源を別名前空間に 共有 ex04。~/.ssh が「存在しない」
cgroups 資源量の制限(CPU/メモリ) 共有 ex04 の --memory --cpus
gVisor Go 製ユーザ空間カーネル Sentry が syscall を代行 非共有 OpenAI Code Interpreter / Modal
Firecracker Rust 製 microVM。125ms/<5MiB 非共有 ex06 E2B / AWS / Vercel
libkrun ライブラリ型 VMM。macOS でも動く 非共有 microsandbox

信頼境界とプロセス

用語 意味 判定の一文
trust boundary(信頼境界) 信頼しない内側と信頼する外側の境目 「この線を越える操作を仲介・検査・遮断するか?」
untrusted code 攻撃者が中身を決めうるコード 「LLM 生成 / 第三者入力 を含むか? → untrusted」
capability(能力) プロセスができること(FS 読み/network 等) 「この能力を持つか?を集合で評価する」(guardrails ex06)
sandbox escape 境界を破ってホストに到達すること 「カーネル/ランタイム/論理バグのどれを突いたか」
attack surface(攻撃面) 攻撃者が触れるコード/APIの総量 「使わない syscall を塞げば、そこのバグは到達不能」
egress 内側から外部への通信 「これを断てば lethal trifecta の外部送信辺が切れる」

lethal trifecta(guardrails_basics と共通)

意味 sandbox での断ち方
機密アクセス 秘密データを読める FS read を deny(ex03)/ 別FSに隔離(ex04/06)
untrusted 取り込み 攻撃者が内容を仕込めるデータを読む (入力側の防御。guardrails ex02/05)
外部送信(egress) データを外へ出せる network deny(ex03/04)が本丸

判定: 3 辺が揃うと窃取成立。sandbox は主に「機密アクセス」と「外部送信」の 2 辺を構造的に断つ。

よくある誤解の訂正

  • 「権限を確認すれば sandbox は要らない」は誤り — コマンド文字列の許可は「許可したコマンドが名前以上のことをする」のを防げない。実行中プロセスには OS sandbox が要る
  • 「コンテナ=VM 並みに安全」は誤り — Docker はカーネルを共有する。脱出 CVE(runc CVE-2019-5736)がある。真のマルチテナントは gVisor/microVM
  • 「microVM なら絶対安全」は誤り — 周辺(DNS・メタデータサービス)が穴になる(AWS AgentCore の実事故)。多層 + 周辺点検が要る
  • 「rlimit でメモリも縛れる」は半分誤りRLIMIT_AS は macOS では強制されない。OS 依存
  • 「Temporal の sandbox がセキュリティを担う」は誤り — それは決定性のための隔離。untrusted コードの被害封じ込めは別途 security sandbox が要る(ex08)

10. 困りごと → 見る/使うもの(クイック早見表)

困りごと 見る ex / 使うもの
LLM にコードを実行させたいが怖い ex07(隔離あり/なしの対比)→ 自分の脅威モデルで ex03/04/06 を選ぶ
自分の Mac でローカルに隔離したい ex03 Seatbelt(macOS)。.sb で network deny + 秘密 path deny
他人の untrusted コードを大量に回す ex06 E2B microVM(カーネル非共有・マルチテナント安全)
暴走(無限ループ/メモリ爆弾)を止めたい ex02 rlimit(ただし可用性専用・OS 依存)
どの隔離層を選べばいいか分からない ex05 の recommend()(脅威モデル → 最小層を逆算)
耐久ワークフローに sandbox を組み込む ex08(決定性 sandbox と security sandbox を分離)
LangChain エージェントにコード実行させたい ex09(create_agent + sandbox 化した python_exec ツール + @wrap_tool_call ゲート)
権限ゲートとの違いが分からない guardrails_basics(ツール権限)→ 本フォルダ(コード実行隔離)

11. 一次資料(教材の柱)


作成: 2026-06-13 / 最終更新: 2026-06-19