diff --git a/README.ja.md b/README.ja.md
index 36b68cc..b7b31da 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -5,201 +5,174 @@
Porta
- 実際に与えた権限だけでエージェントを動かす。
- 今使っている CLI エージェントには OS レベルの制限を、これから作るエージェントには WASM ランタイムを。
+ コマンドも AI エージェントも、与えた権限だけで動かす。
+ macOS と Linux のカーネルで強制。コンテナもデーモンも要らない。
- Almide + Wasmtime · Docker 不要
-
-
-
- English · 日本語
+
+
+
---
-## Porta とは
-
-AI エージェントにコマンドを実行させることは、鍵もトークンもネットワークも、
-その手の届く範囲に置くこと。Porta はコマンドを実行しつつ、OS カーネルに止め
-させます —— 書き込み・読み取り・通信・ソケットを macOS は Seatbelt、Linux は
-Landlock+seccomp で強制。ラッパーでもお願いでもなく、OS の境界そのもの。
-カーネルが表現できない制限は、緩めず実行を拒否します(**fail-closed**)。
-
-主張ではなく証拠で。公開の脱獄[コーパス](docs/benchmarks/escapes.md)は
-**macOS 22/22・Linux 28/28(user namespace を許さないホストでは 24/24)、突破ゼロ**、負けた行も残す([脅威モデル](docs/threat-model.md))。
-同じコーパスを srt・Fence・nono・landrun でも走らせた[比較表](docs/benchmarks/competitors.md)も、porta が負ける項目込みで公開。
-1 コマンドあたりの[コスト](docs/benchmarks/overhead.md)も公開:porta は macOS で約 16 ms、Linux で 2〜6 ms。
-
```text
-$ porta run sh -v ./work --read-policy strict --timeout 5 -- …
- ./work/out.txt に書けた 正規の作業は通る
- SSH秘密鍵を読む → 拒否
- 作業ディレクトリ外に書く → 拒否
- # ハングは締切で強制終了 → 終了コード 124
-```
-
-## インストール
-
-```bash
-curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.sh | bash
-```
-
-macOS(Apple silicon)と Linux(x86-64 / arm64)向けの単一バイナリ。公開済み
-SHA-256 で検証してから導入します。`cosign` があればリリースの Sigstore 署名も検証します
-([リリースの検証](docs/cli.md#verifying-a-release))。[Almide](https://github.com/almide/almide)
-があれば `go install` と同じ感覚で入れられます(Intel Mac やその他の環境はこちら)。
-
-```bash
-almide install github.com/almide/porta --branch main
-```
-
-`main` が最新リリースです。`--tag v0.6.6` で版を固定できます。ソースからビルドして
-`~/.local/bin` に入れるので数分かかります(Rust 側で Wasmtime をコンパイルするため)。手でビルドする方法は
-[CLI リファレンス](docs/cli.md#build-from-source)。`porta run` はネイティブ
-コマンドでも `.wasm` でも取れるので、WASI にコンパイルできるものは何でも動きます。
-
-Debian / Ubuntu 向けには各リリースに `.deb`(amd64・arm64)も付きます。
-`sudo apt install ./porta__amd64.deb` で `/usr/bin/porta` に入り、
-Ubuntu 23.10 以降では porta がコマンドごとに namespace を持てるようにする
-AppArmor プロファイルも読み込みます。ほかの方法で入れた場合は `sudo porta setup`
-で同じ状態にできます(root 所有の `/usr/local/bin/porta` にコピーし、そのコピー
-だけにプロファイルを入れます)。
-
-GitHub Actions では action が同じ検証つきでリリースを入れ、Ubuntu ランナーでは
-ランナーが本来与えない user namespace を porta にだけ許す AppArmor プロファイルも
-読み込みます:
-
-```yaml
-- uses: almide/porta@v0.6.12
-- run: porta run ./scripts/untrusted-step.sh -v .
+$ porta run sh -v ./work --no-net -- -c 'echo ok > out.txt; cat ~/.ssh/id_ed25519; echo x > ~/elsewhere.txt; curl https://example.com'
+cat: /Users/me/.ssh/id_ed25519: Operation not permitted
+sh: /Users/me/elsewhere.txt: Operation not permitted
+curl: (6) Could not resolve host: example.com
+[porta] the sandbox refused this run 3 times; what each would have needed:
+ file-read-data /Users/me/.ssh/id_ed25519
+ → closed by the preset or --deny-read; no mount reopens it
+ file-write-create /Users/me/elsewhere.txt
+ → -v /Users/me
+ …
```
-## こう使う
-
-`--` の前は porta のオプション、後ろはコマンドの引数です。
+`out.txt` は書けた。鍵も、外のファイルも、ネットワークも届かなかった。
-### すでに使っている AI エージェントを制限する
+Porta は 1 つのプロセスと、そこから起動されるすべてを包む。書き込みはマウントした
+ディレクトリだけ、認証情報の置き場所は読めず、ネットワークは指定したポートやホストに
+絞られ、CPU・メモリ・プロセス数・時間には上限がかかる。強制するのは macOS では
+Seatbelt、Linux では Landlock・seccomp・namespace で、お願いベースのラッパーではない。
+カーネルが表現できないルールは、弱めて通すのではなく実行を拒否する。
-エージェントは依存を入れ、テストを走らせ、ファイルを編集する —— 鍵のあるマシンで。
-1つのディレクトリと1つのホストだけ与え、残りは閉じる。
+## インストール
```bash
-cd project
-porta init claude # または porta init codex
-porta up -- -p "main.rs のバグを直して"
+curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.sh | bash
```
-`claude` はそのまま動きますが、書けるのはプロジェクトと自身の `~/.claude` だけ。
-そこでも settings・commands・グローバルな `CLAUDE.md` は書けないので、ある
-セッションが次のセッションにフックを仕込むことはできません。`~/.ssh`・`~/.aws`・
-Keychain は読めません。レシピは必要なものを実測したコメント付きの `porta.toml`
-です。macOS では Keychain を閉じているため、ログインは `ANTHROPIC_API_KEY` か
-`CLAUDE_CODE_OAUTH_TOKEN`(`claude setup-token` で発行)で渡します。Docker も
-イメージも、エージェント本体の変更も不要。`--snapshot` を付ければ、終わったあとに
-セッションが変えたものを一覧し、`porta rollback --yes` で元に戻せます。
+| 環境 | 方法 |
+|---|---|
+| macOS (Apple silicon)、Linux (x86-64, arm64) | 上のスクリプト。SHA-256 を検証し、`cosign` があれば Sigstore 署名も検証 |
+| Debian、Ubuntu | [リリース](https://github.com/almide/porta/releases)の `sudo apt install ./porta__amd64.deb`。Ubuntu 23.10 以降では namespace に必要な AppArmor プロファイルも読み込む |
+| それ以外の方法で入れた Ubuntu | 一度だけ `sudo porta setup` で同じプロファイルを入れる |
+| GitHub Actions | `- uses: almide/porta@v0.6.16` の後、どのステップでも `porta run …` |
+| その他 (Intel Mac など) | `almide install github.com/almide/porta --branch main` でソースからビルド |
-### ツールに API を1本だけ許し、全試行を記録する
+すべてのリリースは署名とビルド来歴付き —
+[リリースの検証](docs/cli.md#verifying-a-release)。
-ある1つのエンドポイントだけ要る。egress を porta のプロキシに通し、記録を残す。
+## 今使っているコーディングエージェントを閉じ込める
```bash
-porta run ./agent --proxy-allow 'api.example.com' --proxy-audit egress.jsonl -v ./work
+cd my-project
+porta init claude # または: porta init codex
+porta up -- -p "Fix the failing test"
```
-届くのは `api.example.com` だけ。直接 TCP・UDP・Unix ソケットの egress は拒否、
-クラウドメタデータやリンクローカルに解決される名前も拒否、決定は全部
-`egress.jsonl` に残ります。
-
-### 素性の知れないコマンドを、マシンを預けずに試す
+`porta init` は、そのエージェントに必要な分だけを測った、コメント付きの `porta.toml`
+を書く。プロジェクトとエージェント自身のディレクトリは書き込み可、そこにある設定・
+フック・グローバル指示は書き込み不可 — 1 つのセッションが次のセッションに何かを
+仕込めないように — そして API キーやトークンは名前で渡す。macOS では Keychain が
+閉じているので、ログインは `ANTHROPIC_API_KEY` か `CLAUDE_CODE_OAUTH_TOKEN`
+(`claude setup-token`) で渡す。
-実行前にポリシーを確認してから、隔離して、締切と資源上限付きで走らせる。上限は
-カーネルが、起動した子プロセス全部に強制します。
+他のコマンドもフラグで同じように動く:
```bash
-porta explain ./sketchy-installer -v ./sandbox --allow-net github.com:443 # ポリシー確認、実行はしない
-porta run ./sketchy-installer -v ./sandbox --allow-net github.com:443 \
- --timeout 60 --max-cpu 30 --max-procs 500 --max-file-size 200
+porta run ./installer -v ./sandbox --allow-net '*:443' --timeout 120 --max-procs 500
```
-ハングは締切で強制終了、CPU 焼きは SIGXCPU で終わり、fork 爆弾は fork できず、
-ファイルは上限で止まる。`--max-memory-mb 512` は実行全体の実メモリの上限で、
-Linux では cgroup v2(systemd のユーザーセッションが必要、無ければフラグを拒否)、
-macOS では監視側が上限到達時に実行を終わらせます。
+`--` より前は porta の、後ろはコマンドの引数。
-### 信頼できない/生成された WASM を動かす
+## 何を止めるか
-ユーザー投稿やモデル生成のコードを、ホストの FS もネットワークもなし、燃料・
-メモリ・時間を縛って実行。core module でも WASI 0.2 / 0.3 コンポーネントでも同じ検査:
-宣言された import ごとに、与えた能力が要ります。
+| | macOS | Linux |
+|---|---|---|
+| **書き込み** | `-v` のマウント、`/tmp`、`/dev` だけ | 同じ |
+| **書き込み可のマウントの中** | `.git/hooks`、`.git/config`、`.envrc`、シェルの rc、エージェントの設定などは書けず、リネームで退かすこともできない | 同じ (user namespace が必要 — 下記) |
+| **読み取り** | 認証情報以外すべて: `~/.ssh`、`~/.aws`、`~/.config/gh`、クラウドやレジストリのトークン、Keychain、ブラウザのプロファイルは閉じる。`--read-policy strict` で読み取りもマウント内に限定 | 同じ (Keychain を除く) |
+| **認証情報のソケット** | SSH agent、gpg-agent、`docker.sock` は `--allow-unix` なしでは拒否 | 同じ |
+| **ネットワーク** | 開放、ポート単位の TCP (`--allow-net`)、porta のプロキシ経由のホスト単位 (`--proxy-allow`)、遮断 (`--no-net`) | 同じ。ポート指定時は UDP も閉じる |
+| **他のプロセス** | 引数や環境変数は読めない。`open(1)` / Launch Services も閉じる | 見えない: 専用の PID namespace |
+| **リソース** | `--timeout`、`--max-cpu`、`--max-procs`、`--max-file-size`、`--max-memory-mb` | 同じ (メモリは cgroup v2) |
+| **環境変数** | 空から始まる。`PATH`、`HOME`、ロケール、端末だけが渡り、他は `-e` / `--env-pass` で指定したものだけ | 同じ |
-```bash
-porta run plugin.wasm --profile worker --step-limit 5000000 --max-memory 256
-```
+何を閉じるかはコードに書いたリストではなくプリセット —
+[`native/presets/default.toml`](native/presets/default.toml)、`porta explain` で表示 —
+で、`--deny-read`、`--protect`、`--deny-unix` で足し、`--preset ` で置き換える。
+理由つきの全体表は [enforcement](docs/enforcement.md) に。
-### 判断ループが WASM のエージェントを作る
-
-`porta run` は他人が書いたエージェントを制限します。`porta agent` はループ自体が
-WASM のものを動かす —— ループも各ツールも別インスタンスで、環境もディレクトリも
-継承せず、モデルの資格情報はホストに留まり、クラッシュしても完了済みの書き込みを
-繰り返さずに再開します。
+## 拒否されたとき
```bash
-porta agent agent.toml --record run.jsonl -- "ツールを使って 20 と 22 を足して"
-porta agent-resume agent.toml run.jsonl # 完了済みの書き込みは繰り返さない
-porta agent-journal run.jsonl # 読み取り専用メタデータ、コードは読み込まない
+porta explain ./tool -v ./work --allow-net '*:443' # ポリシーを文章で。何も実行しない
+porta run ./tool -v ./work --why # 実行後: 拒否ごとに必要だったフラグ
```
-詳しくは[作るエージェント](docs/agent-runtime.md)、ゲストが回避できない
-[完了チェック](docs/completion-checks.md)、WASM をレビュー済み SHA-256 に縛る
-[アーティファクトピン](docs/artifact-pins.md)。
-
-### 設定をプロジェクト設定として残す
-
-```bash
-porta init native mytool # 編集用の porta.toml を生成
-porta up -- --some-flag
+```text
+[porta] the sandbox refused this run 2 times; what each would have needed:
+ file-write-data /home/me/notes/out.txt
+ → -v /home/me/notes
+ network-outbound remote:*:80
+ → --allow-net '*:80'
+ to run again with those granted:
+ porta run ./tool -v ./work -v /home/me/notes --allow-net '*:80'
```
-うまくいった起動をそのまま書き出すこともできます: `porta explain claude … --save porta.toml`。
-
-## 制限が実際に止める様子を見る
+macOS では失敗した実行のあと、カーネルの拒否ログからこれを出す。Linux では `--why`
+がサンドボックスの外から `strace` で実行を追う。
-効いている制限は目に見えないので、失敗するところを見ます。
+## 変更を元に戻す
```bash
-porta run curl -- https://example.com # 既定ではネットワークは開いている
-porta run curl --allow-net '*:443' -- https://example.com # HTTPS 許可 → 通る
-porta run curl --allow-net '*:80' -- https://example.com # → 終了 7、443 は拒否
+porta run ./agent -v ./project --snapshot # 先にマウントを複製し、終了後に変更点を一覧
+porta rollback --yes # 元に戻す
```
-拒否された実行は当て推量を残しません。非ゼロ終了のあと、porta はカーネルの拒否
-記録を読み、どのフラグが要ったかを出します(macOS は失敗のたびに、Linux は
-`--why` を付けると `strace` で実行を追って)。終了コードで「失敗したコマンド」と「そもそも走らなかった」を
-区別できます —— [CLI リファレンス](docs/cli.md#when-a-run-is-refused)。
-
-## 証拠
-
-公開する数値は生レポート・入力ハッシュ・再採点の監査を必ず残し、CI がその監査を
-回します。Porta に有利でない結果も同じ場所に公開します。
-
-- [脱獄コーパス](docs/benchmarks/escapes.md) —— 既知の脱獄をバイナリに当て、
- 何が抜けたかで採点。負けた行も掲載。
-- [起動とメモリ](docs/benchmarks/startup-and-memory.md) —— 比較の限界を明記した
- 固定応答の計測。
-- [封じ込めと回復](docs/benchmarks/containment-evaluation.md) —— 敵対的入力下。
- 5シナリオ中1つだけが差を分け、封じ込めはタスク完了を犠牲にした。
-- [実タスク品質](docs/benchmarks/README.md) —— 一般的な品質優位は示せておらず、
- 共有計算の追試は「勝ち」として公開せず却下した。
+APFS ではクローンなので、複製はほぼタダ。スナップショットはどのマウントの外にも
+置くので、コマンドが自分の取り消し手段を書き換えることはできない。
+
+## 他のツールとの比較
+
+同じ[脱出コーパス](docs/benchmarks/escapes.md) — 実際の抜け道を、何が通ったかで採点 —
+を 5 つのツールでそれぞれの既定のまま実行した
+([全比較](docs/benchmarks/competitors.md)、負けた行も含む):
+
+| | porta | [srt](https://github.com/anthropics/sandbox-runtime) | [Fence](https://github.com/fencesandbox/fence) | [nono](https://github.com/nolabs-ai/nono) | [landrun](https://github.com/Zouuup/landrun) |
+|---|---|---|---|---|---|
+| macOS: 試行 / 脱出 | **22 / 0** | 17 / 5 | 14 / 5 | 13 / 5 | — |
+| Linux: 試行 / 脱出 | **28 / 0** | 23 / 2 | 21 / 3 | 24 / 5 | 22 / 7 |
+
+他にあって porta にないもの: srt と Fence は既定でネットワークを閉じる。Fence は
+コマンドを名前でフィルタする。nono は実行中に追加の権限を求められる。porta の
+オーバーヘッドは macOS で約 16 ms、Linux で 2–6 ms
+([overhead](docs/benchmarks/overhead.md))。
+
+## 制限
+
+- **ネットワークは既定で開いている。** `--no-net`、`--allow-net`、`--proxy-allow`
+ で閉じる。
+- **Linux では一部の保護に user namespace が必要** — マウント内のフック、認証情報の
+ ソケット、他プロセスの隠蔽。Ubuntu 23.10 以降は `.deb` か `sudo porta setup` が
+ プロファイルを入れるまで制限される。無い場合も porta は動き、何が開いたままかを言う。
+- **カーネルは共有。** VM ではなくプロセスのサンドボックスなので、カーネルの脆弱性は
+ 対象外。何を守り何を守らないかは[脅威モデル](docs/threat-model.md)に。
+- **テストは作者によるもの。** コーパス、ファザー、モンキーテストは公開され CI で
+ 回っているが、第三者による監査はまだない。
+
+## その他の機能
+
+- **アンビエント権限のない WASM。** `porta run plugin.wasm --profile worker` は
+ コアモジュールや WASI 0.2/0.3 コンポーネントを、ホストのファイルシステムも
+ ネットワークもなし、fuel・メモリ・時間に上限をつけて実行する。import はすべて
+ 付与した capability が必要。
+- **ループが WASM のエージェント。** `porta agent agent.toml` は判断ループと各ツールを
+ 別インスタンスで動かし、モデルの認証情報はホストに置き、クラッシュした実行を
+ 完了済みの書き込みを繰り返さずに再開する ([agent runtime](docs/agent-runtime.md))。
+- **1 つの API とログ。** `--proxy-allow api.example.com --proxy-audit egress.jsonl`
+ はそのホストだけを通し、すべての判断を記録する。
## ドキュメント
-- [CLI リファレンス](docs/cli.md) —— 全コマンド・フラグ・`porta.toml` キー・終了コード。
-- [強制の中身](docs/enforcement.md) —— macOS と Linux が実際に何を止めるか。
-- [脅威モデル](docs/threat-model.md) —— 何を守り、何を守らないか。
-- [アーキテクチャ](docs/architecture.md) —— ポリシーは Almide、強制は Rust。
-
-## ライセンス
+- [CLI リファレンス](docs/cli.md) — すべてのコマンド、フラグ、`porta.toml` のキー、終了コード
+- [Enforcement](docs/enforcement.md) — 各プラットフォームが何をどう止めるか
+- [脅威モデル](docs/threat-model.md) — 何を守り、何を守らないか
+- [ベンチマーク](docs/benchmarks/README.md) — 脱出コーパス、比較、オーバーヘッド
+- [アーキテクチャ](docs/architecture.md) — ポリシーは Almide が決め、Rust が強制する
-Apache-2.0
+[Almide](https://github.com/almide/almide) と [Wasmtime](https://wasmtime.dev) で
+作られている。Apache-2.0。
diff --git a/README.md b/README.md
index 1578dc4..43c9a7e 100644
--- a/README.md
+++ b/README.md
@@ -5,218 +5,178 @@
Porta
- Run an agent with the permissions you actually granted it.
- OS-enforced limits for the CLI agents you already use, and a WASM runtime for the ones you build.
+ Run a command — or an AI agent — with only what you granted it,
+ enforced by the kernel on macOS and Linux. No container, no daemon.
- Almide + Wasmtime · No Docker required
-
-
-
- English · 日本語
+
+
+
---
-## What Porta is
-
-Letting an AI agent run commands on your machine puts your keys, tokens and
-network within its reach. Porta runs the command and lets the OS kernel stop
-it — writes, reads, network and sockets enforced by Seatbelt on macOS or
-Landlock + seccomp on Linux, not by a wrapper or a prompt. A restriction the
-kernel cannot express refuses the run rather than weakening it (**fail-closed**).
-
-Shown, not claimed: a published jailbreak
-[corpus](docs/benchmarks/escapes.md) holds 22/22 on macOS and 28/28 on Linux (24/24 on hosts that refuse
-unprivileged user namespaces),
-zero escapes, losing rows kept in ([threat model](docs/threat-model.md)).
-The same corpus run under srt, Fence, nono and landrun is
-[published beside it](docs/benchmarks/competitors.md), including where porta loses,
-and so is [what each costs a command](docs/benchmarks/overhead.md): about
-16 ms under porta on macOS and 2 to 6 ms on Linux.
-
```text
-$ porta run sh -v ./work --read-policy strict --timeout 5 -- …
- wrote ./work/out.txt legitimate work goes through
- read the SSH key → refused
- write outside ./work → refused
- # a hung command is killed at the deadline → exit 124
-```
-
-## Install
-
-```bash
-curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.sh | bash
+$ porta run sh -v ./work --no-net -- -c 'echo ok > out.txt; cat ~/.ssh/id_ed25519; echo x > ~/elsewhere.txt; curl https://example.com'
+cat: /Users/me/.ssh/id_ed25519: Operation not permitted
+sh: /Users/me/elsewhere.txt: Operation not permitted
+curl: (6) Could not resolve host: example.com
+[porta] the sandbox refused this run 3 times; what each would have needed:
+ file-read-data /Users/me/.ssh/id_ed25519
+ → closed by the preset or --deny-read; no mount reopens it
+ file-write-create /Users/me/elsewhere.txt
+ → -v /Users/me
+ …
```
-A single binary for macOS on Apple silicon and Linux on x86-64 and arm64,
-checked against its published SHA-256 before it is installed, and against
-the release's Sigstore signature where `cosign` is installed
-([verifying a release](docs/cli.md#verifying-a-release)). Or, with
-[Almide](https://github.com/almide/almide), the way `go install` does it
-— the route for an Intel Mac or any other target:
+`out.txt` was written; the key, the file outside and the network were not.
-```bash
-almide install github.com/almide/porta --branch main
-```
-
-`main` is the latest release; `--tag v0.6.6` pins one. It builds from source
-into `~/.local/bin` and takes a few minutes (the Rust side compiles
-Wasmtime). Building by hand is in
-[the CLI reference](docs/cli.md#build-from-source). `porta
-run` takes either a native command or a `.wasm` module, so anything that
-compiles to WASI runs under it.
-
-On Debian and Ubuntu, each release also carries a `.deb` (amd64, arm64).
-Installed with `sudo apt install ./porta__amd64.deb`, it puts porta
-at `/usr/bin/porta` and, on Ubuntu 23.10 and later, loads the AppArmor
-profile that lets porta give each command its own namespaces. Installed any
-other way, `sudo porta setup` does the same: it copies porta to a root-owned
-`/usr/local/bin/porta` and loads the profile for that copy alone.
-
-In a GitHub Actions workflow, the action installs a release the same checked
-way and, on Ubuntu runners, loads the AppArmor profile that gives porta the
-user namespaces the runner otherwise withholds:
-
-```yaml
-- uses: almide/porta@v0.6.12
-- run: porta run ./scripts/untrusted-step.sh -v .
-```
+Porta wraps one process and everything it starts. Writes go only to the
+directories you mount, credential stores stay unreadable, the network is
+narrowed to the ports or hosts you name, and CPU, memory, processes and time
+are capped. It is enforced by Seatbelt on macOS and by Landlock, seccomp and
+namespaces on Linux — not by a wrapper that asks nicely — and a rule the
+kernel cannot express refuses the run instead of weakening it.
-## Use it for
-
-Everything before `--` is porta's; everything after belongs to the command.
-
-### Restrict an AI agent you already run
-
-The agent installs deps, runs tests, edits files — on the machine that holds
-your keys. Give it one directory and one host, and close the rest.
+## Install
```bash
-cd project
-porta init claude # or: porta init codex
-porta up -- -p "Fix the bug in main.rs"
+curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.sh | bash
```
-`claude` runs unchanged, but it can write only inside the project and its own
-`~/.claude` — where its settings, commands and global `CLAUDE.md` stay
-unwritable, so one session cannot plant hooks for the next — and it cannot
-read `~/.ssh`, `~/.aws` or the Keychain. The recipe is a commented
-`porta.toml`, measured to what the agent needs; on macOS it takes its login
-as `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`),
-since the Keychain is closed. No Docker daemon, no image, no change to the
-agent. Add `--snapshot` and porta lists what the session changed afterwards;
-`porta rollback --yes` puts it back.
+| Where | How |
+|---|---|
+| macOS (Apple silicon), Linux (x86-64, arm64) | the script above: checks the SHA-256, and the Sigstore signature where `cosign` is installed |
+| Debian, Ubuntu | `sudo apt install ./porta__amd64.deb` from a [release](https://github.com/almide/porta/releases); on Ubuntu 23.10+ it also loads the AppArmor profile porta's namespaces need |
+| Ubuntu, installed any other way | `sudo porta setup` once, for the same profile |
+| GitHub Actions | `- uses: almide/porta@v0.6.16`, then `porta run …` in any step |
+| Anything else (Intel Mac…) | `almide install github.com/almide/porta --branch main` builds from source |
-### Give a tool one API, and log every attempt
+Every release is signed and carries build provenance —
+[verifying a release](docs/cli.md#verifying-a-release).
-A tool needs one endpoint and nothing else. Route egress through porta's proxy
-and keep the record.
+## Confine the coding agent you already use
```bash
-porta run ./agent --proxy-allow 'api.example.com' --proxy-audit egress.jsonl -v ./work
+cd my-project
+porta init claude # or: porta init codex
+porta up -- -p "Fix the failing test"
```
-Only `api.example.com` is reachable; direct TCP, UDP and Unix-socket egress are
-denied, a name resolving to a cloud-metadata or link-local address is refused,
-and every decision lands in `egress.jsonl`.
-
-### Try an unknown command without handing over the machine
+`porta init` writes a commented `porta.toml`, measured to what that agent
+needs: the project and the agent's own directory writable, its settings,
+hooks and global instructions there unwritable — so one session cannot plant
+something for the next — and its API key or token passed by name. On macOS
+the Keychain is closed, so the login goes in as `ANTHROPIC_API_KEY` or
+`CLAUDE_CODE_OAUTH_TOKEN` (`claude setup-token`).
-Preview the policy before anything runs, then run it boxed in, with a deadline
-and resource ceilings the kernel enforces on everything it starts.
+Any other command works the same way, with flags:
```bash
-porta explain ./sketchy-installer -v ./sandbox --allow-net github.com:443 # see the policy, run nothing
-porta run ./sketchy-installer -v ./sandbox --allow-net github.com:443 \
- --timeout 60 --max-cpu 30 --max-procs 500 --max-file-size 200
+porta run ./installer -v ./sandbox --allow-net '*:443' --timeout 120 --max-procs 500
```
-A hang is killed at the deadline, a CPU burn ends with SIGXCPU, a fork bomb
-cannot fork, and a file stops growing at the ceiling. `--max-memory-mb 512`
-bounds resident memory for the whole run: a cgroup v2 ceiling on Linux (a
-systemd user session is needed, and the flag is refused without one), a
-supervisor that ends the run at the ceiling on macOS.
+Everything before `--` is porta's; everything after is the command's.
-### Run untrusted or generated WASM
+## What it stops
-Code from a user or a model, run with no host filesystem or network and bounded
-fuel, memory and time. A core module or a WASI 0.2 or 0.3 component, checked
-the same way: every import it declares needs a capability you granted.
+| | macOS | Linux |
+|---|---|---|
+| **Writes** | only the `-v` mounts, `/tmp`, `/dev` | same |
+| **Inside a writable mount** | `.git/hooks`, `.git/config`, `.envrc`, shell rc files, agent settings and the like stay unwritable and cannot be renamed away | same (needs user namespaces — see below) |
+| **Reads** | everything but credential stores: `~/.ssh`, `~/.aws`, `~/.config/gh`, cloud and registry tokens, the Keychain, browser profiles; `--read-policy strict` confines reads to the mounts | same, minus the Keychain |
+| **Credential sockets** | SSH agent, gpg-agent, `docker.sock` refused unless `--allow-unix` | same |
+| **Network** | open, or TCP by port (`--allow-net`), or by host through porta's proxy (`--proxy-allow`), or none (`--no-net`) | same; UDP closed under a port rule |
+| **Other processes** | their arguments and environment unreadable; `open(1)` / Launch Services closed | not visible: own PID namespace |
+| **Resources** | `--timeout`, `--max-cpu`, `--max-procs`, `--max-file-size`, `--max-memory-mb` | same (memory through cgroup v2) |
+| **Environment** | starts empty; `PATH`, `HOME`, locale and terminal cross, the rest only by `-e` / `--env-pass` | same |
-```bash
-porta run plugin.wasm --profile worker --step-limit 5000000 --max-memory 256
-```
+What is closed is a preset — [`native/presets/default.toml`](native/presets/default.toml),
+shown by `porta explain` — not a list in code: `--deny-read`, `--protect` and
+`--deny-unix` add to it, `--preset ` replaces it. The full table, with
+the reasons, is in [enforcement](docs/enforcement.md).
-### Build an agent whose decision loop is WASM
-
-`porta run` restricts an agent someone else wrote. `porta agent` runs one whose
-loop is itself WASM: the loop and every tool run in separate instances that
-inherit no environment or directory, model credentials stay in the host, and a
-crashed run resumes without repeating completed writes.
+## When something is refused
```bash
-porta agent agent.toml --record run.jsonl -- "Add 20 and 22 using the tool."
-porta agent-resume agent.toml run.jsonl # completed writes are not repeated
-porta agent-journal run.jsonl # read-only metadata, no code loaded
+porta explain ./tool -v ./work --allow-net '*:443' # the policy, in words; nothing runs
+porta run ./tool -v ./work --why # afterwards: each refusal and the flag it needed
```
-See [agents you build](docs/agent-runtime.md), [completion
-checks](docs/completion-checks.md) the guest cannot bypass, and [artifact
-pins](docs/artifact-pins.md) that bind WASM to a reviewed SHA-256.
-
-### Keep the settings as project config
-
-```bash
-porta init native mytool # writes a porta.toml to edit
-porta up -- --some-flag
+```text
+[porta] the sandbox refused this run 2 times; what each would have needed:
+ file-write-data /home/me/notes/out.txt
+ → -v /home/me/notes
+ network-outbound remote:*:80
+ → --allow-net '*:80'
+ to run again with those granted:
+ porta run ./tool -v ./work -v /home/me/notes --allow-net '*:80'
```
-Or let a working invocation write its own: `porta explain claude … --save
-porta.toml`.
-
-## See a restriction actually stop something
+On macOS this comes from the kernel's denial log after any failed run; on
+Linux, `--why` traces the run with `strace` from outside the sandbox.
-A working restriction is invisible, so watch one fail.
+## Undo what it changed
```bash
-porta run curl -- https://example.com # network open by default
-porta run curl --allow-net '*:443' -- https://example.com # HTTPS allowed → works
-porta run curl --allow-net '*:80' -- https://example.com # → exit 7, port 443 denied
+porta run ./agent -v ./project --snapshot # copies the mounts first; lists what changed after
+porta rollback --yes # puts them back
```
-A refused run does not leave you guessing: after a non-zero exit, porta reads
-the kernel's denial records and prints which flag each refusal would have
-needed (on macOS after every failed run; on Linux with `--why`, which traces
-the run with `strace`). Exit codes tell a script
-apart a command that failed from one that never ran — see
-[the CLI reference](docs/cli.md#when-a-run-is-refused).
-
-## Evidence
-
-Every published number keeps its raw report, source hashes and an audit that
-regrades it; CI runs those audits, and results that do not favour Porta are
-published in the same place.
-
-- [Escape corpus](docs/benchmarks/escapes.md) — known jailbreaks run against the
- binary, scored on what escaped, losing rows included.
-- [Startup and memory](docs/benchmarks/startup-and-memory.md) — fixed-response
- measurements with explicit comparison limits.
-- [Containment and recovery](docs/benchmarks/containment-evaluation.md) — under
- hostile inputs; one of five scenarios separated the runtimes, and containment
- cost task completion.
-- [Real-task quality](docs/benchmarks/README.md) — Porta has not demonstrated
- general quality superiority, and the shared-compute follow-up was rejected
- rather than published as a win.
+A clone on APFS, so the copy costs next to nothing there. Snapshots live
+outside every mount, so the command cannot rewrite its own undo.
+
+## How it compares
+
+The same [escape corpus](docs/benchmarks/escapes.md) — real ways out, scored
+on what got through — run under five tools, each with its own defaults
+([full comparison](docs/benchmarks/competitors.md), losing rows included):
+
+| | porta | [srt](https://github.com/anthropics/sandbox-runtime) | [Fence](https://github.com/fencesandbox/fence) | [nono](https://github.com/nolabs-ai/nono) | [landrun](https://github.com/Zouuup/landrun) |
+|---|---|---|---|---|---|
+| macOS: tried / escaped | **22 / 0** | 17 / 5 | 14 / 5 | 13 / 5 | — |
+| Linux: tried / escaped | **28 / 0** | 23 / 2 | 21 / 3 | 24 / 5 | 22 / 7 |
+
+What the others do that porta does not: srt and Fence close the network by
+default; Fence filters commands by name; nono can ask for more access
+mid-run. What a command pays under porta: about 16 ms on macOS, 2–6 ms on
+Linux ([overhead](docs/benchmarks/overhead.md)).
+
+## Limits
+
+- **The network is open by default.** Close it with `--no-net`, `--allow-net`
+ or `--proxy-allow`.
+- **On Linux, some protections need user namespaces** — the hooks inside a
+ mount, credential sockets, hiding other processes. Ubuntu 23.10+ restricts
+ them until the `.deb` or `sudo porta setup` loads a profile; without one
+ porta runs and says what is left open.
+- **Same kernel.** This is a process sandbox, not a VM: a kernel exploit is
+ out of scope. The [threat model](docs/threat-model.md) lists what is and is
+ not defended.
+- **Tested by its authors.** The corpus, the fuzzer and the monkey test are
+ public and run in CI; there has been no third-party audit.
+
+## Also in the box
+
+- **WASM with no ambient authority.** `porta run plugin.wasm --profile worker`
+ runs a core module or a WASI 0.2/0.3 component with no host filesystem or
+ network and bounded fuel, memory and time; every import needs a capability
+ you granted.
+- **Agents whose loop is WASM.** `porta agent agent.toml` runs the decision
+ loop and each tool in separate instances, keeps model credentials in the
+ host, and resumes a crashed run without repeating completed writes
+ ([agent runtime](docs/agent-runtime.md)).
+- **One API and a log.** `--proxy-allow api.example.com --proxy-audit
+ egress.jsonl` lets through only that host and records every decision.
## Docs
-- [CLI reference](docs/cli.md) — every command, flag, `porta.toml` key and exit code.
-- [Enforcement](docs/enforcement.md) — exactly what macOS and Linux each stop.
-- [Threat model](docs/threat-model.md) — what it defends against, and what it does not.
-- [Architecture](docs/architecture.md) — Almide decides policy, Rust enforces it.
-
-## License
+- [CLI reference](docs/cli.md) — every command, flag, `porta.toml` key and exit code
+- [Enforcement](docs/enforcement.md) — exactly what each platform stops, and how
+- [Threat model](docs/threat-model.md) — what it defends against, and what it does not
+- [Benchmarks](docs/benchmarks/README.md) — the escape corpus, the comparison, overhead
+- [Architecture](docs/architecture.md) — Almide decides policy, Rust enforces it
-Apache-2.0
+Built with [Almide](https://github.com/almide/almide) and
+[Wasmtime](https://wasmtime.dev). Apache-2.0.
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..d9ac93c
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,169 @@
+
+
+
+
+Porta
+
+
+ 无论是命令还是 AI 智能体,都只按你授予的权限运行。
+ 由 macOS 和 Linux 内核强制执行。无需容器,无需守护进程。
+
+
+
+
+
+
+
+
+---
+
+```text
+$ porta run sh -v ./work --no-net -- -c 'echo ok > out.txt; cat ~/.ssh/id_ed25519; echo x > ~/elsewhere.txt; curl https://example.com'
+cat: /Users/me/.ssh/id_ed25519: Operation not permitted
+sh: /Users/me/elsewhere.txt: Operation not permitted
+curl: (6) Could not resolve host: example.com
+[porta] the sandbox refused this run 3 times; what each would have needed:
+ file-read-data /Users/me/.ssh/id_ed25519
+ → closed by the preset or --deny-read; no mount reopens it
+ file-write-create /Users/me/elsewhere.txt
+ → -v /Users/me
+ …
+```
+
+`out.txt` 写入成功;密钥、挂载之外的文件和网络都没有被触及。
+
+Porta 包裹一个进程及其启动的所有子进程。写入仅限于你挂载的目录,凭据存储不可读,
+网络收窄到你指定的端口或主机,CPU、内存、进程数和时间都有上限。它在 macOS 上由
+Seatbelt 强制执行,在 Linux 上由 Landlock、seccomp 和命名空间强制执行——而不是一个
+“礼貌请求”的包装器。内核无法表达的规则会直接拒绝运行,而不是悄悄放宽。
+
+## 安装
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.sh | bash
+```
+
+| 平台 | 方法 |
+|---|---|
+| macOS (Apple silicon)、Linux (x86-64, arm64) | 上面的脚本:校验 SHA-256,安装了 `cosign` 时还会校验 Sigstore 签名 |
+| Debian、Ubuntu | 从 [release](https://github.com/almide/porta/releases) 下载后 `sudo apt install ./porta__amd64.deb`;在 Ubuntu 23.10+ 上还会加载命名空间所需的 AppArmor 配置 |
+| 以其他方式安装的 Ubuntu | 运行一次 `sudo porta setup`,加载同样的配置 |
+| GitHub Actions | `- uses: almide/porta@v0.6.16`,之后任意步骤中都可 `porta run …` |
+| 其他 (Intel Mac 等) | `almide install github.com/almide/porta --branch main` 从源码构建 |
+
+每个 release 都带有签名和构建来源证明——
+[验证 release](docs/cli.md#verifying-a-release)。
+
+## 约束你正在使用的编码智能体
+
+```bash
+cd my-project
+porta init claude # 或: porta init codex
+porta up -- -p "Fix the failing test"
+```
+
+`porta init` 会写出一个带注释的 `porta.toml`,按该智能体实际所需量身定制:项目和智能体
+自己的目录可写,其中的设置、钩子和全局指令不可写——这样一次会话无法为下一次会话埋下
+东西——API 密钥或令牌按名称传入。macOS 上钥匙串是关闭的,所以登录凭据通过
+`ANTHROPIC_API_KEY` 或 `CLAUDE_CODE_OAUTH_TOKEN`(`claude setup-token`)传入。
+
+其他命令同样用参数即可:
+
+```bash
+porta run ./installer -v ./sandbox --allow-net '*:443' --timeout 120 --max-procs 500
+```
+
+`--` 之前是 porta 的参数,之后是命令的参数。
+
+## 它阻止什么
+
+| | macOS | Linux |
+|---|---|---|
+| **写入** | 仅 `-v` 挂载、`/tmp`、`/dev` | 相同 |
+| **可写挂载内部** | `.git/hooks`、`.git/config`、`.envrc`、shell rc 文件、智能体设置等保持不可写,也无法被重命名移走 | 相同(需要用户命名空间——见下文) |
+| **读取** | 除凭据存储外均可读:`~/.ssh`、`~/.aws`、`~/.config/gh`、云与镜像仓库令牌、钥匙串、浏览器配置均关闭;`--read-policy strict` 将读取也限制在挂载内 | 相同(钥匙串除外) |
+| **凭据套接字** | SSH agent、gpg-agent、`docker.sock` 除非 `--allow-unix` 否则拒绝 | 相同 |
+| **网络** | 开放,或按端口放行 TCP(`--allow-net`),或经 porta 代理按主机放行(`--proxy-allow`),或完全关闭(`--no-net`) | 相同;按端口放行时 UDP 关闭 |
+| **其他进程** | 其参数和环境变量不可读;`open(1)` / Launch Services 关闭 | 不可见:独立的 PID 命名空间 |
+| **资源** | `--timeout`、`--max-cpu`、`--max-procs`、`--max-file-size`、`--max-memory-mb` | 相同(内存通过 cgroup v2) |
+| **环境变量** | 从空开始;只传递 `PATH`、`HOME`、区域设置和终端,其余仅限 `-e` / `--env-pass` 指定的 | 相同 |
+
+关闭哪些内容由预设决定——[`native/presets/default.toml`](native/presets/default.toml),
+可用 `porta explain` 查看——而不是写死在代码里:`--deny-read`、`--protect`、
+`--deny-unix` 在其上追加,`--preset ` 整体替换。附带理由的完整表格见
+[enforcement](docs/enforcement.md)。
+
+## 被拒绝时
+
+```bash
+porta explain ./tool -v ./work --allow-net '*:443' # 用文字描述策略,不执行任何东西
+porta run ./tool -v ./work --why # 运行后:每次拒绝及其所需的参数
+```
+
+```text
+[porta] the sandbox refused this run 2 times; what each would have needed:
+ file-write-data /home/me/notes/out.txt
+ → -v /home/me/notes
+ network-outbound remote:*:80
+ → --allow-net '*:80'
+ to run again with those granted:
+ porta run ./tool -v ./work -v /home/me/notes --allow-net '*:80'
+```
+
+在 macOS 上,任何失败的运行之后都会从内核的拒绝日志中给出这些信息;在 Linux 上,
+`--why` 在沙箱外用 `strace` 跟踪运行。
+
+## 撤销它做的改动
+
+```bash
+porta run ./agent -v ./project --snapshot # 先复制挂载目录;结束后列出改动
+porta rollback --yes # 恢复原样
+```
+
+在 APFS 上是克隆,复制几乎零成本。快照存放在所有挂载之外,命令无法改写自己的撤销手段。
+
+## 对比
+
+同一套[逃逸用例](docs/benchmarks/escapes.md)——真实的逃逸手法,按实际突破的数量计分——
+在五个工具下各自以默认配置运行([完整对比](docs/benchmarks/competitors.md),包括 porta
+落后的项):
+
+| | porta | [srt](https://github.com/anthropics/sandbox-runtime) | [Fence](https://github.com/fencesandbox/fence) | [nono](https://github.com/nolabs-ai/nono) | [landrun](https://github.com/Zouuup/landrun) |
+|---|---|---|---|---|---|
+| macOS:尝试 / 逃逸 | **22 / 0** | 17 / 5 | 14 / 5 | 13 / 5 | — |
+| Linux:尝试 / 逃逸 | **28 / 0** | 23 / 2 | 21 / 3 | 24 / 5 | 22 / 7 |
+
+其他工具有而 porta 没有的:srt 和 Fence 默认关闭网络;Fence 按名称过滤命令;nono
+可在运行中申请更多权限。porta 的开销:macOS 约 16 ms,Linux 2–6 ms
+([overhead](docs/benchmarks/overhead.md))。
+
+## 局限
+
+- **网络默认开放。** 用 `--no-net`、`--allow-net` 或 `--proxy-allow` 关闭。
+- **在 Linux 上,部分保护需要用户命名空间**——挂载内的钩子、凭据套接字、隐藏其他进程。
+ Ubuntu 23.10+ 会限制它们,直到 `.deb` 或 `sudo porta setup` 加载配置;没有配置时
+ porta 照常运行,并说明哪些仍然开放。
+- **共享内核。** 这是进程沙箱,不是虚拟机:内核漏洞不在防护范围内。
+ [威胁模型](docs/threat-model.md)列出了防护与不防护的内容。
+- **由作者测试。** 逃逸用例、模糊测试和猴子测试均公开并在 CI 中运行;尚未经过第三方审计。
+
+## 其他功能
+
+- **无环境权限的 WASM。** `porta run plugin.wasm --profile worker` 运行核心模块或
+ WASI 0.2/0.3 组件,不接触宿主文件系统和网络,fuel、内存和时间均有上限;每个导入都
+ 需要你授予的能力。
+- **循环本身是 WASM 的智能体。** `porta agent agent.toml` 将决策循环和每个工具放在
+ 独立实例中运行,模型凭据留在宿主,崩溃的运行可恢复且不会重复已完成的写入
+ ([agent runtime](docs/agent-runtime.md))。
+- **一个 API 加一份日志。** `--proxy-allow api.example.com --proxy-audit egress.jsonl`
+ 只放行该主机,并记录每一次决策。
+
+## 文档
+
+- [CLI 参考](docs/cli.md) — 所有命令、参数、`porta.toml` 键和退出码
+- [Enforcement](docs/enforcement.md) — 各平台究竟阻止什么、如何阻止
+- [威胁模型](docs/threat-model.md) — 防护什么,不防护什么
+- [基准测试](docs/benchmarks/README.md) — 逃逸用例、对比、开销
+- [架构](docs/architecture.md) — 策略由 Almide 决定,由 Rust 强制执行
+
+基于 [Almide](https://github.com/almide/almide) 和 [Wasmtime](https://wasmtime.dev) 构建。Apache-2.0。
diff --git a/native/denial_advice.rs b/native/denial_advice.rs
index 2176caa..4e7bd6b 100644
--- a/native/denial_advice.rs
+++ b/native/denial_advice.rs
@@ -64,6 +64,9 @@ fn outbound_advice(target: &str) -> String {
Some((_, port)) => format!("--allow-net '*:{port}'"),
None => format!("--allow-net '{endpoint}'"),
},
+ // The resolver's socket is closed with the network, not as a
+ // credential socket: reopening it alone resolves names nothing can reach.
+ None if target.ends_with("/var/run/mDNSResponder") => "name resolution, closed with the network: --allow-net or --proxy-allow reopens it".to_string(),
None if target.starts_with('/') => format!("--allow-unix {target}"),
None => "the network is closed by --allow-net; name the port to reach".to_string(),
}