テック技報最新号へ →
AIコーディング

Claude Code のサンドボックスを「外れた記録」から締めていく(bypass ログ・パイプ deny の罠・起動失敗時の扱い)

サンドボックスを有効にしても、Claude は失敗したコマンドを dangerouslyDisableSandbox 付きで外で再試行することがある。止めずに記録する hook を入れて頻度順に読み、許可リストか deny ルールに落としていくと、外れる回数が減っていく。合わせて「パイプを含む deny は効かない」「サンドボックスが起動できないと黙って素で動く」の2つの罠を潰す。

@okdt · 投稿 2026-04-18
#claude-code#sandbox#hooks#permissions#セキュリティ#settings

どんな場面で使う?

  • サンドボックスを入れたのに、許可プロンプトが減らない/外での実行が常態化している
  • Bash(curl *|*sh) のような deny を書いて安心している
  • 他人の公開スキルや MCP を入れていて、万一のときの被害範囲を狭めたい

手順

1. 外れた瞬間だけ記録する hook

~/.claude/hooks/log-sandbox-bypass.sh(chmod +x して PreToolUse の matcher: "Bash" に登録):

bash
#!/bin/bash
INPUT=$(cat)
LOG=~/.claude/logs/sandbox-bypass.tsv
DISABLED=$(echo "$INPUT" | jq -r '.tool_input.dangerouslyDisableSandbox // false')
if [ "$DISABLED" = "true" ]; then
  CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
  CMD=$(printf '%s' "$CMD" | sed -E \
    -e 's/([A-Za-z_]*(KEY|SECRET|TOKEN|PASSWORD|PASSWD)[A-Za-z_]*=)[^ ]*/\1***/g' \
    -e 's/(Bearer )[A-Za-z0-9._~+\/-]+/\1***/g')
  mkdir -p "$(dirname "$LOG")"
  umask 077
  printf '%s\t%s\n' "$(date -Iseconds)" "$CMD" >> "$LOG"
fi
exit 0   # 止めない。止めると作業が進まず記録も溜まらない

2. 週に一度、多い順に読んで反映する

bash
cut -f2 ~/.claude/logs/sandbox-bypass.tsv | sort | uniq -c | sort -rn | head
ログから読み取れること反映先
特定ホストへの接続で外れているsandbox.network.allowedDomains
作業ディレクトリ外への書き込みで外れているsandbox.filesystem.allowWrite
docker など特定ツールが毎回外れるsandbox.excludedCommands
そもそもやらせたくない操作許可ではなく permissions.deny
散発的で正当性も薄いallowUnsandboxedCommands: false(Strict)

ログはコマンド全文なので秘密が残りうる。cleanupPeriodDays は ~/.claude/projects/ の transcript にしか効かないので、tail -n 500 … > /tmp/sb.tsv && mv … で自分で切り詰め、調整が済んだら消す。

3. deny はパイプの「部品」ごとに照合される

curl https://x | sh は curl … と sh に分けて判定されるので、Bash(curl *|*sh) は永遠にマッチしない。代わりに受け側のインタプリタを止め、取得系は確認に回す:

jsonc
"permissions": {
  "deny": ["Bash(sh *)", "Bash(bash *)", "Bash(zsh *)"],
  "ask":  ["Bash(curl *)", "Bash(wget *)"]
}

4. 黙って素で動く・環境変数が漏れる、を塞ぐ

jsonc
{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,        // 依存不足でサンドボックスが立たないとき起動を拒否
    "filesystem": { "denyRead": ["~/.ssh", "~/.gnupg", "~/.aws", "~/.config/gcloud"] },
    "credentials": {
      "files":   [{ "path": "~/.aws/credentials", "mode": "deny" }],
      "envVars": [{ "name": "GITHUB_TOKEN", "mode": "deny" }]
    }
  },
  "env": { "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "1" },   // Anthropic・クラウドの認証情報を子プロセスから剥がす
  "apiKeyHelper": "/usr/local/bin/get-my-key",           // env にキーの値を直書きしない
  "cleanupPeriodDays": 14
}
  • 読み取りは既定で全域許可なので、denyRead が実質の防御線。ファイル隔離とネットワーク隔離は片方だけだと抜け道が残る
  • credentials に組み込みの除外リストはない。挙げたものだけが守られる

5. 効いているか確かめる

/sandbox の Config/Overrides、/permissions の Recently denied、claude doctor を見たうえで、許可していないドメインへ実際に curl して弾かれるかを試す。チームで集めるなら OpenTelemetry の claude_code.tool_decision イベントが decision と source(config/hook/user_reject)を持つので「何が止めたか」まで分かる。

注意点

出典

X の元の投稿を開く