mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 02:15:12 +00:00
feat: add governance-audit hook — threat detection for Copilot sessions
Add real-time governance audit hook that scans prompts for threat signals: - 5 threat categories: data exfiltration, privilege escalation, system destruction, prompt injection, credential exposure - 4 governance levels: open, standard, strict, locked - Append-only JSON audit trail (logs/copilot/governance/audit.log) - Session summary with threat counts at session end - Privacy-aware: logs decisions and metadata, never prompt content Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -27,5 +27,6 @@ Hooks enable automated workflows triggered by specific events during GitHub Copi
|
|||||||
|
|
||||||
| Name | Description | Events | Bundled Assets |
|
| Name | Description | Events | Bundled Assets |
|
||||||
| ---- | ----------- | ------ | -------------- |
|
| ---- | ----------- | ------ | -------------- |
|
||||||
|
| [Governance Audit](../hooks/governance-audit/README.md) | Scans Copilot agent prompts for threat signals and logs governance events | sessionStart, sessionEnd, userPromptSubmitted | `audit-prompt.sh`<br />`audit-session-end.sh`<br />`audit-session-start.sh`<br />`hooks.json` |
|
||||||
| [Session Auto-Commit](../hooks/session-auto-commit/README.md) | Automatically commits and pushes changes when a Copilot coding agent session ends | sessionEnd | `auto-commit.sh`<br />`hooks.json` |
|
| [Session Auto-Commit](../hooks/session-auto-commit/README.md) | Automatically commits and pushes changes when a Copilot coding agent session ends | sessionEnd | `auto-commit.sh`<br />`hooks.json` |
|
||||||
| [Session Logger](../hooks/session-logger/README.md) | Logs all Copilot coding agent session activity for audit and analysis | sessionStart, sessionEnd, userPromptSubmitted | `hooks.json`<br />`log-prompt.sh`<br />`log-session-end.sh`<br />`log-session-start.sh` |
|
| [Session Logger](../hooks/session-logger/README.md) | Logs all Copilot coding agent session activity for audit and analysis | sessionStart, sessionEnd, userPromptSubmitted | `hooks.json`<br />`log-prompt.sh`<br />`log-session-end.sh`<br />`log-session-start.sh` |
|
||||||
|
|||||||
99
hooks/governance-audit/README.md
Normal file
99
hooks/governance-audit/README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
name: 'Governance Audit'
|
||||||
|
description: 'Scans Copilot agent prompts for threat signals and logs governance events'
|
||||||
|
tags: ['security', 'governance', 'audit', 'safety']
|
||||||
|
---
|
||||||
|
|
||||||
|
# Governance Audit Hook
|
||||||
|
|
||||||
|
Real-time threat detection and audit logging for GitHub Copilot coding agent sessions. Scans user prompts for dangerous patterns before the agent processes them.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This hook provides governance controls for Copilot coding agent sessions:
|
||||||
|
- **Threat detection**: Scans prompts for data exfiltration, privilege escalation, system destruction, prompt injection, and credential exposure
|
||||||
|
- **Governance levels**: Open, standard, strict, locked — from audit-only to full blocking
|
||||||
|
- **Audit trail**: Append-only JSON log of all governance events
|
||||||
|
- **Session summary**: Reports threat counts at session end
|
||||||
|
|
||||||
|
## Threat Categories
|
||||||
|
|
||||||
|
| Category | Examples | Severity |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| `data_exfiltration` | "send all records to external API" | 0.7 - 0.95 |
|
||||||
|
| `privilege_escalation` | "sudo", "chmod 777", "add to sudoers" | 0.8 - 0.95 |
|
||||||
|
| `system_destruction` | "rm -rf /", "drop database" | 0.9 - 0.95 |
|
||||||
|
| `prompt_injection` | "ignore previous instructions" | 0.6 - 0.9 |
|
||||||
|
| `credential_exposure` | Hardcoded API keys, AWS access keys | 0.9 - 0.95 |
|
||||||
|
|
||||||
|
## Governance Levels
|
||||||
|
|
||||||
|
| Level | Behavior |
|
||||||
|
|-------|----------|
|
||||||
|
| `open` | Log threats only, never block |
|
||||||
|
| `standard` | Log threats, block only if `BLOCK_ON_THREAT=true` |
|
||||||
|
| `strict` | Log and block all detected threats |
|
||||||
|
| `locked` | Log and block all detected threats |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Copy the hook folder to your repository:
|
||||||
|
```bash
|
||||||
|
cp -r hooks/governance-audit .github/hooks/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Ensure scripts are executable:
|
||||||
|
```bash
|
||||||
|
chmod +x .github/hooks/governance-audit/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create the logs directory and add to `.gitignore`:
|
||||||
|
```bash
|
||||||
|
mkdir -p logs/copilot/governance
|
||||||
|
echo "logs/" >> .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Commit to your repository's default branch.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Set environment variables in `hooks.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"GOVERNANCE_LEVEL": "strict",
|
||||||
|
"BLOCK_ON_THREAT": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Variable | Values | Default | Description |
|
||||||
|
|----------|--------|---------|-------------|
|
||||||
|
| `GOVERNANCE_LEVEL` | `open`, `standard`, `strict`, `locked` | `standard` | Controls blocking behavior |
|
||||||
|
| `BLOCK_ON_THREAT` | `true`, `false` | `false` | Block prompts with threats (standard level) |
|
||||||
|
| `SKIP_GOVERNANCE_AUDIT` | `true` | unset | Disable governance audit entirely |
|
||||||
|
|
||||||
|
## Log Format
|
||||||
|
|
||||||
|
Events are written to `logs/copilot/governance/audit.log` in JSON Lines format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"timestamp":"2026-01-15T10:30:00Z","event":"session_start","governance_level":"standard","cwd":"/workspace/project"}
|
||||||
|
{"timestamp":"2026-01-15T10:31:00Z","event":"prompt_scanned","governance_level":"standard","status":"clean"}
|
||||||
|
{"timestamp":"2026-01-15T10:32:00Z","event":"threat_detected","governance_level":"standard","threat_count":1,"threats":[{"category":"privilege_escalation","severity":0.8,"description":"Elevated privileges","evidence":"sudo"}]}
|
||||||
|
{"timestamp":"2026-01-15T10:45:00Z","event":"session_end","total_events":12,"threats_detected":1}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `jq` for JSON processing (pre-installed on most CI environments and macOS)
|
||||||
|
- `grep` with `-E` (extended regex) support
|
||||||
|
- `bc` for floating-point comparison (optional, gracefully degrades)
|
||||||
|
|
||||||
|
## Privacy & Security
|
||||||
|
|
||||||
|
- Prompts are **never** logged — only threat signals and metadata are recorded
|
||||||
|
- Add `logs/` to `.gitignore` to keep audit data local
|
||||||
|
- Set `SKIP_GOVERNANCE_AUDIT=true` to disable entirely
|
||||||
|
- All data stays local — no external network calls
|
||||||
131
hooks/governance-audit/audit-prompt.sh
Normal file
131
hooks/governance-audit/audit-prompt.sh
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Governance Audit: Scan user prompts for threat signals before agent processing
|
||||||
|
#
|
||||||
|
# Environment variables:
|
||||||
|
# GOVERNANCE_LEVEL - "open", "standard", "strict", "locked" (default: standard)
|
||||||
|
# BLOCK_ON_THREAT - "true" to exit non-zero on threats (default: false)
|
||||||
|
# SKIP_GOVERNANCE_AUDIT - "true" to disable (default: unset)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${SKIP_GOVERNANCE_AUDIT:-}" == "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
mkdir -p logs/copilot/governance
|
||||||
|
|
||||||
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
LEVEL="${GOVERNANCE_LEVEL:-standard}"
|
||||||
|
BLOCK="${BLOCK_ON_THREAT:-false}"
|
||||||
|
LOG_FILE="logs/copilot/governance/audit.log"
|
||||||
|
|
||||||
|
# Extract prompt text from Copilot input (JSON with userMessage field)
|
||||||
|
PROMPT=""
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
PROMPT=$(echo "$INPUT" | jq -r '.userMessage // .prompt // empty' 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
if [[ -z "$PROMPT" ]]; then
|
||||||
|
PROMPT="$INPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Threat detection patterns organized by category
|
||||||
|
# Each pattern has: category, description, severity (0.0-1.0)
|
||||||
|
THREATS_FOUND=()
|
||||||
|
|
||||||
|
check_pattern() {
|
||||||
|
local pattern="$1"
|
||||||
|
local category="$2"
|
||||||
|
local severity="$3"
|
||||||
|
local description="$4"
|
||||||
|
|
||||||
|
if echo "$PROMPT" | grep -qiE "$pattern"; then
|
||||||
|
local evidence
|
||||||
|
evidence=$(echo "$PROMPT" | grep -oiE "$pattern" | head -1)
|
||||||
|
THREATS_FOUND+=("$category:$severity:$description:$evidence")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Data exfiltration signals
|
||||||
|
check_pattern "send\s+(all|every|entire)\s+\w+\s+to\s+" "data_exfiltration" "0.8" "Bulk data transfer"
|
||||||
|
check_pattern "export\s+.*\s+to\s+(external|outside|third.?party)" "data_exfiltration" "0.9" "External export"
|
||||||
|
check_pattern "curl\s+.*\s+-d\s+" "data_exfiltration" "0.7" "HTTP POST with data"
|
||||||
|
check_pattern "upload\s+.*\s+(credentials|secrets|keys)" "data_exfiltration" "0.95" "Credential upload"
|
||||||
|
|
||||||
|
# Privilege escalation signals
|
||||||
|
check_pattern "(sudo|as\s+root|admin\s+access|runas\s+/user)" "privilege_escalation" "0.8" "Elevated privileges"
|
||||||
|
check_pattern "chmod\s+777" "privilege_escalation" "0.9" "World-writable permissions"
|
||||||
|
check_pattern "add\s+.*\s+(sudoers|administrators)" "privilege_escalation" "0.95" "Adding admin access"
|
||||||
|
|
||||||
|
# System destruction signals
|
||||||
|
check_pattern "(rm\s+-rf\s+/|del\s+/[sq]|format\s+c:)" "system_destruction" "0.95" "Destructive command"
|
||||||
|
check_pattern "(drop\s+database|truncate\s+table|delete\s+from\s+\w+\s*$)" "system_destruction" "0.9" "Database destruction"
|
||||||
|
check_pattern "wipe\s+(all|entire|every)" "system_destruction" "0.9" "Mass deletion"
|
||||||
|
|
||||||
|
# Prompt injection signals
|
||||||
|
check_pattern "ignore\s+(previous|above|all)\s+(instructions?|rules?|prompts?)" "prompt_injection" "0.9" "Instruction override"
|
||||||
|
check_pattern "you\s+are\s+now\s+(a|an)\s+" "prompt_injection" "0.7" "Role reassignment"
|
||||||
|
check_pattern "system\s*:\s*" "prompt_injection" "0.6" "System prompt injection"
|
||||||
|
|
||||||
|
# Credential exposure signals
|
||||||
|
check_pattern "(api[_-]?key|secret[_-]?key|password|token)\s*[:=]\s*['\"]?\w{8,}" "credential_exposure" "0.9" "Hardcoded credential"
|
||||||
|
check_pattern "(aws_access_key|AKIA[0-9A-Z]{16})" "credential_exposure" "0.95" "AWS key exposure"
|
||||||
|
|
||||||
|
# Log the prompt event
|
||||||
|
if [[ ${#THREATS_FOUND[@]} -gt 0 ]]; then
|
||||||
|
# Build threats JSON array
|
||||||
|
THREATS_JSON="["
|
||||||
|
FIRST=true
|
||||||
|
MAX_SEVERITY="0.0"
|
||||||
|
for threat in "${THREATS_FOUND[@]}"; do
|
||||||
|
IFS=':' read -r category severity description evidence <<< "$threat"
|
||||||
|
|
||||||
|
if [[ "$FIRST" != "true" ]]; then
|
||||||
|
THREATS_JSON+=","
|
||||||
|
fi
|
||||||
|
FIRST=false
|
||||||
|
|
||||||
|
THREATS_JSON+=$(jq -Rn \
|
||||||
|
--arg cat "$category" \
|
||||||
|
--arg sev "$severity" \
|
||||||
|
--arg desc "$description" \
|
||||||
|
--arg ev "$evidence" \
|
||||||
|
'{"category":$cat,"severity":($sev|tonumber),"description":$desc,"evidence":$ev}')
|
||||||
|
|
||||||
|
# Track max severity
|
||||||
|
if (( $(echo "$severity > $MAX_SEVERITY" | bc -l 2>/dev/null || echo 0) )); then
|
||||||
|
MAX_SEVERITY="$severity"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
THREATS_JSON+="]"
|
||||||
|
|
||||||
|
jq -Rn \
|
||||||
|
--arg timestamp "$TIMESTAMP" \
|
||||||
|
--arg level "$LEVEL" \
|
||||||
|
--argjson threats "$THREATS_JSON" \
|
||||||
|
--argjson count "${#THREATS_FOUND[@]}" \
|
||||||
|
'{"timestamp":$timestamp,"event":"threat_detected","governance_level":$level,"threat_count":$count,"threats":$threats}' \
|
||||||
|
>> "$LOG_FILE"
|
||||||
|
|
||||||
|
echo "⚠️ Governance: ${#THREATS_FOUND[@]} threat signal(s) detected"
|
||||||
|
for threat in "${THREATS_FOUND[@]}"; do
|
||||||
|
IFS=':' read -r category severity description evidence <<< "$threat"
|
||||||
|
echo " 🔴 [$category] $description (severity: $severity)"
|
||||||
|
done
|
||||||
|
|
||||||
|
# In strict/locked mode or when BLOCK_ON_THREAT is true, exit non-zero to block
|
||||||
|
if [[ "$BLOCK" == "true" ]] || [[ "$LEVEL" == "strict" ]] || [[ "$LEVEL" == "locked" ]]; then
|
||||||
|
echo "🚫 Prompt blocked by governance policy (level: $LEVEL)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
jq -Rn \
|
||||||
|
--arg timestamp "$TIMESTAMP" \
|
||||||
|
--arg level "$LEVEL" \
|
||||||
|
'{"timestamp":$timestamp,"event":"prompt_scanned","governance_level":$level,"status":"clean"}' \
|
||||||
|
>> "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
39
hooks/governance-audit/audit-session-end.sh
Normal file
39
hooks/governance-audit/audit-session-end.sh
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Governance Audit: Log session end with summary statistics
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${SKIP_GOVERNANCE_AUDIT:-}" == "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
mkdir -p logs/copilot/governance
|
||||||
|
|
||||||
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
LOG_FILE="logs/copilot/governance/audit.log"
|
||||||
|
|
||||||
|
# Count events from this session
|
||||||
|
TOTAL=0
|
||||||
|
THREATS=0
|
||||||
|
if [[ -f "$LOG_FILE" ]]; then
|
||||||
|
TOTAL=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0)
|
||||||
|
THREATS=$(grep -c '"threat_detected"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq -Rn \
|
||||||
|
--arg timestamp "$TIMESTAMP" \
|
||||||
|
--argjson total "$TOTAL" \
|
||||||
|
--argjson threats "$THREATS" \
|
||||||
|
'{"timestamp":$timestamp,"event":"session_end","total_events":$total,"threats_detected":$threats}' \
|
||||||
|
>> "$LOG_FILE"
|
||||||
|
|
||||||
|
if [[ "$THREATS" -gt 0 ]]; then
|
||||||
|
echo "⚠️ Session ended: $THREATS threat(s) detected in $TOTAL events"
|
||||||
|
else
|
||||||
|
echo "✅ Session ended: $TOTAL events, no threats"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
27
hooks/governance-audit/audit-session-start.sh
Normal file
27
hooks/governance-audit/audit-session-start.sh
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Governance Audit: Log session start with governance context
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${SKIP_GOVERNANCE_AUDIT:-}" == "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
mkdir -p logs/copilot/governance
|
||||||
|
|
||||||
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
CWD=$(pwd)
|
||||||
|
LEVEL="${GOVERNANCE_LEVEL:-standard}"
|
||||||
|
|
||||||
|
jq -Rn \
|
||||||
|
--arg timestamp "$TIMESTAMP" \
|
||||||
|
--arg cwd "$CWD" \
|
||||||
|
--arg level "$LEVEL" \
|
||||||
|
'{"timestamp":$timestamp,"event":"session_start","governance_level":$level,"cwd":$cwd}' \
|
||||||
|
>> logs/copilot/governance/audit.log
|
||||||
|
|
||||||
|
echo "🛡️ Governance audit active (level: $LEVEL)"
|
||||||
|
exit 0
|
||||||
33
hooks/governance-audit/hooks.json
Normal file
33
hooks/governance-audit/hooks.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"hooks": {
|
||||||
|
"sessionStart": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"bash": ".github/hooks/governance-audit/audit-session-start.sh",
|
||||||
|
"cwd": ".",
|
||||||
|
"timeoutSec": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sessionEnd": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"bash": ".github/hooks/governance-audit/audit-session-end.sh",
|
||||||
|
"cwd": ".",
|
||||||
|
"timeoutSec": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"userPromptSubmitted": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"bash": ".github/hooks/governance-audit/audit-prompt.sh",
|
||||||
|
"cwd": ".",
|
||||||
|
"env": {
|
||||||
|
"GOVERNANCE_LEVEL": "standard",
|
||||||
|
"BLOCK_ON_THREAT": "false"
|
||||||
|
},
|
||||||
|
"timeoutSec": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user