[English](./README.md) | 日本語

# pi-sandbox

Pi coding-agent 向けのプロセスレベル・サンドボックス拡張機能です。
OS 固有のサンドボックス機構（bwrap / landlock / seatbelt / windows-acl）を活用し、**「ワークスペースと一時ディレクトリのみ書き込み可能」「その他は読み取り専用」「安全側に倒すフェイルクローズ（fail-closed）設計」** を実現します。コンテナを使用しないため、ホストのパスがそのまま動作します（パス透過）。拒否先行（denial-first）の権限昇格フローと、独立した AI モデルによる自動審査（auto-review）を標準搭載しています。

## インストール

```bash
# npm からインストール
pi install npm:@yuru7/pi-sandbox

# またはローカルチェックアウトからインストール
pi install .
```

## 基本的な使い方

### 3つのパーミッションモード

| モード | ファイルへの影響 |
|---|---|
| `workspace-write`（デフォルト） | 作業ディレクトリ（ワークスペース） + `/tmp` + `os.tmpdir()` のみ書き込み可能（ワークスペースが git 管理サブディレクトリの場合はリポジトリルートも追加。`includeGitRoot` がオフなら加えない）。その他はすべて読み取り専用 |
| `read-only` | 書き込み不可（`/dev/null` や Windows の `NUL` デバイスのみ書き込み可能）。その他はすべて読み取り専用 |
| `danger-full-access` | サンドボックスを完全にバイパスし、無制限のフルアクセスを許可（明示的なエスケープハッチ） |

> **注意**: ネットワーク通信は常に許可されています（ネットワーク分離はありません）。

### git 管理サブディレクトリ

`workspace-write` では、作業ディレクトリが git 管理サブディレクトリ（祖先に `.git` がある）のとき、書き込み可能の範囲にリポジトリルートも加えます。`git commit` は `.git` に書き込むため、これがフェンスの外にあると必ず拒否されてしまうからです。`.git` があるディレクトリを書き込み可能にし、`.git` がファイルのとき（リンクされた worktree や submodule）は、`gitdir:` の参照先とその `commondir` も加えます。検出は作業ディレクトリから上へたどってディレクトリごとに1回だけ行い、結果を記憶するので呼び出しごとの再走査は起きません。適用は `workspace-write` のみで、`read-only` では何も開きません。

> **注意**: これは意図的な範囲拡大です。`workspace-write` ではリポジトリルート配下すべて（ワークスペースのサブディレクトリだけでなく）が書き込み可能になります。

この拡大は既定でオンで、`pi-sandbox.json` の `"includeGitRoot": false` で止められます。オフにしても止まるのは git 由来のルート追加だけで、作業ディレクトリ・一時ディレクトリ・明示的な `sandbox_grant_write` 許可はそのままです。そのため、git のメタデータが書き込み可能な範囲の外に残るようになると、サブディレクトリからの `git commit` はワークスペース外への書き込みと同じように拒否されます。

### ディレクトリ許可

`sandbox_grant_write` は、今のユーザー依頼が終わるまで、指定したディレクトリを書き込み可能な根に足します。承認は昇格と同じモード（`human` / `auto-review` / `allow-all`）です。そのディレクトリの中のパスを名指ししたサンドボックス拒否のあと、かつこの依頼の続きでもそこへ書くときに呼びます。そのディレクトリを一度しか使わないときや、拒否がディレクトリを一つに特定できないときは、拒否された呼び出しを `danger-full-access` でやり直します。

許可は、エージェントがこの依頼を終えたとき、および次のユーザー発話（steering と follow-up を含む）が始まったときに消えます。そのとき、このツールが作ったディレクトリが空のままなら削除します。一緒に作った親も、空になっていれば消します。もともとあったディレクトリや、中身があるディレクトリは残します。`/`、ホームディレクトリ、その祖先は拒否します。カスタムの `runnerCommand` には追加の根を渡せません。子セッションには引き継ぎません。

- `/permission`
  現在のサンドボックス状態（現在のモードと設定ソース、選択されたランナーと適用レベル、ワークスペースパス、有効な承認モードなど）を表示します。
- `/permission <read-only|workspace-write|danger-full-access>`
  パーミッションモードを**プロセス全体**で即座に切り替えます。親セッションおよびすべてのサブエージェント子セッションにおいて、次回のツール呼び出しから適用されます。
- `/pi-sandbox init`
  推奨のデフォルト設定で設定ファイル（`pi-sandbox.json`）を書き出します。保存先はグローバル（エージェントディレクトリ）またはプロジェクト単位（`<project>/.pi/`）から選択できます。既存ファイルがある場合は上書き前に確認が行われます。

## 動作の仕組み

### 概要と保護の仕組み

pi-sandbox は、エージェントが実行する操作の種類に応じて適切な保護を適用します。

- **コマンド実行（bash / powershell）**: プラットフォーム固有の軽量サンドボックスランナーでプロセスをラップして起動します。コンテナを使用しないため、ホスト上の絶対パスがそのまま透過的に動作します。
- **ファイル書き込み・編集（write / edit ツール）**: ツール実行前にインプロセスで「書き込みフェンス（write fence）」によるパス検査を行い、許可されていないパスへの書き込みを即座にブロックします。
- **ファイル読み取り（read ツールおよびコマンドでの読み込み）**: 制限はありません。ホスト上でユーザー自身がアクセス権を持つファイルはすべて読み取り可能です。

### 保護の対象範囲（スコープ）

**エージェント（モデル）が発行したタスクのみが保護対象です。**

- ユーザー自身がプロンプトで直接入力した `!command` / `!!command` や、RPC 経由の `bash` コマンドは、Pi 本体の `user_bash` イベント経由で実行されるため、サンドボックスの制限を受けません（ユーザー自身のシェル権限で直接実行されます）。
- Pi のプロセス外で起動された他のツールやプロセスもサンドボックスの対象外です。

### プラットフォームとランナーの対応

| プラットフォーム | ランナー | 仕組み |
|---|---|---|
| Linux | `bwrap`（推奨） | `--ro-bind / /` によりファイルシステム全体を読み取り専用化し、ワークスペースおよびホストの `/tmp` のみ読み書き（rw）バインド |
| Linux | `landlock-run`（フォールバック） | 同梱の事前ビルドバイナリを使用。Landlock LSM の許可リストにより `/` を読み取り専用、ワークスペースと `/tmp` を書き込み可能に設定 |
| macOS | `sandbox-exec`（組み込み） | macOS 組み込みの Seatbelt SBPL を使用。`deny file-write*` を基本とし、ワークスペースおよび一時ディレクトリのみ例外許可 |
| Windows | `windows-acl`（組み込み、一部適用） | 制限付きトークン（`WRITE_RESTRICTED`）+ ワークスペースおよび `%TEMP%` への機能 SID（capability SID）ACL 付与 + Low mandatory 整合性ラベル。**`powershell` ツールのみ対応**（制限モードでは `bash` は拒否） |
| その他 | なし | **フェイルクローズ（fail-closed）**: 制限されたコマンドは常に拒否され、保護なしで暗黙的に実行されることはありません |

## 権限昇格（エスカレーション承認）

エージェントが作業を進める上でワークスペース外への書き込みや危険なコマンドが必要になった場合、エージェントは一時的な権限昇格（エスカレーション）を要求できます。

### 拒否先行（Denial-first）の原則

安全のため、エージェントは事前の申告だけで昇格権限を得ることはできません。エスカレーションは**拒否先行（denial-first）**で動作します。

1. **初回実行での拒否**: エージェントが通常のモードで操作を実行し、サンドボックスによって実際に拒否される必要があります。
2. **昇格リクエスト**: 同一セッション内で、**「直前にサンドボックスに拒否された操作」と完全に一致する操作**（同一ツール、同一作業ディレクトリ、同一引数）に対してのみ、エージェントは `sandbox_permissions`（`workspace-write` または `danger-full-access`）と `justification`（理由）を添えて昇格を再試行できます。
3. **拒否記録の消費**: 拒否記録は**1回のみ消費**され、**10分で失効**します。1回の拒否につき、昇格再試行は1回のみ許可されます（承認はその呼び出し1回にのみ適用されます）。

> **メモ（strict スキーマ対策）**: 一致する拒否記録がない場合、エスカレーション用パラメータは無視されて現在のモードで実行され、結果に注記が含まれます。また、一部の LLM プロバイダが要求する strict スキーマ（全パラメータ必須）に対応するため、`null` や空文字などのプレースホルダーは「昇格要求なし」として安全に正規化されます。

### 承認モード（approvalMode）

再試行されたエスカレーション要求を誰が判定するかは、設定の `approvalMode` で制御します。

| モード | 動作 |
|---|---|
| `auto-review`（デフォルト） | 独立したレビュワーモデルが自動で `ALLOW`（許可）または `DENY`（拒否）を判定します。人間への確認プロンプトは表示されず、ヘッドレス環境やサブエージェントセッションでも一貫して自動判定されます |
| `human` | 人間に確認プロンプトを表示し、「1回のみ許可（Allow once）」または「拒否（Deny）」を選択します。拒否時には理由を入力できます。サブエージェントのリクエストは親セッションに転送されます（親に UI がない場合は拒否されます） |
| `allow-all` | 正当な拒否記録に裏付けられた昇格リクエストを自動的に1回のみ承認します。セッション全体のモードが恒久的に `danger-full-access` に切り替わるわけではありません |

いずれのモードでも、一致する拒否記録のない不正なリクエストは拒否されます。また設定に不正な値がある場合も安全側に倒れ、エスカレーションは拒否されます（サンドボックスが勝手に解除されることはありません）。

## 自動レビュー（Auto-review）の詳細

`auto-review` モードでは、独立したモデル呼び出しによってエスカレーションの安全性が審査されます。

### レビュワーモデルの独立性と入力情報

レビュワーはエージェント本体とは完全に分離されたモデル呼び出しです。エージェントの思考過程（Thinking）や過去のツール実行結果全体、ツール機能は一切与えられません。

レビュワーのプロンプトに渡される情報は以下に限定されます:
- **サンドボックスのメタデータ**: ランナー、現在のモード、ワークスペースパス、書き込み可能なパス
- **試行された操作内容**: 実行しようとしたコマンドまたはファイルパス、作業ディレクトリ（cwd）、終了コード、stdout/stderr の末尾（各 8KiB）または書き込みフェンスのエラー内容
- **要求内容**: 要求されたモードと、エージェントが提示した理由（justification）
- **直近の対話コンテキスト**: セッションのコンテキストに残っている直近最大 3 ターン（各ターンは「ユーザー指示 1 件」と「エージェントの最終応答 1 件」、合計 16KiB まで。古いターンから切り捨て）

### 判定ポリシー

レビュワーは以下の厳格な基準に従って判定します:

- **許可（ALLOW）の基準**:
  - その昇格操作が、**直近のユーザー指示を直接実行するための手段**（指示された操作そのもの、またはそれに不可欠な手順）である場合にのみ許可されます。
- **拒否（DENY）の基準**:
  - 指示との関連性が薄い操作、指示された範囲を超える操作、エージェントの弁明（justification）や返答のみに依存した根拠不十分な操作は拒否されます。
  - ユーザー指示が存在しないか取得できない場合は、モデルを呼び出さずに即座に拒否されます。
- **操作種別ごとの審査観点**:
  - `write` / `edit`: 昇格しても当該呼び出しのファイルパスに対する書き込みフェンスが解除されるだけです。ユーザー指示に対象ファイルが明記されていれば、編集後の具体的なテキストが指示に含まれていなくても拒否理由にはなりません（本文はレビュワーに渡されません）。また、「ワークスペース外であること」自体は初回拒否の理由であって、エスカレーションを拒否する理由にはなりません。
  - `bash` / `powershell`: 当該呼び出しにおいてサンドボックスが完全に解除されてコマンドが実行されるため、初回拒否された特定のパスだけでなく、**コマンド全体の安全性**が審査されます（一般的なインストールコマンドであっても、任意のパッケージスクリプトが実行されるリスクがあります）。
- **「範囲が広い」の考え方**:
  - 要求されたモードが `danger-full-access` であること自体は拒否理由になりません（モード昇格の要求では、`workspace-write` から一段上の選択肢はこれしかないため）。審査されるのは「実行される操作の内容が、ユーザー指示の求めた範囲を超えているかどうか」です。ディレクトリ許可は別の要求であり、この判定の対象ではありません。
- **ディレクトリ許可**（`sandbox_grant_write`）:
  - 許可はユーザー依頼が終わるまで続き、その間の後続ツール呼び出しは、そのディレクトリ以下に書けます。それ以外の場所ではサンドボックスが残ります。
  - 一番狭い許可先は、拒否されたパスを直接含むディレクトリです。ファイルならその親です。削除や名前変更は親ディレクトリへの書き込みなので、ファイル単体まで狭くはできません。そのディレクトリに他のファイルがあることや、ユーザーが中のファイルを1つ名指ししたことは、広すぎる理由になりません。
  - そのディレクトリの祖先（ファイルの親の、さらに親を含む）は拒否されたパスより広いので拒否します。
- **証拠と安全性の原則**:
  - ユーザー指示と一致していても、本質的に危険な操作が安全になるわけではありません。
  - 対話履歴は信頼できない証拠データとして扱われ、エージェントの応答は「ユーザーが何を頼んだか」の根拠にはなりません。

### 判定結果と通知

- レビュワーは `{"decision":"ALLOW"}` または `{"decision":"DENY","reason":"短い1文"}` の形式で単一の JSON オブジェクトのみを返します（それ以外の出力はすべて拒否扱い）。
- `DENY` の場合、1文の拒否理由が UI 通知（または stderr）にのみ表示されます。この理由はエージェントには渡されません（シークレットの伏字処理、最大 500 文字制限）。
- 通常の `DENY` 判定は確定であり、別のモデルに再問い合わせたり人間にフォールバックすることはありません。

### モデル設定とフォールバック動作

- `autoReview.model`: `CURRENT`（エスカレーション発生時点のアクティブなエージェントモデル）または `provider/model-id` を指定します。
- `autoReview.thinkingLevel`: `CURRENT` または思考レベル（`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`）を指定します（モデル非対応のレベルを指定すると失敗します）。
- `autoReviewTimeoutMs`: レビューワー 1 回の呼び出し上限（ミリ秒）（デフォルト `90000`、正の数）。エスカレーション審査とディレクトリ許可審査の両方に、フォールバック試行を含めて適用されます。
- **フォールバック**: 設定されたモデルが利用できない場合（認証エラー、APIエラー、`autoReviewTimeoutMs` の期限超過など）、1度だけ警告を表示した上で、**現在アクティブなエージェントモデル（同一の思考設定）で1回だけ再試行**します。2回目の呼び出しも失敗した場合はエスカレーションが拒否されます。

### セキュリティとプライバシーに関する注意

- レビュワープロバイダの API には、実行コマンド、出力ログ、および直近の対話ターンが送信されます。シークレット情報のマスク処理は一部の明確なパターンのみに適用されるため、機密情報の取り扱いにはご注意ください。
- 自動レビューはリスク評価を行う仕組みであり、絶対的な安全を保証するものではありません。

## 設定

### 設定ファイルの場所と優先順位

- **グローバル設定**: Pi のエージェントディレクトリ（通常 `~/.pi/agent/pi-sandbox.json`、環境変数 `$PI_CODING_AGENT_DIR` がある場合はその配下）
- **プロジェクト設定**: `<project>/.pi/pi-sandbox.json`

設定ファイルは起動時に自動作成されません。`/pi-sandbox init` を実行したときに新規作成されます。

**設定項目の優先順位（下に記載されたものが優先）**:
1. デフォルト設定
2. `<agent-dir>/sandbox.json`（レガシー）
3. `<agent-dir>/pi-sandbox.json`
4. `<project>/.pi/sandbox.json`（レガシー）
5. `<project>/.pi/pi-sandbox.json`
6. `/permission` コマンドによる手動切り替え（ファイル設定より常に優先）

> **プロジェクトの信頼性保護**: 信頼されていないプロジェクト、または `isProjectTrusted()` が判定できない環境では、プロジェクト設定から `approvalMode`・`autoReview`・`autoReviewTimeoutMs` を上書きすることはできません（グローバル設定の値が強制されます）。これにより、悪意あるリポジトリをチェックアウトした際に勝手に `allow-all` が有効化されるリスクを防止します。

### 設定例（pi-sandbox.json）

```json
{
  "mode": "workspace-write",
  "runnerCommand": null,
  "runnerFailureSignatures": null,
  "probeTimeoutMs": 5000,
  "includeGitRoot": true,
  "approvalMode": "auto-review",
  "autoReview": {
    "model": "CURRENT",
    "thinkingLevel": "CURRENT"
  },
  "autoReviewTimeoutMs": 90000
}
```

### 設定項目一覧

| フィールド | デフォルト値 | 説明 |
|---|---|---|
| `mode` | `workspace-write` | デフォルトのパーミッションモード。不正な値の場合は警告を表示してデフォルト値にフォールバックします |
| `runnerCommand` | `null` | カスタムの bwrap 互換ランナー引数配列（argv）（`runnerFailureSignatures` とのペア指定が必須） |
| `runnerFailureSignatures` | `null` | カスタムランナーの致命的な診断シグネチャ文字列（空でない単一行文字列） |
| `probeTimeoutMs` | `5000` | ランナーの機能確認プローブのタイムアウトミリ秒（正の整数） |
| `includeGitRoot` | `true` | 作業ディレクトリが git 管理サブディレクトリのとき、`workspace-write` をリポジトリルートまで広げる。`false` はこの拡大だけを止める（ワークスペース・`/tmp`・明示的な許可はそのまま） |
| `approvalMode` | `auto-review` | 承認方式（`human` / `auto-review` / `allow-all`）。不正な値の場合はエスカレーションを拒否します |
| `autoReview.model` | `CURRENT` | 自動レビューに使用するモデル（`CURRENT` または `provider/model-id`） |
| `autoReview.thinkingLevel` | `CURRENT` | 自動レビューの思考レベル（`CURRENT` または Pi の思考レベル） |
| `autoReviewTimeoutMs` | `90000` | レビューワー 1 試行のタイムアウトミリ秒（正の数）。不正な値の場合はエスカレーションを拒否します |

`/permission` は、実際に使用される承認モードを表示します（プロジェクトが信頼されていない場合はグローバル設定の値）。

## セキュリティに関する注意事項

- **読み取り権限の仕様**: 制限されたプロセスであっても、ホスト上でユーザー自身が**読み取り権限を持つすべてのファイル**（`~/.ssh` などを含む）を読み取ることができます。これは本サンドボックスの設計仕様です。root 専用ファイルなどは OS のアクセス権によって保護されます。
- **一時ディレクトリのホスト共有**:
  - Linux（bwrap / landlock / write fence）や macOS では、`/tmp` はホストの `/tmp` と共有（rw バインド）されます。
  - Windows では、サンドボックスの一時ルートはホストの `%TEMP%` です。
  - 制限されたコマンドであっても、ホストの一時ファイル（他のセッションのソケットや一時ファイルなど）を変更・削除できる可能性があります。またホストの一時ディレクトリの書き込み権限がそのまま適用されます。
- **ロケールの強制**: 拒否メッセージの自動分類判定を行うため、制限された子プロセスでは `LC_MESSAGES=C` が強制されます（ユーザーの `LANG` や `LC_CTYPE` には影響しません）。
- **バックグラウンドプロセス**: 制限されたシェルは独立したプロセスグループで実行されます。タイムアウトや中断時にはグループ全体が終了されますが、Pi 自体が強制終了（SIGKILL など）された場合、バックグラウンドの子孫プロセスが残る可能性があります。
- **Landlock の制限**: Linux の Landlock フォールバックは、古いカーネル ABI では一部のみの適用（partial enforcement）となります（ステータス表示で確認できます）。

## プラットフォーム固有の仕様と注意点（Windows）

Windows 環境（`windows-acl` ランナー）では、OS のセキュリティ構造に起因する以下の固有仕様や制約があります。

### 利用可能なシェル
- 利用可能なシェルは **`powershell` ツールのみ**です。
- Windows では、制限付き `bash` は `exposure: "hidden"` として登録されます。これによりモデルからは見えず、明示的に指定（`defaultTools`、`--tools`、`setActiveTools()`）してもアクティブ化されません。拡張機能が `bash` という名前を占有し続けることで、組み込みの無制限な `bash` が誤って使われるのをフェイルクローズで防いでいます。
- 将来ホスト側で `hidden` の扱いが変更されたとしても、その定義は依然として制限付きの呼び出しをすべて拒否します（プロセスは起動されません）。
- Windows で `bash` が実行できるのは、明示的に `danger-full-access` に切り替えた場合のみです（他のプラットフォームと同様に直接実行されます）。
- 既知の制限事項として、`defaultTools` では拡張機能が登録したツールを選択解除できません（`-name` 指定は組み込みツールのみを削除し、拡張機能のツールは定義側で除外しない限り自動的にアクティブ化されます）。pi-sandbox ではこのための設定を要求しません。Windows では定義レベル（`exposure: "hidden"`）でモデルから `bash` の名前が取り下げられているため、設定で除外する必要はありません。
- `powershell` がアクティブでない場合（`powershell` ツールが同梱されていない 1.0.0 より前のホスト、または選択から除外されている場合）、`/permission` には `shell: powershell only (not activated)` と表示され、アクティブなリストにまだ `bash` が含まれている場合（旧バージョンのホスト）はアクティブ化時に一度ヒントが表示されます。`{ "defaultTools": ["+powershell"] }`（Pi >= 1.0.0 が必要）を設定することで再度追加できます。

### サポート環境
- **OS バージョンの新しい下限はありません**: レガシーな Win32 API（`WRITE_RESTRICTED` トークンと Low mandatory 整合性ラベル）を使用しているため、Windows 11 24H2 以降を要求する `mxc` と異なり、幅広い環境で動作します。
- **Windows 10 Enterprise LTSC 2019（ビルド 17763.316）** で e2e 検証を完了しており（自動 e2e テストスイートおよび手動受け入れチェックリストをパス）、Windows 10 / 11 / Server 2025 以降でも動作が見込まれます。上流のテストフィクスチャと異なり、本パッケージのテストはバージョン非依存のプリミティブを使用しています。

### セキュリティ適用と制約（一部適用 / partial）
- **NTFS ハードリンク**: ワークスペース内の許可されたファイルへのハードリンクが存在する場合、ワークスペース外からも同様に書き込み可能です。
- **AppContainer パッケージ SID**: 他の AppContainer ツールによってタグ付けされたファイルは、Low 整合性レベルの子プロセスからは読み取れなくなる場合があります（復旧するには外部の ACE を削除するかツリーを再インストールしてください）。
- **PowerShell 言語モード**: ACL 境界ではなく起動時の制約に従います。`read-only` モードでは `%TEMP%` が書き込み不可となるため、PowerShell が `ConstrainedLanguage` に低下する場合があります（`Add-Type` や COM、リフレクションが失敗します）。`workspace-write` では `FullLanguage` が維持されます。
- **NUL デバイス**: `> NUL` や `\\.\NUL` などのデバイス表記は環境プロパティ（DACL で Everyone に読み取り/書き込み/実行が付与）であるため、両モードで常に書き込み可能です。ただし相対パスの `NUL`（例: `writeFileSync('NUL', …)`）はカレントディレクトリの通常ファイルとして扱われ、ワークスペース内では許可されワークスペース外では拒否されます。
- **持続的なセキュリティ記述子の変更**: ワークスペースおよび `%TEMP%` に付与された ACE、全ユーザーに対する `FILE_DELETE_CHILD` 拒否、Low mandatory ラベル（`NO_WRITE_UP`）はべき等ですが、Pi 終了後もそのまま残ります。これにより同一ユーザーの他の Low 整合性プロセスに対してもそれらのツリーが緩和されます。`read-only` に切り替えると機能 ACE は不活性化されますが削除されません。初回の付与時は `%TEMP%` ツリー全体に伝播するため数秒かかる場合があります。また `%TEMP%` および `TMP` 自体のパスは書き換えられず、ホストの `%TEMP%` がそのまま使われます。
- **%TEMP% への影響**: サブディレクトリが `FILE_DELETE_CHILD` 拒否 ACE を継承するため、自身のテンポラリサブディレクトリを `GENERIC_ALL`/`FullControl` で開こうとするサードパーティ製アプリが拒否される場合があります（DELETE による削除、`MAXIMUM_ALLOWED`、通常の読み取り/書き込みオープンには影響しません）。
- **拒否分類判定の言語依存（英語のみ対応）**: エラー出力（stderr）に含まれる英語の Win32/`cmd`/PowerShell メッセージを解析して拒否マーカー（`[sandbox: …]`）やエスカレーションを準備します。日本語版 Windows などではエラーメッセージがローカライズされるため、サンドボックスによるアクセス拒否自体は機能するものの、ツールが拒否を注記できずエスカレーションが準備されない場合があります。マーカーが見当たらない場合は分類判定の制約として扱ってください。
- **FFI ライブラリ（koffi）**: Win32 呼び出し用の `koffi` は通常の依存関係ですが、遅延読み込みされ Windows 上でのみ読み込まれます。
- **診断スキル**: Windows ファイルアクセス許可によって pi-sandbox の権限付与がブロックされた場合、同梱の `diagnose-windows-sandbox-acl` スキルで診断・修復できます（Windows 環境でのみ Pi に提供されます。修復にはセキュリティ記述子の変更が伴うため、承認された `danger-full-access` または手動で実行してください）。

## pi-container-sandbox 1.x からの移行

- **設定ファイルの互換性**: 以前の設定ファイル（エージェントディレクトリおよび `<project>/.pi/` 内の `sandbox.json`）は引き続き読み込まれますが、新しい設定は `pi-sandbox.json` に記述してください。従来の `image` / `runtime` / `host` セクションは警告付きで無視されます。既存の `sandbox.json` が勝手に削除・上書きされることはありません。`/pi-sandbox init` は `pi-sandbox.json` を新規作成し、上書き時は確認を行います。
- **コンテナ機能の非搭載**: コンテナランタイム（Docker/Podman）、イメージビルド、`runtime.mounts`、`/sandbox` コマンド、`--container*` フラグ、外部パスの承認フローなどは本パッケージには含まれません。
- **コンテナ版との排他利用**: コンテナレベルの分離（独立したファイルシステムやネットワーク名前空間）が必要な場合は `@yuru7/pi-container-sandbox` をご利用ください。`pi-sandbox` と `@yuru7/pi-container-sandbox` は**排他利用**です。両方が `bash` / `write` / `edit` を乗っ取り、両方が `sandbox.json` を読み込むため、同時に有効化せず一方を無効化またはアンインストールしてください（本パッケージはコンテナパッケージが読み込まない `pi-sandbox.json` も読み込みます）。

## 開発

```bash
npm test              # ユニットテスト + 統合テスト（ランナーがない環境では統合テストは自動スキップ）
npm run typecheck
./tests/e2e.sh
```

### ローカルビルドでのエスカレーション転送の検証

親セッションは `pi -e <path>` を使ってローカルの pi-sandbox を読み込むことができますが、**`-e` は親セッションにのみ影響します**。pi-subagents は各子セッションに対して個別のリソースローダーを構築するため、子セッションは `agentDir` およびプロジェクトの `.pi/` から拡張機能を**再検出**します。もし `~/.pi/agent/settings.json` にまだ `npm:@yuru7/pi-sandbox` が宣言されている場合、子セッションは公開版ビルドを読み込み、転送は**何のエラーも出さずに失敗します**（子セッションは `requires approval, but no approval channel is available` とだけ報告します）。両側で同じビルドが検出されるように設定してください:

```bash
AG=$(mktemp -d); cp ~/.pi/agent/auth.json "$AG/" 2>/dev/null || true
cat > "$AG/settings.json" <<EOF
{ "packages": ["<repo>/pi-sandbox", "<repo>/pi-subagents"] }
EOF
cd <writable project dir> && PI_CODING_AGENT_DIR="$AG" pi
```

観察結果および検証記録は `docs/superpowers/specs/2026-09-30-escalation-approval-forwarding-design.md` §11 に記載されています。

## 謝辞

この拡張は [yandy/pi-packages](https://github.com/yandy/pi-packages) の [`pi-sandbox`](https://github.com/yandy/pi-packages/tree/main/pi-sandbox) にインスパイアされて作成しました。

## ライセンス

MIT
