Claude CodeでSalesforce CLIの権限を切る — 本番deployを止める設定
Claude Codeにsfコマンドを使わせるとき、sandboxへのdeployだけを通し、本番orgへのdeployは止めるpermissionsの書き方。エイリアスの罠とhooksでの補強まで。
Salesforce CLI(sf)をClaude Codeに使わせる場合、決めておくのは「どの組織(org)に対する操作を通すか」です。sandboxへのdeployは任せ、本番orgへのdeployは止める。この線引きは、settings.jsonのpermissionsで書けます。
ただし、書き方には3つの落とし穴があります。sfにはコマンドの別名があること。orgをエイリアスでもユーザー名でも指定できること。--target-orgを省くと既定のorgに向かうことです。この記事では、この3つを踏まえた設定例と、ルールだけでは足りない部分の補い方を示します。
先に結論: 許可は狭く、禁止は広く書く
permissionsの評価順は、denyが先で、askが次、allowが最後です。最初に当たったルールで結果が決まり、具体的なallowでもdenyの例外にはなりません。
ここから設計の方針が決まります。
ルール設計の3原則
allowは sandbox の alias を文字列で固定
--target-org dev-sandboxまで含めて書きます。orgを省いた呼び出しはallowに当たらず、確認に回ります。denyは本番の識別子が含まれる sf コマンド全部
サブコマンドを個別に列挙せず、本番aliasの文字列が現れた時点で止めます。
別名と設定変更の入口を塞ぐ
sf deploy metadataなどの別名、sf config set、sf org loginをdenyに入れます。
本番deployを止める設定ファイル
前提として、sandboxのエイリアスをdev-sandbox、本番orgのエイリアスをprod-orgとします。エイリアスはsf org login web --alias prod-orgのように、ログイン時に付けられます。組織の命名規則に合わせて読み替えてください。
{
"permissions": {
"allow": [
"Bash(sf project deploy start * --target-org dev-sandbox)",
"Bash(sf project deploy start --target-org dev-sandbox *)",
"Bash(sf project deploy preview * --target-org dev-sandbox)",
"Bash(sf project retrieve start * --target-org dev-sandbox)",
"Bash(sf org list)"
],
"deny": [
"Bash(sf *prod-org*)",
"Bash(sf *prod-admin@example.com*)",
"Bash(sf project deploy quick *)",
"Bash(sf deploy metadata *)",
"Bash(sf force *)",
"Bash(sf config set *)",
"Bash(sf org login *)",
"Bash(sf org logout *)"
]
}
}ワイルドカードの規則は公式のBashルールの説明に沿っています。*は空白を含む任意の文字列に当たり、末尾の*は引数なしの呼び出しにも当たります。内容ごとに見ていきます。
allow: sandboxへのdeployだけを通す
sf project deploy startは、--source-dir force-appのようなフラグを先に置く書き方も、--target-orgを先に置く書き方もできます。そのため、allowは位置の違う2行にしています。
deployの--target-orgは、target-orgの設定変数がなければ必須のフラグです。逆に言うと、設定変数があると省略できます。allowが--target-org dev-sandboxを含む形に限られているので、省略した呼び出しはallowに当たりません。許可のない呼び出しは、通常の権限フローで確認が出ます。
sf project deploy previewは、デプロイされる内容と競合の可能性、無視されるファイルを事前に見るコマンドです。sf project retrieve startは組織から手元へ取り込む操作で、本番へ書き込む向きではありません。この2つも、orgを固定したうえで許可しています。
deny: 本番の文字列が出たら止める
Bash(sf *prod-org*)は、sfと空白のあとにprod-orgを含む文字列が続く呼び出しすべてに当たります。*とprod-orgの間に空白を入れていないので、--target-org=prod-orgのように=でつないだ形にも当たります。sf * prod-org*と書くと、prod-orgの直前に空白が必要になり、=の形を取りこぼします。次の形は、いずれも1行で止まります。
| 呼び出しの形 | 当たるルール |
|---|---|
sf project deploy start --target-org prod-org | 当たるルールsf *prod-org* |
sf project deploy start -o prod-org | 当たるルール同上 |
sf project deploy start --target-org=prod-org | 当たるルール同上 |
sf project deploy start --source-dir force-app --target-org dev-sandbox --target-org prod-org | 当たるルール同上(denyが先に評価される) |
sf project deploy quick --job-id 0Af... | 当たるルールsf project deploy quick * |
sf deploy metadata --target-org prod-org | 当たるルールsf *prod-org*、sf deploy metadata * |
この表は、公式のワイルドカード規則とdeny先行の評価順から導いた振り分けです。自分の環境に入れたら、/permissionsで各ルールの読み込み元を確認し、sandbox向けの呼び出しで実際の挙動を見ておくと安心です。
sf project deploy quickは、別扱いにしています。検証済みのデプロイをジョブIDで反映するコマンドで、--target-orgは必須ではありません。公式の説明では本番org向けのコマンドで、sandboxにはsf project deploy startを使うよう書かれています。sandbox向けのallowに入れる理由がなく、ジョブIDだけの呼び出しでは本番aliasの文字列も現れないため、コマンドごと止めます。
別名と設定変更の入口を塞ぐ
sf deploy metadataはproject deploy startの別名
公式のコマンド一覧には、sf project deploy startの別名としてsf deploy metadataが載っています。validateやquickにも、同じ形の別名があります。sf project delete sourceにはsf force source deleteという別名があります。
allowは正規の名前だけを書いているので、別名の呼び出しはallowに当たりません。ただし、別名経由でも本番aliasが含まれればsf *prod-org*に当たります。それでもsf deploy metadata *とsf force *をdenyに加えておくのは、別名の呼び出しを丸ごと止め、Claudeが正規の名前に寄せて書き直すよう促すためです。
既定のorgを書き換えさせない
sf config set target-org prod-orgのように、既定のorgに本番を指定する操作は、--target-orgなしのdeployを本番に向けます。環境変数SF_TARGET_ORGを設定した場合は、それがtarget-orgの設定変数より優先されます。
sf config set *をdenyに入れると、Claude経由でこの設定は変えられません。sf org login *も同じ理由で入れています。新しいorgを認証する操作を、Claudeの手から外すためです。
起動前に、既定のorgがどこに向いているかも確かめておきます。
sf config get target-org --verbose--verboseを付けると、値がプロジェクト単位(local)か全体(global)のどちらで設定されたものかも表示されます。ここに本番が出ているなら、先に外してください。
落とし穴
本番のユーザー名で指定されると alias の deny をすり抜ける
--target-orgは「エイリアスまたはユーザー名」を受け取ります。prod-orgだけをdenyに書いても、本番のログインユーザー名(例: prod-admin@example.com)を直接指定した呼び出しには当たりません。設定例の2行目(sf *prod-admin@example.com*)は、そのための穴埋めです。本番のユーザー名は組織ごとに異なるので、自分のものに差し替えてください。
ルールの限界と、hooksでの補強
Bashのdenyは、Claudeが書いたコマンドの文字列と照合する仕組みです。公式も、denyやaskは同じプログラムの別の呼び方までは止めず、セキュリティの境界ではないと説明しています。たとえばgit pushのdenyは、git -C . pushを止めません。
sfに置き換えると、/usr/local/bin/sf ...のようにパス付きで呼ぶ形や、bash -c 'sf ...'の中に書く形がこれに当たります。変数に本番aliasを入れて渡す書き方も、文字列としてはprod-orgが現れません。
文字列の照合を強める手段として、PreToolUseフックがあります。本番に繋ぐ構成の一般論はMCPで本番環境に接続する権限設計にまとめています。フックはコマンド全体を自分のロジックで検査でき、exit code 2で呼び出しを止められます。さらに、フックのブロックはallowルールより先に効きます。
#!/bin/bash
# .claude/hooks/block-sf-prod.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
# sf / sfdx を呼ぶコマンドに、本番の識別子が含まれていたら止める
if echo "$COMMAND" | grep -Eq '(^|[[:space:]/;&|"'\''])(sf|sfdx)([[:space:]]|$)' \
&& echo "$COMMAND" | grep -Eq 'prod-org|prod-admin@example\.com'; then
echo "Blocked: 本番orgを対象にしたSalesforce CLIの実行は許可していません" >&2
exit 2
fi
exit 0settings.jsonには、次の登録を足します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-sf-prod.sh"
}
]
}
]
}
}フックなら、パス付きの呼び出しやbash -c 'sf ...'の内側も、文字列としては拾えます。正規表現の文字クラスにシングルクォートを入れているのは、そのためです。それでも「コマンド文字列にprod-orgが現れる」ことが条件なので、変数経由の指定は拾えません。ここまでが、文字列検査でできる範囲です。
本当の境界は「本番に認証しない」こと
文字列のルールには限界があるので、確実に止めたいなら、本番orgに認証済みの状態をClaudeのセッションから切り離します。
SalesforceをMCP経由で操作する構成(ClaudeとSalesforceの連携方法)とは、権限の切り方が違います。MCPは接続先のロールで絞り、CLIはコマンドの文字列とorgの認証状態で絞ります。ほかのCLIでも同じ考え方を使えます。例としてSAP HANA CLIのMCPの権限設計や、Alembicのマイグレーションと本番DBの権限分離があります。
本番の認証情報をClaudeの手元から外す手順
- 1
sandbox だけを認証した状態にする
本番へのログインは、別の端末か別のユーザーで行います。Claude Codeを動かす環境では、
sf org login web --alias dev-sandbox --instance-url ...でsandboxだけにログインします。 - 2
残っている本番の認証を外す
sf org listで認証済みorgを確認し、本番が残っていればsf org logout --target-org prod-orgで外します。--no-promptを付けなければ、確認が出ます。 - 3
本番へのdeployは別の経路に置く
本番へのdeployは、CIなど人が承認する経路で実行します。PaaS系の本番deployの絞り方はPaaS系MCPの本番デプロイ権限にも例があります。Claudeには、sandboxで検証したdeployの結果とPull Requestまでを任せます。
sf org login webのsandbox向けの例では、--instance-url https://MyDomainName--SandboxName.sandbox.my.salesforce.comの形が使われています。本番にログインしていなければ、--target-org prod-orgを指定されても、そもそも使える認証情報が存在しません。
CLAUDE.mdには方針だけを書く
最後に、CLAUDE.mdにも次のような方針を書いておくと、Claudeが最初から正しい形のコマンドを組み立てやすくなります。
## Salesforce CLI
- deployとretrieveは必ず `--target-org dev-sandbox` を付ける
- 本番orgは扱わない。本番へのdeployが必要なときは、変更内容をPRにまとめて止まる
- `sf deploy metadata` などの別名は使わず、`sf project deploy start` を使うこれはClaudeの振る舞いを寄せるだけで、強制力はありません。公式も、プロンプトやCLAUDE.mdの指示は、Claude Codeが許可する範囲を変えないと説明しています。強制するのは、ここまでのpermissionsとhooksです。
sandboxでdeployを通した後の確認
sandboxへの反映を許可した場合、Claudeが出したdeployの結果は、--dry-runや--verboseを付けて確認できます。--dry-runは、デプロイの検証とApexテストの実行までを行い、組織には保存しません。本番に近い検証をsandbox側で済ませる使い方もあります。
なお、--test-levelのNoTestRunは、sandboxや開発者向けのorgなど、開発用の環境でのみ使えるテストレベルです。本番へのdeployでApexのクラスやトリガーを含む場合の既定はRunLocalTestsです。sandboxで通ったdeployが、本番ではテストやカバレッジの要件で変わりうるため、sandboxでの成功をそのまま本番の安全と読み替えないほうが無難です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。