このチュートリアルは、Copilot CLI を使用して開発者をサポートする DevOps エンジニア、プラットフォーム チーム、エンジニアリング リーダーを対象としています。
フックは、Copilot CLI セッション中に特定のポイントで実行されるカスタム スクリプトです。 プロンプトやツールの呼び出し、監査のログ情報を検査したり、特定のコマンドの実行をブロックしたりできます。
次のようなリポジトリ スコープのフックを構成します。
- プロンプトとツールの使用を可視化します。
- 実行前に危険度の高いコマンド パターンをブロックします。
- 開発者が明確なメッセージングを使用して組織のポリシーを理解できるようにします。
[前提条件]
- シェル スクリプトに関する知識 (Bash または PowerShell)
- JSON 構成ファイルの基本的な理解
- Copilot CLI が使用されているリポジトリへのアクセス
-
`jq` インストール済み (Bash の例用)
1. 組織ポリシーを定義する
フック スクリプトを記述する前に、自動的に許可するアクションと、人間によるレビューを必要とするアクションを決定します。
明確なポリシーを使用すると、オーバーブロッキングを回避しながら、リスクを軽減できます。
レビューが常に必要なコマンドを特定する
最初に、Copilot CLI によって自動実行されないようにするパターンを特定します。 たとえば、次のような場合です。
-
**特権エスカレーション**: `sudo`、 `su`、 `runas` -
**破壊的なシステム操作**: `rm -rf /`、 `mkfs`、 `dd`、 `format` -
**ダウンロードと実行のパターン**: `curl ... | bash`、 `wget ... | sh`、PowerShell `iex (irm ...)`
これらのコマンドは、意図せずに実行された場合、元に戻せない影響を与える可能性があります。
ログに記録する内容を決定する
フックを使用すると、Copilot CLI がリポジトリでどのように使用されているかに関する情報をキャプチャできます。これには、Copilot CLI が実行を試みるユーザーやツールによって送信されたプロンプトが含まれます。
少なくとも、ほとんどの組織では次のログが記録されます。
- タイムスタンプとリポジトリのパス
- プロンプト テキスト (または編集済みのフォーム)
- ツール名とツール引数
- ポリシーの決定 (拒否されたコマンドとその理由など)
シークレットまたは資格情報のログ記録は避けてください。 プロンプトまたはコマンドに機密データが含まれている可能性がある場合は、ログを書き込む前にやり直しを適用します。
このチュートリアルでは、簡単でわかりやすい例としてローカル .github/hooks/logs ディレクトリを使用します。 これらのログ ファイルは リポジトリにコミットすることを意図したものではなく 、通常は開発者のコンピューター上にのみ存在します。
運用環境では、多くの組織がローカルでログを書き込むのではなく、一元的なログ記録または監視システムにイベントを転送します。 これにより、チームはリポジトリおよびユーザー全体で一貫した修正、アクセス制御、保持ポリシー、監視を適用できます。
利害関係者と連携する
ポリシーを適用する前に、以下を使用してポリシーを確認します。
- リスクの境界を確認するためのセキュリティチームまたはコンプライアンス チーム
- より広範なアクセス許可が必要なプラットフォームまたはインフラストラクチャ チーム
- 開発チームは、ブロックされる内容とその理由を理解します
明確な期待により、ポリシーの適用の導入と維持が容易になります。
2. リポジトリフックファイルを設定する
このチュートリアルでは、リポジトリスコープのフックを.github/hooks/に格納されているリポジトリで使用します。 これらのフックは、Copilot CLI がこのリポジトリ内から実行されるたびに適用されます。
メモ
Copilot エージェントはリポジトリ内の .github/hooks/*.json からフック設定ファイルをロードします。 フックは同期的に実行され、実行をブロックできます。
ディレクトリ構造を作成する
リポジトリ ルートから、フック構成、スクリプト、およびログのディレクトリを作成します。
mkdir -p .github/hooks/scripts mkdir -p .github/hooks/logs
mkdir -p .github/hooks/scripts
mkdir -p .github/hooks/logs
ローカル監査ログがコミットされないように、.gitignore に .github/hooks/logs/ を追加します。
echo ".github/hooks/logs/" >> .gitignore
echo ".github/hooks/logs/" >> .gitignore
このチュートリアルでは、次の構造を使用します。
.github/
└── hooks/
├── copilot-cli-policy.json
├── logs/
│ └── audit.jsonl
└── scripts/
├── session-banner.sh
├── session-banner.ps1
├── log-prompt.sh
├── log-prompt.ps1
├── pre-tool-policy.sh
└── pre-tool-policy.ps1
フック構成ファイルを作成する
`.github/hooks/copilot-cli-policy.json`にフック構成ファイルを作成します。
このファイルでは、実行するフック、実行するタイミング、実行するスクリプトを定義します。
{
"version": 1,
"hooks": {
"sessionStart": [
{
"type": "command",
"bash": "./scripts/session-banner.sh",
"powershell": "./scripts/session-banner.ps1",
"cwd": ".github/hooks",
"timeoutSec": 10
}
],
"userPromptSubmitted": [
{
"type": "command",
"bash": "./scripts/log-prompt.sh",
"powershell": "./scripts/log-prompt.ps1",
"cwd": ".github/hooks",
"timeoutSec": 10
}
],
"preToolUse": [
{
"type": "command",
"bash": "./scripts/pre-tool-policy.sh",
"powershell": "./scripts/pre-tool-policy.ps1",
"cwd": ".github/hooks",
"timeoutSec": 15
}
]
}
}
{
"version": 1,
"hooks": {
"sessionStart": [
{
"type": "command",
"bash": "./scripts/session-banner.sh",
"powershell": "./scripts/session-banner.ps1",
"cwd": ".github/hooks",
"timeoutSec": 10
}
],
"userPromptSubmitted": [
{
"type": "command",
"bash": "./scripts/log-prompt.sh",
"powershell": "./scripts/log-prompt.ps1",
"cwd": ".github/hooks",
"timeoutSec": 10
}
],
"preToolUse": [
{
"type": "command",
"bash": "./scripts/pre-tool-policy.sh",
"powershell": "./scripts/pre-tool-policy.ps1",
"cwd": ".github/hooks",
"timeoutSec": 15
}
]
}
}
この構成の内容を理解する
この構成では、次の 3 つのフックが設定されます。
-
`sessionStart`: 新しいエージェント セッションの開始時または再開時の情報メッセージを表示します。 -
`userPromptSubmitted`: ユーザーがプロンプトを送信するたびに実行されます。 -
`preToolUse`: ツールが実行される前に実行され、実行を明示的に許可または拒否できます。
フック構成をコミットして共有する
フック構成をコラボレーターと共有する準備ができたら (たとえば、pull request またはテスト リポジトリで)、フック構成とスクリプトをコミットします。 ローカル監査ログはコミットしないでください。
git add .github/hooks/copilot-cli-policy.json .github/hooks/scripts git commit -m "Add Copilot CLI hook configuration" git push
git add .github/hooks/copilot-cli-policy.json .github/hooks/scripts
git commit -m "Add Copilot CLI hook configuration"
git push
この時点で、Copilot CLI は、フック スクリプトをまだ作成していない場合でも、フックの構成を検出できます。
3. セッション開始時にポリシー バナーを追加する
新しいCopilot CLIセッションが開始または再開されるたびに、フックsessionStartを使用してバナーを表示します。 これにより、組織のポリシーがアクティブであることを開発者に明確に示します。
`sessionStart` フックは、現在の作業ディレクトリや初期プロンプトなどのコンテキスト情報を受け取ります。 このフックからの出力はCopilot CLIによって無視されるため、情報メッセージ用として適しています。
セッション バナー スクリプトを作成する (Bash)
`.github/hooks/scripts/session-banner.sh`を作成します。
#!/bin/bash set -euo pipefail cat << 'EOF' COPILOT CLI POLICY ACTIVE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Prompts and tool use may be logged for auditing • High-risk commands may be blocked automatically • If something is blocked, follow the guidance shown ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EOF exit 0
#!/bin/bash
set -euo pipefail
cat << 'EOF'
COPILOT CLI POLICY ACTIVE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• Prompts and tool use may be logged for auditing
• High-risk commands may be blocked automatically
• If something is blocked, follow the guidance shown
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
セッション バナー スクリプトを作成する (PowerShell)
`.github/hooks/scripts/session-banner.ps1`を作成します。
$ErrorActionPreference = "Stop" Write-Host @" COPILOT CLI POLICY ACTIVE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Prompts and tool use may be logged for auditing • High-risk commands may be blocked automatically • If something is blocked, follow the guidance shown ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ "@ exit 0
$ErrorActionPreference = "Stop"
Write-Host @"
COPILOT CLI POLICY ACTIVE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• Prompts and tool use may be logged for auditing
• High-risk commands may be blocked automatically
• If something is blocked, follow the guidance shown
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"@
exit 0
セッション バナーをテストする
バナー スクリプトを直接テストできます。
.github/hooks/scripts/session-banner.sh
# or, for PowerShell
.github/hooks/scripts/session-banner.ps1
いずれかのスクリプトを実行すると、ターミナルにポリシー バナーが表示されます。
4. 監査のログ プロンプト
ユーザーがプロンプトをCopilot CLIに送信した際に、userPromptSubmitted フックを使用して記録します。 このフックは、ツールが呼び出される前に、プロンプトが送信されるたびに実行されます。
フックは、タイムスタンプ、現在の作業ディレクトリ、および完全なプロンプト テキストを含む構造化された JSON 入力を受け取ります。 このフックの出力は無視されます。
重要
プロンプトには機密情報が含まれている場合があります。 このデータをログに記録するときは、編集を適用し、組織のデータ処理と保持ポリシーに従います。
プロンプト ログ スクリプトを作成する (Bash)
`.github/hooks/scripts/log-prompt.sh`を作成します。
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TIMESTAMP_MS="$(echo "$INPUT" | jq -r '.timestamp // empty')"
CWD="$(echo "$INPUT" | jq -r '.cwd // empty')"
# This example logs only metadata, not the full prompt, to avoid storing
# potentially sensitive data. Adjust to match your organization’s needs.
LOG_DIR=".github/hooks/logs"
mkdir -p "$LOG_DIR"
chmod 700 "$LOG_DIR"
jq -n \
--arg ts "$TIMESTAMP_MS" \
--arg cwd "$CWD" \
'{event:"userPromptSubmitted", timestampMs:$ts, cwd:$cwd}' \
>> "$LOG_DIR/audit.jsonl"
exit 0
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TIMESTAMP_MS="$(echo "$INPUT" | jq -r '.timestamp // empty')"
CWD="$(echo "$INPUT" | jq -r '.cwd // empty')"
# This example logs only metadata, not the full prompt, to avoid storing
# potentially sensitive data. Adjust to match your organization’s needs.
LOG_DIR=".github/hooks/logs"
mkdir -p "$LOG_DIR"
chmod 700 "$LOG_DIR"
jq -n \
--arg ts "$TIMESTAMP_MS" \
--arg cwd "$CWD" \
'{event:"userPromptSubmitted", timestampMs:$ts, cwd:$cwd}' \
>> "$LOG_DIR/audit.jsonl"
exit 0
プロンプト ログ スクリプトを作成する (PowerShell)
`.github/hooks/scripts/log-prompt.ps1`を作成します。
$ErrorActionPreference = "Stop"
$inputObj = [Console]::In.ReadToEnd() | ConvertFrom-Json
$timestampMs = $inputObj.timestamp
$cwd = $inputObj.cwd
$prompt = $inputObj.prompt
# Optional example redaction. Adjust to match your organization’s needs.
$redactedPrompt = $prompt -replace 'ghp_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]'
$logDir = ".github/hooks/logs"
if (-not (Test-Path $logDir)) {
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
}
$logEntry = @{
event = "userPromptSubmitted"
timestampMs = $timestampMs
cwd = $cwd
prompt = $redactedPrompt
} | ConvertTo-Json -Compress
Add-Content -Path "$logDir/audit.jsonl" -Value $logEntry
exit 0
$ErrorActionPreference = "Stop"
$inputObj = [Console]::In.ReadToEnd() | ConvertFrom-Json
$timestampMs = $inputObj.timestamp
$cwd = $inputObj.cwd
$prompt = $inputObj.prompt
# Optional example redaction. Adjust to match your organization’s needs.
$redactedPrompt = $prompt -replace 'ghp_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]'
$logDir = ".github/hooks/logs"
if (-not (Test-Path $logDir)) {
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
}
$logEntry = @{
event = "userPromptSubmitted"
timestampMs = $timestampMs
cwd = $cwd
prompt = $redactedPrompt
} | ConvertTo-Json -Compress
Add-Content -Path "$logDir/audit.jsonl" -Value $logEntry
exit 0
プロンプト ログ スクリプトをテストする
サンプル入力をパイプ処理することで、スクリプトを直接テストできます。
echo '{"timestamp":1704614500000,"cwd":"/repo","prompt":"List all branches"}' \
| .github/hooks/scripts/log-prompt.sh
# or, for PowerShell
echo '{"timestamp":1704614500000,"cwd":"/repo","prompt":"List all branches"}' |
.github/hooks/scripts/log-prompt.ps1
スクリプトを実行した後、 .github/hooks/logs/audit.jsonl で新しいログ エントリを確認します。
cat .github/hooks/logs/audit.jsonl
cat .github/hooks/logs/audit.jsonl
この時点で、このリポジトリの Copilot CLI に送信されたプロンプトが監査のために記録されます。
5. 次の方法でポリシーを適用する preToolUse
`preToolUse` フックを使用して、ツール呼び出しを**実行前に評価します**。 このフックを使用すると、(何もしない) 実行または実行の拒否 (構造化された応答を返すことによって) 実行を許可できます。
`preToolUse`入力を理解する
`preToolUse`フック入力には、次のものが含まれます。
*
toolName: Copilot CLI を実行しようとしているツール (たとえば、 bash)
*
toolArgs: そのツールの引数を含む JSON 文字列
`toolArgs`は JSON 文字列であるため、スクリプトでは、`command`などのフィールドを読み取る前に解析する必要があります。
重要
ツールの引数とコマンドには、API トークン、パスワード、その他の資格情報などの機密情報が含まれている場合があります。 このデータをログに記録する前に編集を適用し、組織のセキュリティ ポリシーに従います。 機密性の高いメタデータ (ツール名、タイムスタンプ、ポリシー決定) のみをログに記録し、適切なアクセス制御と保持ポリシーを使用して、セキュリティで保護された一元化されたログ システムに監査イベントを転送することを検討してください。
ポリシー スクリプトを作成する
次に、ポリシー スクリプトを作成します。 この例では次のとおりです。
- 試行されたすべてのツールの使用状況をログに記録します。
- 拒否規則を bash コマンドにのみ適用します。
- 特権エスカレーション、破壊的操作、ダウンロードおよび実行コマンドなどのリスクの高いパターンをブロックします。
拒否フローを安全に検証できるように、スクリプトには無害なテスト コマンドをブロックする一時的なデモ ルールも含まれています。 フックが期待どおりに動作することを確認したら、デモルールを削除し、組織のポリシーを反映するパターンに置き換えます。
スクリプトの例 (Bash)
`.github/hooks/scripts/pre-tool-policy.sh`を作成します。
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TOOL_NAME="$(echo "$INPUT" | jq -r '.toolName // empty')"
TOOL_ARGS_RAW="$(echo "$INPUT" | jq -r '.toolArgs // empty')" # JSON string
LOG_DIR=".github/hooks/logs"
mkdir -p "$LOG_DIR"
# Example redaction logic.
# GitHub does not currently provide built-in secret redaction for hooks.
# This example shows one possible approach; many organizations prefer to
# forward events to a centralized logging system that handles redaction.
# Redact sensitive patterns before logging.
# Adjust these patterns to match your organization's needs.
REDACTED_TOOL_ARGS="$(echo "$TOOL_ARGS_RAW" | \
sed -E 's/ghp_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/gho_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghu_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghs_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/Bearer [A-Za-z0-9_\-\.]+/Bearer [REDACTED]/g' | \
sed -E 's/--password[= ][^ ]+/--password=[REDACTED]/g' | \
sed -E 's/--token[= ][^ ]+/--token=[REDACTED]/g')"
# Log attempted tool use with redacted toolArgs.
jq -n \
--arg tool "$TOOL_NAME" \
--arg toolArgs "$REDACTED_TOOL_ARGS" \
'{event:"preToolUse", toolName:$tool, toolArgs:$toolArgs}' \
>> "$LOG_DIR/audit.jsonl"
# Only enforce command rules for bash.
if [ "$TOOL_NAME" != "bash" ]; then
exit 0
fi
# Parse toolArgs JSON string.
# If toolArgs isn't valid JSON for some reason, allow (and rely on logs).
if ! echo "$TOOL_ARGS_RAW" | jq -e . >/dev/null 2>&1; then
exit 0
fi
COMMAND="$(echo "$TOOL_ARGS_RAW" | jq -r '.command // empty')"
# ---------------------------------------------------------------------------
# Demo-only deny rule for safe testing.
# This blocks a harmless test command so you can validate the deny flow.
# Remove this rule after confirming your hooks work as expected.
# ---------------------------------------------------------------------------
if echo "$COMMAND" | grep -q "COPILOT_HOOKS_DENY_DEMO"; then
deny "Blocked demo command (test rule). Remove this rule after validating hooks."
fi
deny() {
local reason="$1"
# Redact sensitive patterns from command before logging.
local redacted_cmd="$(echo "$COMMAND" | \
sed -E 's/ghp_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/gho_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghu_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghs_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/Bearer [A-Za-z0-9_\-\.]+/Bearer [REDACTED]/g' | \
sed -E 's/--password[= ][^ ]+/--password=[REDACTED]/g' | \
sed -E 's/--token[= ][^ ]+/--token=[REDACTED]/g')"
# Log the denial decision with redacted command.
jq -n \
--arg cmd "$redacted_cmd" \
--arg r "$reason" \
'{event:"policyDeny", toolName:"bash", command:$cmd, reason:$r}' \
>> "$LOG_DIR/audit.jsonl"
# Return a denial response.
jq -n \
--arg r "$reason" \
'{permissionDecision:"deny", permissionDecisionReason:$r}'
exit 0
}
# Privilege escalation
if echo "$COMMAND" | grep -qE '\b(sudo|su|runas)\b'; then
deny "Privilege escalation requires manual approval."
fi
# Destructive filesystem operations targeting root
if echo "$COMMAND" | grep -qE 'rm\s+-rf\s*/($|\s)|rm\s+.*-rf\s*/($|\s)'; then
deny "Destructive operations targeting the filesystem root require manual approval."
fi
# System-level destructive operations
if echo "$COMMAND" | grep -qE '\b(mkfs|dd|format)\b'; then
deny "System-level destructive operations are not allowed via automated execution."
fi
# Download-and-execute patterns
if echo "$COMMAND" | grep -qE 'curl.*\|\s*(bash|sh)|wget.*\|\s*(bash|sh)'; then
deny "Download-and-execute patterns require manual approval."
fi
# Allow by default
exit 0
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TOOL_NAME="$(echo "$INPUT" | jq -r '.toolName // empty')"
TOOL_ARGS_RAW="$(echo "$INPUT" | jq -r '.toolArgs // empty')" # JSON string
LOG_DIR=".github/hooks/logs"
mkdir -p "$LOG_DIR"
# Example redaction logic.
# GitHub does not currently provide built-in secret redaction for hooks.
# This example shows one possible approach; many organizations prefer to
# forward events to a centralized logging system that handles redaction.
# Redact sensitive patterns before logging.
# Adjust these patterns to match your organization's needs.
REDACTED_TOOL_ARGS="$(echo "$TOOL_ARGS_RAW" | \
sed -E 's/ghp_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/gho_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghu_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghs_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/Bearer [A-Za-z0-9_\-\.]+/Bearer [REDACTED]/g' | \
sed -E 's/--password[= ][^ ]+/--password=[REDACTED]/g' | \
sed -E 's/--token[= ][^ ]+/--token=[REDACTED]/g')"
# Log attempted tool use with redacted toolArgs.
jq -n \
--arg tool "$TOOL_NAME" \
--arg toolArgs "$REDACTED_TOOL_ARGS" \
'{event:"preToolUse", toolName:$tool, toolArgs:$toolArgs}' \
>> "$LOG_DIR/audit.jsonl"
# Only enforce command rules for bash.
if [ "$TOOL_NAME" != "bash" ]; then
exit 0
fi
# Parse toolArgs JSON string.
# If toolArgs isn't valid JSON for some reason, allow (and rely on logs).
if ! echo "$TOOL_ARGS_RAW" | jq -e . >/dev/null 2>&1; then
exit 0
fi
COMMAND="$(echo "$TOOL_ARGS_RAW" | jq -r '.command // empty')"
# ---------------------------------------------------------------------------
# Demo-only deny rule for safe testing.
# This blocks a harmless test command so you can validate the deny flow.
# Remove this rule after confirming your hooks work as expected.
# ---------------------------------------------------------------------------
if echo "$COMMAND" | grep -q "COPILOT_HOOKS_DENY_DEMO"; then
deny "Blocked demo command (test rule). Remove this rule after validating hooks."
fi
deny() {
local reason="$1"
# Redact sensitive patterns from command before logging.
local redacted_cmd="$(echo "$COMMAND" | \
sed -E 's/ghp_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/gho_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghu_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/ghs_[A-Za-z0-9]{20,}/[REDACTED_TOKEN]/g' | \
sed -E 's/Bearer [A-Za-z0-9_\-\.]+/Bearer [REDACTED]/g' | \
sed -E 's/--password[= ][^ ]+/--password=[REDACTED]/g' | \
sed -E 's/--token[= ][^ ]+/--token=[REDACTED]/g')"
# Log the denial decision with redacted command.
jq -n \
--arg cmd "$redacted_cmd" \
--arg r "$reason" \
'{event:"policyDeny", toolName:"bash", command:$cmd, reason:$r}' \
>> "$LOG_DIR/audit.jsonl"
# Return a denial response.
jq -n \
--arg r "$reason" \
'{permissionDecision:"deny", permissionDecisionReason:$r}'
exit 0
}
# Privilege escalation
if echo "$COMMAND" | grep -qE '\b(sudo|su|runas)\b'; then
deny "Privilege escalation requires manual approval."
fi
# Destructive filesystem operations targeting root
if echo "$COMMAND" | grep -qE 'rm\s+-rf\s*/($|\s)|rm\s+.*-rf\s*/($|\s)'; then
deny "Destructive operations targeting the filesystem root require manual approval."
fi
# System-level destructive operations
if echo "$COMMAND" | grep -qE '\b(mkfs|dd|format)\b'; then
deny "System-level destructive operations are not allowed via automated execution."
fi
# Download-and-execute patterns
if echo "$COMMAND" | grep -qE 'curl.*\|\s*(bash|sh)|wget.*\|\s*(bash|sh)'; then
deny "Download-and-execute patterns require manual approval."
fi
# Allow by default
exit 0
ポリシー スクリプトを作成する (PowerShell)
`.github/hooks/scripts/pre-tool-policy.ps1`を作成します。
$ErrorActionPreference = "Stop"
$inputObj = [Console]::In.ReadToEnd() | ConvertFrom-Json
$toolName = $inputObj.toolName
$toolArgsRaw = $inputObj.toolArgs # JSON string
$logDir = ".github/hooks/logs"
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
# Example redaction logic.
# GitHub does not currently provide built-in secret redaction for hooks.
# This example shows one possible approach; many organizations prefer to
# forward events to a centralized logging system that handles redaction.
# Redact sensitive patterns before logging.
# Adjust these patterns to match your organization's needs.
$redactedToolArgs = $toolArgsRaw `
-replace 'ghp_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'gho_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghu_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghs_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'Bearer [A-Za-z0-9_\-\.]+', 'Bearer [REDACTED]' `
-replace '--password[= ][^ ]+', '--password=[REDACTED]' `
-replace '--token[= ][^ ]+', '--token=[REDACTED]'
# Log attempted tool use with redacted toolArgs.
(@{
event = "preToolUse"
toolName = $toolName
toolArgs = $redactedToolArgs
} | ConvertTo-Json -Compress) | Add-Content -Path "$logDir/audit.jsonl"
if ($toolName -ne "bash") { exit 0 }
# Parse toolArgs JSON string.
$toolArgs = $null
try { $toolArgs = $toolArgsRaw | ConvertFrom-Json } catch { exit 0 }
$command = $toolArgs.command
# ---------------------------------------------------------------------------
# Demo-only deny rule for safe testing.
# This blocks a harmless test command so you can validate the deny flow.
# Remove this rule after confirming your hooks work as expected.
# ---------------------------------------------------------------------------
if ($command -match 'COPILOT_HOOKS_DENY_DEMO') {
Deny "Blocked demo command (test rule). Remove this rule after validating hooks."
}
function Deny([string]$reason) {
# Redact sensitive patterns from command before logging.
$redactedCommand = $command `
-replace 'ghp_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'gho_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghu_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghs_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'Bearer [A-Za-z0-9_\-\.]+', 'Bearer [REDACTED]' `
-replace '--password[= ][^ ]+', '--password=[REDACTED]' `
-replace '--token[= ][^ ]+', '--token=[REDACTED]'
# Log the denial decision with redacted command.
(@{
event = "policyDeny"
toolName = "bash"
command = $redactedCommand
reason = $reason
} | ConvertTo-Json -Compress) | Add-Content -Path "$logDir/audit.jsonl"
(@{
permissionDecision = "deny"
permissionDecisionReason = $reason
} | ConvertTo-Json -Compress)
exit 0
}
if ($command -match '\b(sudo|su|runas)\b') { Deny "Privilege escalation requires manual approval." }
if ($command -match 'rm\s+-rf\s*/(\s|$)|rm\s+.*-rf\s*/(\s|$)') { Deny "Destructive operations targeting the filesystem root require manual approval." }
if ($command -match '\b(mkfs|dd|format)\b') { Deny "System-level destructive operations are not allowed via automated execution." }
if ($command -match 'curl.*\|\s*(bash|sh)|wget.*\|\s*(bash|sh)') { Deny "Download-and-execute patterns require manual approval." }
exit 0
$ErrorActionPreference = "Stop"
$inputObj = [Console]::In.ReadToEnd() | ConvertFrom-Json
$toolName = $inputObj.toolName
$toolArgsRaw = $inputObj.toolArgs # JSON string
$logDir = ".github/hooks/logs"
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
# Example redaction logic.
# GitHub does not currently provide built-in secret redaction for hooks.
# This example shows one possible approach; many organizations prefer to
# forward events to a centralized logging system that handles redaction.
# Redact sensitive patterns before logging.
# Adjust these patterns to match your organization's needs.
$redactedToolArgs = $toolArgsRaw `
-replace 'ghp_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'gho_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghu_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghs_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'Bearer [A-Za-z0-9_\-\.]+', 'Bearer [REDACTED]' `
-replace '--password[= ][^ ]+', '--password=[REDACTED]' `
-replace '--token[= ][^ ]+', '--token=[REDACTED]'
# Log attempted tool use with redacted toolArgs.
(@{
event = "preToolUse"
toolName = $toolName
toolArgs = $redactedToolArgs
} | ConvertTo-Json -Compress) | Add-Content -Path "$logDir/audit.jsonl"
if ($toolName -ne "bash") { exit 0 }
# Parse toolArgs JSON string.
$toolArgs = $null
try { $toolArgs = $toolArgsRaw | ConvertFrom-Json } catch { exit 0 }
$command = $toolArgs.command
# ---------------------------------------------------------------------------
# Demo-only deny rule for safe testing.
# This blocks a harmless test command so you can validate the deny flow.
# Remove this rule after confirming your hooks work as expected.
# ---------------------------------------------------------------------------
if ($command -match 'COPILOT_HOOKS_DENY_DEMO') {
Deny "Blocked demo command (test rule). Remove this rule after validating hooks."
}
function Deny([string]$reason) {
# Redact sensitive patterns from command before logging.
$redactedCommand = $command `
-replace 'ghp_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'gho_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghu_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'ghs_[A-Za-z0-9]{20,}', '[REDACTED_TOKEN]' `
-replace 'Bearer [A-Za-z0-9_\-\.]+', 'Bearer [REDACTED]' `
-replace '--password[= ][^ ]+', '--password=[REDACTED]' `
-replace '--token[= ][^ ]+', '--token=[REDACTED]'
# Log the denial decision with redacted command.
(@{
event = "policyDeny"
toolName = "bash"
command = $redactedCommand
reason = $reason
} | ConvertTo-Json -Compress) | Add-Content -Path "$logDir/audit.jsonl"
(@{
permissionDecision = "deny"
permissionDecisionReason = $reason
} | ConvertTo-Json -Compress)
exit 0
}
if ($command -match '\b(sudo|su|runas)\b') { Deny "Privilege escalation requires manual approval." }
if ($command -match 'rm\s+-rf\s*/(\s|$)|rm\s+.*-rf\s*/(\s|$)') { Deny "Destructive operations targeting the filesystem root require manual approval." }
if ($command -match '\b(mkfs|dd|format)\b') { Deny "System-level destructive operations are not allowed via automated execution." }
if ($command -match 'curl.*\|\s*(bash|sh)|wget.*\|\s*(bash|sh)') { Deny "Download-and-execute patterns require manual approval." }
exit 0
ポリシー スクリプトをテストする
入力の例をパイプ処理し、スクリプトpreToolUseをテストできます。
許可する例:
echo '{"toolName":"bash","toolArgs":"{\"command\":\"git status\"}"}' \
| .github/hooks/scripts/pre-tool-policy.sh
# or, for PowerShell
echo '{"toolName":"bash","toolArgs":"{\"command\":\"git status\"}"}' |
.github/hooks/scripts/pre-tool-policy.ps1
拒否の例:
echo '{"toolName":"bash","toolArgs":"{\"command\":\"sudo rm -rf /\"}"}' \
| .github/hooks/scripts/pre-tool-policy.sh
# or, for PowerShell
echo '{"toolName":"bash","toolArgs":"{\"command\":\"sudo rm -rf /\"}"}' |
.github/hooks/scripts/pre-tool-policy.ps1
拒否の例を実行した後、 .github/hooks/logs/audit.jsonl で新しい拒否ログ エントリを確認します。
{"permissionDecision":"deny","permissionDecisionReason":"Privilege escalation requires manual approval."}
この時点で、リスクの高い bash コマンドは、このリポジトリでの自動実行からブロックされます。
6. リポジトリでエンド ツー エンドのテストを行う
構成ファイルとスクリプトを作成したら、このリポジトリで Copilot CLI を使用するときに、フックが期待どおりに実行されることを確認します。
フック構成ファイルを検証する
フック構成ファイルが有効な JSON であることを確認します。
jq '.' < .github/hooks/copilot-cli-policy.json
jq '.' < .github/hooks/copilot-cli-policy.json
スクリプトのアクセス許可を確認する (Unix ベースのシステム)
macOS と Linux では、Bash スクリプトが実行可能であることを確認します。
chmod +x .github/hooks/scripts/*.sh
chmod +x .github/hooks/scripts/*.sh
基本的なセッションを実行する
リポジトリで新しい Copilot CLI セッションを開始します。
copilot -p "Show me the status of this repository"
copilot -p "Show me the status of this repository"
予想される結果:
- (
sessionStartから) ポリシー バナーが表示されます。 - 新しいエントリが (
.github/hooks/logs/audit.jsonlから)userPromptSubmittedに追加されます。
トリガー ツールの使用とログ記録の確認
Copilot CLI がツール (例えば、bash) を使用するように促すプロンプトを実行します。
copilot -p "Show me the last 5 git commits"
copilot -p "Show me the last 5 git commits"
予想される結果:
-
`preToolUse` エントリが`.github/hooks/logs/audit.jsonl`に追加されます。 - ツール呼び出しが許可されている場合は、通常どおり実行が続行されます。
拒否されたコマンドをテストする
ポリシー スクリプトの例には、文字列 COPILOT_HOOKS_DENY_DEMOを含むコマンドをブロックする一時的なデモ ルールが含まれています。 これにより、破壊的なコマンドを実行せずに、拒否フローを安全に検証できます。
拒否されたコマンドをトリガーするプロンプトを実行します。
copilot -p "Run a test command: echo COPILOT_HOOKS_DENY_DEMO"
copilot -p "Run a test command: echo COPILOT_HOOKS_DENY_DEMO"
予想される結果:
- Copilot CLI はコマンドを実行しません。
- フックは具体的な理由を伴って拒否応答を返します。
-
`policyDeny`エントリが`.github/hooks/logs/audit.jsonl`に書き込まれます。
拒否フローが正しく動作することを確認したら、スクリプトからデモ ルールを削除し、組織のポリシーを反映する拒否パターンに置き換えます。
監査ログを検査する
最近のエントリを表示するには:
tail -n 50 .github/hooks/logs/audit.jsonl
tail -n 50 .github/hooks/logs/audit.jsonl
拒否された決定のみをフィルター処理するには:
jq 'select(.event=="policyDeny")' .github/hooks/logs/audit.jsonl
jq 'select(.event=="policyDeny")' .github/hooks/logs/audit.jsonl
7. チーム間で安全にロールアウトする
1 つのリポジトリでフックを検証した後、開発ワークフローを中断しないように徐々に展開します。
ロールアウト戦略を選択する
一般的なロールアウト方法は次のとおりです。
-
**ログ優先ロールアウト (推奨):** 実行を拒否せずに、プロンプトとツールの使用状況をログに記録することから始めます。 一定期間ログを確認し、一般的な使用パターンを理解したら拒否ルールを導入します。 -
**チームごとのロールアウト**: 一度に 1 つのチームまたはリポジトリにフックをデプロイし、フィードバックを収集してから、追加のチームに展開します。 -
**リスクベースのロールアウト**: 機密性の高いシステムまたは運用インフラストラクチャを処理するリポジトリから始めて、リスクの低いリポジトリに拡張します。
期待を伝える
拒否規則を適用する前に、開発者が以下を理解していることを確認してください。
- そのフックがリポジトリでアクティブになっている
- ブロックされる可能性があるコマンドの種類
- コマンドが拒否された場合の続行方法
明確なコミュニケーションにより、混乱とサポート要求が軽減されます。
ポリシーを保守可能な状態に保つ
使用が進化するにつれて、次のようになります。
- フックの構成とスクリプトをバージョン 管理に格納します。
- 監査ログを定期的に確認して、新しいリスク パターンを検出します。
- 広範な一致を追加するのではなく、拒否ルールを段階的かつ細かく更新します。
- 各拒否規則が存在する理由 (特に影響の大きい制限の場合) を文書化します。
例外を慎重に処理する
一部のチーム (インフラストラクチャチームやプラットフォーム チームなど) には、より広範なアクセス許可が必要な場合があります。 これを安全に処理するには:
- 異なるリポジトリに対して個別のフック構成を維持します。
- 例外を絞り込み、十分に文書化します。
- 監査可能性を損なうアドホック ローカル バイパスを回避します。
詳細については、次を参照してください。
フックのトラブルシューティングについては、 GitHub Copilot エージェントを使ったフックの活用 を参照してください。