Claude Media
Claude CodeでSalesforce CLIの権限を切る — 本番deployを止める設定

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 0

settings.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. 1

    sandbox だけを認証した状態にする

    本番へのログインは、別の端末か別のユーザーで行います。Claude Codeを動かす環境では、sf org login web --alias dev-sandbox --instance-url ...でsandboxだけにログインします。

  2. 2

    残っている本番の認証を外す

    sf org listで認証済みorgを確認し、本番が残っていればsf org logout --target-org prod-orgで外します。--no-promptを付けなければ、確認が出ます。

  3. 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での成功をそのまま本番の安全と読み替えないほうが無難です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/permissions
  2. code.claude.com/docs/en/hooks-guide
  3. github.com/salesforcecli/plugin-deploy-retrieve
  4. github.com/salesforcecli/plugin-auth
  5. github.com/salesforcecli/plugin-settings
この記事を共有:XはてブLinkedIn