Claude CodeでBigQueryのクエリを実行する際のガードレールを設計する

背景

  • Claude CodeにBigQueryのクエリを書かせていて、実行やクエリ結果の確認もClaude Codeにさせたい場面はあると思います
    • その場合、bqコマンドを手元から叩かせることが多いと思います
  • しかし、bqコマンドはBigQueryに関わるかなり広い範囲の強力なオペレーションもできてしまいます
    • データ基盤の運営に関わる人は強い権限などを持っていることも多いと思うので、その権限でどのようなコマンドが実行されうるかを把握した上でClaude Codeにbqコマンドを実行させるのは重要です
  • 以下、Claude Codeを前提に書いていますが、Codex CLIGemini CLIでも今回紹介する内容は通用すると思います

おさらい: bqコマンドで実行できること

基本のbq query: 思いのほか、多様な破壊的操作ができてしまう...

  • 基本的に想定しているのはこれでしょう
  • SELECT文を実行するだけだから、基本的には安心...」と思いたいですが、実はそんなことはないです
  • 以下のオプションは副作用を伴います
    • --replace(テーブルの洗い替え) / --destination_table(クエリ結果から新しくテーブルを作る) / --append_table(テーブルに行を追加する)
    • 「うまくいかないので、--replaceで綺麗にしてからやり直してみましょう」というのはClaude Codeは実行する場合がありえます
  • また、そもそもSELECT文以外がbq queryで実行されることも想定すべきです

その他の破壊的なオペレーション

bqコマンドはクエリ発行以外にも色々できる便利コマンドのため、Claude Codeがこれらのコマンドを実行し得ることは想定しておくべきです。

  • テーブルの削除 / 新規作成 / 意図しないところにコピー(見えてはいけない場所にコピーするなどのリスク)
    • bq rm
    • bq load
    • bq cp
    • bq extract
    • bq mk
  • テーブルの行や列などを追加 / 削除 / 上書き
    • bq update
    • bq insert
  • 他人のジョブを殺してしまう
    • bq cancel
  • 権限の設定の変更
    • bq set-iam-policy
    • bq add-iam-policy-binding
    • bq remove-iam-policy-binding

いかにClaude Codeが意図せぬクエリを実行できないようにするか

bqコマンド単体でかなり強力なことができることは分かりましたが、どうやって意図せぬコマンドを実行されないようにするか現実的な案を考えます。

なお、bq query以外の破壊的操作を伴なうサブコマンドについては、以下のようにsettings.jsonのpermissions.denyに放り込んでそもそも許可しないようにします。

settings.jsonの設定(クリックで開きます)

{
  "permissions": {
    "deny": [
      ...,
      "Bash(bq rm:*)",
      "Bash(bq load:*)",
      "Bash(bq cp:*)",
      "Bash(bq extract:*)",
      "Bash(bq mk:*)",
      "Bash(bq update:*)",
      "Bash(bq cancel:*)",
      "Bash(bq insert:*)",
      "Bash(bq shell:*)",
      "Bash(bq partition:*)",
      "Bash(bq set-iam-policy:*)",
      "Bash(bq add-iam-policy-binding:*)",
      "Bash(bq remove-iam-policy-binding:*)"
    ]
  },
}

これを前提に、以降はbq queryに対してどのようにガードレールを敷くかを考えていきます。

素朴だが面倒: クエリ実行を毎回Acceptする

一番簡単な方法です。Claude Codeが実行したいと言っているクエリを目視で確認して、毎回OKを出す方式です。確実ではありますが、課題がないわけではありません。例えば、以下のような課題がありえます。

  • 試行錯誤が多くなってくると、思考停止でAcceptしてしまう場合がある
    • コマンド実行なら一回だけで済む場合も多いと思いますが、クエリの場合は結果を見ながら調整することが多いため、Claude Codeであってもどうしても試行錯誤が多くなりがちです
  • 毎回Acceptするのが面倒になって、settings.jsonのpermissions.allowBash(bq query:*)と書いてしまう

特に後者は前述したクエリを介した破壊的な操作をされる場合があるので、基本的にやりたくないです。しかし、人間は面倒なことを嫌う生き物。もっとやりようがないかを考えてみましょう

危険なクエリかどうかをLLM Agentに判断させる

危険なクエリかどうかをちゃんと判断するのはそれなりに大変です。正規表現でマッチさせるのが簡単ではありますが、false positiveなマッチをしてしまう場合(危険ではないクエリを危険と判断してしまうケース)もあります。より正確にやろうとするとSQLをparseする方法もありますが、これは中々手間がかかります。

こういったケースの判断はLLM Agentがかなり得意です。もちろん、100%ではないですが、思考停止でAcceptしてしまう人間よりも正確に判断できます。どういったクエリやオプションが危ないかを列挙させておいたり(Few-shot prompting)、自信がない場合はdenyに倒すなどしておくと、ほぼ問題なく危険なクエリは弾いてくれました。具体的には、以下のようなカスタムスラッシュコマンドを用意しました。

Hookはexit codeによって挙動を変えることもできますが、その後のシェル芸が若干面倒です。その場合、JSON Outputを使うとHook後の挙動を制御できます。例えばこういった具合。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "DROP文によりテーブルを削除する危険な操作です"
  }
}

このカスタムスラッシュコマンドをClaude CodeのHook(具体的にはPreToolUse)に仕込んでおきます。私が作ったcchookでの設定例を以下に載せておきますが、素のHookでも簡単に設定できると思います。bq queryから始まるBashツールの使用でHookが発動します。

PreToolUse:
  - matcher: "Bash"
    conditions:
      - type: command_starts_with
        value: "bq query"
    actions:
      - type: command
        exit_status: 0
        command: |-
          echo '/cccsc:syou6162:claude-code-commands:validate_bq_query {.tool_input.command}' | claude --print

このHookが実際に動いている様子がこちらです。DDL操作以外にもクエリの実行前に危険なものは実行を止めてくれるガードレールの役割を果たします。

危険なクエリの実行をHookが止めてくれている様子

最小の権限を保持するサービスアカウントからクエリを実行させる

そもそも危険なコマンドを実行されたとしても、その権限を持っていなければ実行が失敗するので安心できるのではないか、というのがアイディアです。最小の権限を持ったユーザーアカウントを作るのは手間だし、推奨されないことが多いと思うので、ここでは最小の権限を持ったサービスアカウントを作ることを考えます。

ここでいう最小の権限は以下のような構成を想定しています。

  • roles/bigquery.jobUser
  • roles/bigquery.dataViewer / roles/bigquery.metadataViewer
    • プロジェクト単位だと広すぎることが多いので、データセット単位で付与するなどが現実的

この権限を持ったサービスアカントをユーザーアカウントから借用(impersonate)します。

また、サービスアカウントを作ったとして、キーを発行して手元にダウンロード、などはやりたくないです。キーのローテーションなども面倒。こういった場合は有効期間の短かい*1認証情報を使うとよいでしょう。

これを利用しておけば、万が一Claude Codeが危険なクエリを実行した際にも権限不足で失敗するので安心です。

クエリの実行がユーザーではなくサービスアカウント経由になるので、チームの誰が実行したクエリかが分かなくなる、などはデメリットとしてありそうです。

そもそもクエリを実行させたくない場合

これまでは「クエリが安全かどうかの条件を確認して、問題ない場合のみ実行させる」形でしたが、場合によっては一切クエリの実行を許可したくない場合があります。よくあるのは

  • 大規模な機能の追加やリファクタリングをやる必要があり一定時間かかるため、Claude Codeに自走させたい
    • その間自分が画面に張り付いていたくない、あるいは他の作業を別途したいのでClaude Code自身に作業を進めておいて欲しい
  • setting.jsonのpermissions.denyBash(bq:*)と書いたとしても、まだ危険性はある
    • pythonなどのSDK経由でBigQueryのクエリを実行しにいこうとする
    • SDKだと、危険なコマンドがスクリプトによって自動化されることがあるので、さらに危険なことが起きてしまう場合がある

などでしょうか。こういったケースはbqコマンド以外にもそもそもクエリを一切叩けない環境を用意した上でClaude Codeに自走してもらうのが重要です。

Development containers上で実行させる

一つ目はDevelopment containers上で実行させるやり方です。Anthropic公式が用意してくれているのがありがたいですね。ネットワークなども含めて環境を隔離してくれるので、--dangerously-skip-permissionsなど自走を押し進めるコマンドを使っても安心できます(最悪、作業していた内容が吹き飛ぶくらいで済む)。

Development containersはVSCode上で動かすことが多いと思います。私はVSCodeに縛られずにやりたいことが多かったので、次の方法を取ることが多いです。

サンドボックス環境で動かす

Macにはサンドボックス環境で動かすためのsandbox-execというコマンドがあり、これを使うとVSCodeに縛られずに安全にClaude Codeが自走できる環境を構築できます。

私の場合、SDK経由でもクエリを実行されないようにしたい場合は、そもそも認証系のディレクトリの読み込みができないように以下のようなします。この設定だと、Claude Codeがいくら頑張ってもGoogle Cloudのアセットに触ることができなくなるので、その前提で安心してクエリ以外の作業を任せることできます。

;; Deny access to sensitive configuration files
(deny file-read* 
    (subpath (string-append (param "HOME_DIR") "/.ssh"))
    (subpath (string-append (param "HOME_DIR") "/.config/gcloud"))
    (subpath (string-append (param "HOME_DIR") "/.config/gh"))
    (literal (string-append (param "HOME_DIR") "/.envrc"))
)

サンドボックス全体の設定はこちらに置いているので、真似したい人は参照してください。

また、サンドボックス環境でClaude Codeを立ち上げるのはコマンドが長くなってしまいがちです。使いにくいと使わなくなってしまうので、私はfishのabbrに登録して、さっと呼び出せるようにしています。sccとタイプしてスペースキーを打つと、以下の長いコマンドが展開される形になっています。必要であれば--continue--resumeで前のセッションから続けたり、--modelで利用するモデルをさっと切り替えたりなどがやりやすいのがお気に入りポイントです。

abbr --add scc sandbox-exec -f ~/.files/sandbox/safe_claude_code.sb -D TARGET_DIR="$PWD" -D HOME_DIR="$HOME" claude --dangerously-skip-permissions --disallowedTools "Bash(gcloud:*),Bash(gh:*),Bash(gsutil:*),Bash(bq:*)"'

まとめ

本エントリでは、意外と色々できてしまうbq queryとそれを実行してしまいかねないClaude Codeに対して、様々な観点からガードレールを敷く方法を紹介しました。起き得る危険性や自分の目をどれだけ光らせておくことができるか、など状況に応じてガードレールを使い込なし、快適なClaude Codeライフを過ごしましょう。

*1:ディフォルトで1時間、最大で12時間