Ce tutoriel est destiné aux ingénieurs DevOps, aux équipes de plateforme et aux responsables de l’ingénierie qui prennent en charge les développeurs à l’aide de Copilot CLI.
Les hooks sont des scripts personnalisés qui s’exécutent à des points spécifiques pendant une session Copilot CLI. Ils peuvent inspecter les invites et les appels d’outils, consigner des informations pour l’audit et même bloquer l’exécution de certaines commandes.
Vous allez configurer des hooks au niveau du référentiel qui :
- Assurez la visibilité sur les messages et l’utilisation des outils.
- Bloquer les modèles de commandes à haut risque avant l’exécution.
- Aidez les développeurs à comprendre les stratégies organisationnelles avec une messagerie claire.
Prerequisites
- Connaissance des scripts d’interpréteur de commandes (Bash ou PowerShell)
- Compréhension de base des fichiers de configuration JSON
- Accès à un référentiel dans lequel Copilot CLI est utilisé
-
`jq` installé (pour les exemples Bash)
1. Définir une stratégie d’organisation
Avant d’écrire des scripts de hook, décidez quelles actions doivent être autorisées automatiquement et qui doivent nécessiter une révision humaine.
Une stratégie claire vous permet d’éviter le sur-blocage tout en réduisant les risques.
Identifier les commandes qui nécessitent toujours une révision
Commencez par identifier les modèles qui ne doivent jamais être exécutés automatiquement par Copilot CLI. Voici quelques exemples courants :
-
**Escalade de privilèges** : `sudo`, `su`, `runas` -
**Opérations système destructrices** : `rm -rf /`, `mkfs`, `dd`, `format` -
**Modèles de téléchargement et d’exécution** : `curl ... | bash`, `wget ... | sh`, PowerShell `iex (irm ...)`
Ces commandes peuvent avoir des effets irréversibles s’ils sont exécutés involontairement.
Décider de ce qu’il faut consigner
Lorsque vous utilisez des hooks, vous pouvez capturer des informations sur la façon dont Copilot CLI est utilisé dans un référentiel, y compris les invites envoyées par les utilisateurs et les outils que Copilot CLI tente d’exécuter.
Au minimum, la plupart des organisations journalisent :
- Le horodatage et le chemin d’accès du référentiel
- Texte de l'invite (ou forme réduite)
- Nom de l’outil et arguments de l’outil
- Toute décision de stratégie (par exemple, une commande refusée et sa raison)
Évitez de journaliser les secrets ou les informations d’identification. Si des invites ou des commandes peuvent contenir des données sensibles, appliquez le masquage avant d’écrire dans les logs.
Ce tutoriel utilise un répertoire local .github/hooks/logs comme exemple simple et illustrant. Ces fichiers journaux ne sont pas destinés à être intégrés au référentiel et ne résident généralement que sur l’ordinateur d’un développeur.
Dans les environnements de production, de nombreuses organisations transfèrent les événements de hook vers un système centralisé de journalisation ou d’observabilité plutôt que de rédiger des journaux localement. Cela permet aux équipes d’appliquer la rédaction cohérente, les contrôles d’accès, les stratégies de rétention et la surveillance entre les référentiels et les utilisateurs.
S'aligner sur les parties prenantes
Avant d’appliquer des stratégies, passez en revue les éléments suivants :
- Équipes de sécurité ou de conformité pour confirmer les limites des risques
- Équipes de plateforme ou d’infrastructure, qui peuvent avoir besoin d’autorisations plus larges
- Les équipes de développement, de sorte qu’elles comprennent ce qui sera bloqué et pourquoi
Les attentes claires facilitent l’adoption et la maintenance des stratégies.
2. Configurer des fichiers de hooks du référentiel
Tout au long de ce tutoriel, vous allez utiliser hooks à portée de référentiel stockés dans le référentiel sous .github/hooks/. Ces hooks s’appliquent chaque fois que Copilot CLI s’exécute à partir de ce référentiel.
Remarque
Copilot les agents chargent les fichiers de configuration du hook depuis le .github/hooks/*.json du référentiel. Les hooks s’exécutent de manière synchrone et peuvent bloquer l’exécution.
Créer la structure de répertoires
À partir de la racine du référentiel, créez des répertoires pour votre configuration de hook, vos scripts et vos journaux :
mkdir -p .github/hooks/scripts mkdir -p .github/hooks/logs
mkdir -p .github/hooks/scripts
mkdir -p .github/hooks/logs
Ajoutez .github/hooks/logs/ à .gitignore pour que les journaux d’audit locaux ne soient pas intégrés dans le dépôt :
echo ".github/hooks/logs/" >> .gitignore
echo ".github/hooks/logs/" >> .gitignore
Ce tutoriel utilise la structure suivante :
.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
Créer un fichier de configuration de hook
Créez un fichier de configuration de hook à l’adresse .github/hooks/copilot-cli-policy.json.
Ce fichier définit les hooks qui s’exécutent, lorsqu’ils s’exécutent et les scripts qu’ils exécutent.
{
"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
}
]
}
}
Comprendre ce que fait cette configuration
Cette configuration configure trois hooks :
-
`sessionStart`: affiche un message d’information quand une nouvelle session d’agent démarre ou reprend. -
`userPromptSubmitted`: s’exécute chaque fois qu’un utilisateur envoie une commande. -
`preToolUse`: s’exécute avant l’exécution d’un outil et peut autoriser ou refuser explicitement l’exécution.
Valider et partager la configuration du hook
Lorsque vous êtes prêt à partager la configuration du hook avec des collaborateurs (par exemple, via une pull request ou dans un référentiel de test), validez la configuration du hook et les scripts. Ne validez aucun journal d’audit local.
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
À ce stade, Copilot CLI peut découvrir votre configuration de hook, même si vous n’avez pas encore créé les scripts de hook.
3. Ajouter une bannière de stratégie au démarrage de la session
Pour afficher une bannière chaque fois qu'une nouvelle session de Copilot CLI commence ou reprend, utilisez un crochet sessionStart. Cela indique clairement aux développeurs que les stratégies organisationnelles sont actives.
Le sessionStart hook reçoit des informations contextuelles telles que le répertoire de travail actuel et l’invite initiale. Toute sortie de ce hook est ignorée par Copilot CLI, ce qui le rend adapté aux messages informationnels.
Créer le script de bannière de session (Bash)
Créer .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
Créer le script de bannière de session (PowerShell)
Créer .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
Tester la bannière de session
Vous pouvez tester directement les scripts de bannière :
.github/hooks/scripts/session-banner.sh
# or, for PowerShell
.github/hooks/scripts/session-banner.ps1
Lorsque vous exécutez l’un ou l’autre script, vous devez voir la bannière de stratégie affichée dans votre terminal.
4. Messages de journalisation pour l’audit
Utilisez le hook userPromptSubmitted pour enregistrer quand les utilisateurs envoient des requêtes à Copilot CLI. Ce hook s’exécute chaque fois qu’une invite est envoyée, avant qu’aucun outil ne soit appelé.
Le hook reçoit une entrée JSON structurée qui inclut l’horodatage, le répertoire de travail actif et le texte d’invite complet. La sortie de ce hook est ignorée.
Important
Les sujets peuvent contenir des informations sensibles. Appliquez la rédaction et suivez les stratégies de gestion et de rétention des données de votre organisation lors de la journalisation de ces données.
Créer le script de journalisation du terminal (Bash)
Créer .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
Créer le script de journalisation d’invite de commandes (PowerShell)
Créer .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
Tester le script de journalisation de l'invite de commande
Vous pouvez tester les scripts directement en redirigeant un exemple d'entrée.
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
Après avoir exécuté le script, recherchez .github/hooks/logs/audit.jsonl pour une nouvelle entrée de journal.
cat .github/hooks/logs/audit.jsonl
cat .github/hooks/logs/audit.jsonl
À ce stade, les invites envoyées à Copilot CLI dans ce référentiel sont enregistrées à des fins d'audit.
5. Appliquer des stratégies avec preToolUse
Utilisez le preToolUse hook pour évaluer un appel d’outil avant son exécution. Ce hook peut autoriser l’exécution (en ne faisant rien) ou refuser l’exécution (en retournant une réponse structurée).
Comprendre l’entrée preToolUse
L’entrée preToolUse de hook comprend les éléments suivants :
-
`toolName` : l'outil que Copilot CLI va bientôt exécuter (par exemple, `bash`) -
`toolArgs`: **chaîne JSON** contenant les arguments de cet outil
Étant donné qu’il toolArgs s’agit d’une chaîne JSON, votre script doit l’analyser avant de lire des champs comme command.
Important
Les arguments et commandes de l’outil peuvent contenir des informations sensibles telles que des jetons d’API, des mots de passe ou d’autres informations d’identification. Appliquez la rédaction avant de journaliser ces données et suivez les stratégies de sécurité de votre organisation. Envisagez de journaliser uniquement les métadonnées non sensibles (nom de l’outil, horodatage, décision de stratégie) et de diriger les événements d’audit vers un système de journalisation sécurisé et centralisé avec des contrôles d’accès et des stratégies de rétention appropriés.
Créer le script de stratégie
Ensuite, créez un script de stratégie. Cet exemple :
- Enregistre toutes les tentatives d’utilisation de l’outil.
- Applique des règles de refus uniquement aux commandes bash.
- Bloque les modèles à haut risque tels que l’escalade de privilèges, les opérations destructrices et les commandes download-and-execute.
Pour vous permettre de valider le flux de refus en toute sécurité, le script inclut également une règle de démonstration temporaire qui bloque une commande de test inoffensive. Après avoir confirmé que les hooks fonctionnent comme prévu, supprimez la règle de démonstration et remplacez-la par des modèles qui reflètent les stratégies de votre organisation.
Exemple de script (Bash)
Créer .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
Créer le script de stratégie (PowerShell)
Créer .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
Tester le script de stratégie
Vous pouvez tester les scripts en pipant des exemples preToolUse d’entrée.
Autoriser l'exemple :
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
Exemple de refus :
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
Après avoir exécuté l’exemple de refus, recherchez pour une nouvelle entrée de journal de refus dans .github/hooks/logs/audit.jsonl.
{"permissionDecision":"deny","permissionDecisionReason":"Privilege escalation requires manual approval."}
À ce stade, les commandes à haut risque bash sont bloquées contre l’exécution automatique dans ce référentiel.
6. Tester de bout en bout dans le référentiel
Une fois que vous avez créé le fichier de configuration et les scripts, vérifiez que les hooks s’exécutent comme prévu lorsque vous utilisez Copilot CLI dans ce référentiel.
Valider votre fichier de configuration de hook
Vérifiez que votre fichier de configuration de hook est valide JSON :
jq '.' < .github/hooks/copilot-cli-policy.json
jq '.' < .github/hooks/copilot-cli-policy.json
Vérifier les autorisations de script (systèmes unix)
Sur macOS et Linux, vérifiez que vos scripts Bash sont exécutables :
chmod +x .github/hooks/scripts/*.sh
chmod +x .github/hooks/scripts/*.sh
Exécuter une session de base
Démarrez une nouvelle session Copilot CLI dans le référentiel :
copilot -p "Show me the status of this repository"
copilot -p "Show me the status of this repository"
Résultats attendus :
- Vous voyez la bannière de politique (de
sessionStart). - Une nouvelle entrée est ajoutée à
.github/hooks/logs/audit.jsonl(à partir deuserPromptSubmitted).
Déclencher l'utilisation de l'outil et vérifier la journalisation
Exécutez une commande qui permet à Copilot CLI d'utiliser un outil (par exemple, bash) :
copilot -p "Show me the last 5 git commits"
copilot -p "Show me the last 5 git commits"
Résultats attendus :
- Une
preToolUseentrée est ajoutée à.github/hooks/logs/audit.jsonl. - Si l’appel d’outil est autorisé, l’exécution se poursuit normalement.
Tester une commande refusée
L’exemple de script de stratégie inclut une règle de démonstration temporaire qui bloque les commandes contenant la chaîne COPILOT_HOOKS_DENY_DEMO. Cela vous permet de valider le flux de refus en toute sécurité sans exécuter de commandes destructrices.
Exécutez une invite qui déclencherait une commande refusée :
copilot -p "Run a test command: echo COPILOT_HOOKS_DENY_DEMO"
copilot -p "Run a test command: echo COPILOT_HOOKS_DENY_DEMO"
Résultats attendus :
- Copilot CLI n’exécute pas la commande.
- Votre hook retourne une réponse de déni avec une raison claire.
- Une
policyDenyentrée est écrite dans.github/hooks/logs/audit.jsonl.
Après avoir confirmé que le flux de refus fonctionne correctement, supprimez la règle de démonstration de votre script et remplacez-la par des modèles de refus qui reflètent les stratégies de votre organisation.
Inspecter vos journaux d’audit
Pour afficher les entrées récentes :
tail -n 50 .github/hooks/logs/audit.jsonl
tail -n 50 .github/hooks/logs/audit.jsonl
Pour filtrer uniquement les décisions refusées :
jq 'select(.event=="policyDeny")' .github/hooks/logs/audit.jsonl
jq 'select(.event=="policyDeny")' .github/hooks/logs/audit.jsonl
7. Déployer en toute sécurité au sein des équipes
Après avoir validé vos hooks dans un référentiel unique, déployez-les progressivement pour éviter de perturber les flux de travail de développement.
Choisir une stratégie de déploiement
Les approches courantes de déploiement sont les suivantes :
-
**Déploiement axé sur la journalisation (recommandé)** : commencez par journaliser les invites et l’utilisation de l’outil sans interdire l’exécution. Passez en revue les journaux pour une période donnée, puis introduisez des règles de refus une fois que vous comprenez les modèles d’utilisation courants. -
**Déploiement d’équipe par équipe** : déployez des hooks sur une équipe ou un dépôt à la fois, rassemblez des commentaires, puis développez-les vers des équipes supplémentaires. -
**Déploiement basé sur les risques** : commencez par les référentiels qui gèrent les systèmes sensibles ou l’infrastructure de production, puis développez-les vers des référentiels à faible risque.
Communiquer les attentes
Avant d’appliquer des règles de refus, assurez-vous que les développeurs comprennent :
- Ces hooks sont actifs dans le référentiel
- Quels types de commandes peuvent être bloqués
- Procédure à suivre si une commande est refusée
La communication claire réduit la confusion et les demandes de support.
Conserver les politiques faciles à maintenir
À mesure que l’utilisation évolue :
- Stocker la configuration et les scripts de hook dans le contrôle de version.
- Passez régulièrement en revue les journaux d’audit pour détecter les nouveaux modèles de risque.
- Mettez à jour les règles de refus de manière incrémentielle plutôt que d’ajouter des correspondances étendues.
- Documentez la raison pour laquelle chaque règle de refus existe, en particulier pour les restrictions à impact élevé.
Gérer soigneusement les exceptions
Certaines équipes (par exemple, les équipes d’infrastructure ou de plateforme) peuvent nécessiter des autorisations plus larges. Pour gérer cela en toute sécurité :
- Conservez des configurations de raccordement distinctes pour différents référentiels.
- Conservez les exceptions étroites et bien documentées.
- Évitez les contournements locaux ad hoc qui sapent l’auditabilité.
Lectures complémentaires
Pour résoudre les problèmes de hooks, consultez Utilisation de hooks avec les agents de GitHub Copilot.