[BUG] sandbox.filesystem の deny bind-mount が Bash コマンド終了後も残存・累積する(解除に sudo umount が必要)
不具合の概要
sandbox.filesystem.denyRead / denyWrite が、sandbox 構築時点で 実在するパスを保護する場合、bubblewrap は各 deny 対象パスに read-only のデバイスノードを bind-mount で被せます。この bind-mount が sandboxed な Bash コマンドの終了後に解除されず、ホスト側に残り、セッション内で 累積します。マスクされたパスはキャラクタスペシャルデバイスになり、以下の状態になります。
sandbox.excludedCommands(例:git)のコマンドでもunlinkできない →EBUSY("device or resource busy")- 通常の
rmでも削除できない sudo umount <path>(superuser)でしか解除できない。--dangerously-skip-permissions下の Claude Code 自身は sudo を実行できないため、エージェントは自力で後始末できない
結果として古い deny-mount が溜まり続け、エージェントが片付けられない状態で作業ツリーに残ります。
環境
- Claude Code: 2.1.179
- Platform: Linux 6.11.0-1016-nvidia, aarch64
- bubblewrap 0.9.0, socat 1.8.0.0
- AppArmor:
kernel.apparmor_restrict_unprivileged_userns=1,/etc/apparmor.d/bwrap設置済 .claude/settings.jsonのsandbox.filesystem.denyRead/denyWriteに**/グロブ(例:**/*.pem,**/.env)を使用
再現手順
sandbox.filesystem.denyRead(およびdenyWrite)に例えば**/*.pemを設定する。- ある Bash tool 呼び出しで:
touch ./probe.pem(ファイルが実在する)。 - 別の Bash tool 呼び出し(sandbox が再構築される)で:
cat ./probe.pem
→ 正しく Permission denied で拒否される(deny 自体は機能している ✅)。
- mount を確認:
/proc/mountsに
udev /abs/path/probe.pem devtmpfs ro,nosuid,nodev,... が現れる。
- 後始末を試す:
git clean -fx -- probe.pem→failed to remove ... : device or resource busy;
rm probe.pem → busy;umount probe.pem → must be superuser to unmount。
- この mount はコマンド終了後も、以降のコマンドをまたいでも残存する。別の deny 対象パスで
繰り返すと、古い mount がさらに累積する。
関連
#39284(非実在の denyRead パスで bwrap が mount エラーになる件)とは別物です。本件では deny 対象パスが
実在し、deny は正しく適用されています。問題は mount の 残存・リーク・累積であって、mount の失敗ではありません。
副次的な観測(優先度低)
deny が bind-mount ベースで sandbox 構築時に確定するため、グロブは sandbox 構築時に実在するファイルしか
保護しません。セッション中に作成されたセンシティブファイル(例: .env)は、それを作成したコマンド内では
保護されません(次のコマンドの sandbox 再構築時に初めて保護対象になります)。macOS(Seatbelt)は同じ設定でも
ポリシーが access 時に評価されるため runtime 生成ファイルも保護されます。このプラットフォーム差はドキュメント化の
価値があると思います。
期待される挙動
deny の bind-mount は sandbox の mount 名前空間内に閉じ込められ、コマンド終了時に解除されるべきです。
そうすればホストへリーク・累積したり、片付けに sudo umount が必要になったりしません。
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗