gh CLIラッパースクリプトでAIエージェントのコマンド実行を制限
AIエージェントが実行できる gh CLIコマンドを限定するラッパースクリプト gh.sh を追加し、トリアージ・重複検出ワークフローの許可コマンドをこのラッパー経由に切り替えました。これにより、エージェントが意図しない操作(Issueの作成・削除・編集など)を実行するリスクを排除しています。
背景
Claude Codeには、GitHubのIssueをトリアージする /triage-issue とIssueの重複を検出する /dedupe という2つのカスタムコマンドがあります。これらのコマンドは allowed-tools フロントマターで実行可能なBashコマンドを宣言しており、従来は Bash(gh issue view:*) のように個別のサブコマンドを列挙していました。
この方式では、コマンドの種類ごとにフロントマターへの記載が必要で、管理が分散していました。加えて、gh CLI自体はIssueの作成・編集・削除など多数の操作をサポートしており、許可ルールの記述ミスがあった場合にエージェントが意図しない副作用を持つ操作を実行できてしまうリスクがありました。
このラッパースクリプトの導入で、許可コマンドの管理をスクリプト内に一元化し、エージェントへの露出面を最小化しています。
技術的な変更
scripts/gh.sh を新規追加し、gh CLIを呼び出せるサブコマンドとフラグを明示的に制限しました。許可されるのは読み取り系の操作のみです。
許可されるサブコマンド:
issue viewissue listsearch issueslabel list
許可されるフラグ:
--comments--state--limit--label
スクリプトの中核となるバリデーションロジックは以下の通りです。まずサブコマンドをallowlistで検証し、許可外なら即座に exit 1 します。
#!/usr/bin/env bash
set -euo pipefail
ALLOWED_FLAGS=(--comments --state --limit --label)
FLAGS_WITH_VALUES=(--state --limit --label)
SUB1="${1:-}"
SUB2="${2:-}"
CMD="$SUB1 $SUB2"
case "$CMD" in
"issue view"|"issue list"|"search issues"|"label list")
;;
*)
exit 1
;;
esac
shift 2
# Separate flags from positional arguments
POSITIONAL=()
FLAGS=()
skip_next=false
for arg in "$@"; do
if [[ "$skip_next" == true ]]; then
FLAGS+=("$arg")
skip_next=false
elif [[ "$arg" == -* ]]; then
flag="${arg%%=*}"
matched=false
for allowed in "${ALLOWED_FLAGS[@]}"; do
if [[ "$flag" == "$allowed" ]]; then
matched=true
break
fi
done
if [[ "$matched" == false ]]; then
exit 1
fi
フラグのバリデーションでは ${arg%%=*} で --state=open のような = 付き形式にも対応しており、値部分を除いたフラグ名だけをallowlistと照合しています。
コマンドのスコープは環境変数 GH_REPO または GITHUB_REPOSITORY で指定されたリポジトリに限定されます。あわせて .github/workflows/claude-issue-triage.yml に GH_REPO: ${{ github.repository }} の環境変数設定が追加され、ワークフロー実行時のリポジトリスコープが明示的に渡されるようになっています。
allowed-tools の変更前後:
変更前 (dedupe.md):
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(./scripts/comment-on-duplicates.sh:*)
変更後 (dedupe.md):
allowed-tools: Bash(./scripts/gh.sh:*), Bash(./scripts/comment-on-duplicates.sh:*)
フロントマターのエントリ数が複数から1つに集約され、許可コマンドの追加・変更はスクリプト側のallowlistを編集するだけで済むようになりました。
設計判断
許可リスト方式(allowlist) を採用し、未定義のコマンドはすべて拒否する設計になっています。
スクリプトはサブコマンドとフラグの両方をallowlistで検証します。どちらかが許可リスト外であれば exit 1 で即時終了し、gh への呼び出し自体が行われません。これにより、gh issue create や gh issue edit など書き込み系の操作はスクリプトレベルで遮断されます。
また、set -euo pipefail を宣言することで、スクリプト内でのコマンド失敗・未定義変数参照・パイプ失敗を確実にエラーとして扱い、意図しない挙動でコマンドが続行されることを防いでいます。エージェントへのプロンプトにもラッパーの使い方を具体例付きで記載することで、エージェントが正しいインターフェースを通じてのみ操作するよう誘導しています。
まとめ
gh.sh ラッパーの導入は、AIエージェントに与えるツールの「最小権限原則」を実装レベルで担保する変更です。allowed-tools フロントマターでの個別コマンド列挙から、スクリプト内allowlistへの集約により、許可範囲の変更が単一箇所の編集で完結する保守性の高い構造が実現されています。