|
| 1 | +--- |
| 2 | +name: 'Secrets Scanner' |
| 3 | +description: 'Scans files modified during a Copilot coding agent session for leaked secrets, credentials, and sensitive data' |
| 4 | +tags: ['security', 'secrets', 'scanning', 'pre-commit'] |
| 5 | +--- |
| 6 | + |
| 7 | +# Secrets Scanner Hook |
| 8 | + |
| 9 | +Scans files modified during a GitHub Copilot coding agent session for accidentally leaked secrets, credentials, API keys, and other sensitive data before they are committed. |
| 10 | + |
| 11 | +## Overview |
| 12 | + |
| 13 | +AI coding agents generate and modify code rapidly, which increases the risk of hardcoded secrets slipping into the codebase. This hook acts as a safety net by scanning all modified files at session end for 20+ categories of secret patterns, including: |
| 14 | + |
| 15 | +- **Cloud credentials**: AWS access keys, GCP service account keys, Azure client secrets |
| 16 | +- **Platform tokens**: GitHub PATs, npm tokens, Slack tokens, Stripe keys |
| 17 | +- **Private keys**: RSA, EC, OpenSSH, PGP, DSA private key blocks |
| 18 | +- **Connection strings**: Database URIs (PostgreSQL, MongoDB, MySQL, Redis, MSSQL) |
| 19 | +- **Generic secrets**: API keys, passwords, bearer tokens, JWTs |
| 20 | +- **Internal infrastructure**: Private IP addresses with ports |
| 21 | + |
| 22 | +## Features |
| 23 | + |
| 24 | +- **Two scan modes**: `warn` (log only) or `block` (exit non-zero to prevent commit) |
| 25 | +- **Two scan scopes**: `diff` (modified files vs HEAD) or `staged` (git-staged files only) |
| 26 | +- **Smart filtering**: Skips binary files, lock files, and placeholder/example values |
| 27 | +- **Allowlist support**: Exclude known false positives via `SECRETS_ALLOWLIST` |
| 28 | +- **Structured logging**: JSON Lines output for integration with monitoring tools |
| 29 | +- **Redacted output**: Findings are truncated in logs to avoid re-exposing secrets |
| 30 | +- **Zero dependencies**: Uses only standard Unix tools (`grep`, `file`, `git`) |
| 31 | + |
| 32 | +## Installation |
| 33 | + |
| 34 | +1. Copy the hook folder to your repository: |
| 35 | + |
| 36 | + ```bash |
| 37 | + cp -r hooks/secrets-scanner .github/hooks/ |
| 38 | + ``` |
| 39 | + |
| 40 | +2. Ensure the script is executable: |
| 41 | + |
| 42 | + ```bash |
| 43 | + chmod +x .github/hooks/secrets-scanner/scan-secrets.sh |
| 44 | + ``` |
| 45 | + |
| 46 | +3. Create the logs directory and add it to `.gitignore`: |
| 47 | + |
| 48 | + ```bash |
| 49 | + mkdir -p logs/copilot/secrets |
| 50 | + echo "logs/" >> .gitignore |
| 51 | + ``` |
| 52 | + |
| 53 | +4. Commit the hook configuration to your repository's default branch. |
| 54 | + |
| 55 | +## Configuration |
| 56 | + |
| 57 | +The hook is configured in `hooks.json` to run on the `sessionEnd` event: |
| 58 | + |
| 59 | +```json |
| 60 | +{ |
| 61 | + "version": 1, |
| 62 | + "hooks": { |
| 63 | + "sessionEnd": [ |
| 64 | + { |
| 65 | + "type": "command", |
| 66 | + "bash": ".github/hooks/secrets-scanner/scan-secrets.sh", |
| 67 | + "cwd": ".", |
| 68 | + "env": { |
| 69 | + "SCAN_MODE": "warn", |
| 70 | + "SCAN_SCOPE": "diff" |
| 71 | + }, |
| 72 | + "timeoutSec": 30 |
| 73 | + } |
| 74 | + ] |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +### Environment Variables |
| 80 | + |
| 81 | +| Variable | Values | Default | Description | |
| 82 | +|----------|--------|---------|-------------| |
| 83 | +| `SCAN_MODE` | `warn`, `block` | `warn` | `warn` logs findings only; `block` exits non-zero to prevent auto-commit | |
| 84 | +| `SCAN_SCOPE` | `diff`, `staged` | `diff` | `diff` scans uncommitted changes vs HEAD; `staged` scans only staged files | |
| 85 | +| `SKIP_SECRETS_SCAN` | `true` | unset | Disable the scanner entirely | |
| 86 | +| `SECRETS_LOG_DIR` | path | `logs/copilot/secrets` | Directory where scan logs are written | |
| 87 | +| `SECRETS_ALLOWLIST` | comma-separated | unset | Patterns to ignore (e.g., `test_key_123,example.com`) | |
| 88 | + |
| 89 | +## How It Works |
| 90 | + |
| 91 | +1. When a Copilot coding agent session ends, the hook executes |
| 92 | +2. Collects all modified files using `git diff` (respects the configured scope) |
| 93 | +3. Filters out binary files and lock files |
| 94 | +4. Scans each text file line-by-line against 20+ regex patterns for known secret formats |
| 95 | +5. Skips matches that look like placeholders (e.g., values containing `example`, `changeme`, `your_`) |
| 96 | +6. Checks matches against the allowlist if configured |
| 97 | +7. Reports findings with file path, line number, pattern name, and severity |
| 98 | +8. Writes a structured JSON log entry for audit purposes |
| 99 | +9. In `block` mode, exits non-zero to signal the agent to stop before committing |
| 100 | + |
| 101 | +## Detected Secret Patterns |
| 102 | + |
| 103 | +| Pattern | Severity | Example Match | |
| 104 | +|---------|----------|---------------| |
| 105 | +| `AWS_ACCESS_KEY` | critical | `AKIAIOSFODNN7EXAMPLE` | |
| 106 | +| `AWS_SECRET_KEY` | critical | `aws_secret_access_key = wJalr...` | |
| 107 | +| `GCP_SERVICE_ACCOUNT` | critical | `"type": "service_account"` | |
| 108 | +| `GCP_API_KEY` | high | `AIzaSyC...` | |
| 109 | +| `AZURE_CLIENT_SECRET` | critical | `azure_client_secret = ...` | |
| 110 | +| `GITHUB_PAT` | critical | `ghp_xxxxxxxxxxxx...` | |
| 111 | +| `GITHUB_FINE_GRAINED_PAT` | critical | `github_pat_...` | |
| 112 | +| `PRIVATE_KEY` | critical | `-----BEGIN RSA PRIVATE KEY-----` | |
| 113 | +| `GENERIC_SECRET` | high | `api_key = "sk-..."` | |
| 114 | +| `CONNECTION_STRING` | high | `postgresql://user:pass@host/db` | |
| 115 | +| `SLACK_TOKEN` | high | `xoxb-...` | |
| 116 | +| `STRIPE_SECRET_KEY` | critical | `sk_live_...` | |
| 117 | +| `NPM_TOKEN` | high | `npm_...` | |
| 118 | +| `JWT_TOKEN` | medium | `eyJhbGci...` | |
| 119 | +| `INTERNAL_IP_PORT` | medium | `192.168.1.1:8080` | |
| 120 | + |
| 121 | +See the full list in `scan-secrets.sh`. |
| 122 | + |
| 123 | +## Example Output |
| 124 | + |
| 125 | +### Clean scan |
| 126 | + |
| 127 | +``` |
| 128 | +🔍 Scanning 5 modified file(s) for secrets... |
| 129 | +✅ No secrets detected in 5 scanned file(s) |
| 130 | +``` |
| 131 | + |
| 132 | +### Findings detected (warn mode) |
| 133 | + |
| 134 | +``` |
| 135 | +🔍 Scanning 3 modified file(s) for secrets... |
| 136 | +
|
| 137 | +⚠️ Found 2 potential secret(s) in modified files: |
| 138 | +
|
| 139 | + FILE LINE PATTERN SEVERITY |
| 140 | + ---- ---- ------- -------- |
| 141 | + src/config.ts 12 GITHUB_PAT critical |
| 142 | + .env.local 3 CONNECTION_STRING high |
| 143 | +
|
| 144 | +💡 Review the findings above. Set SCAN_MODE=block to prevent commits with secrets. |
| 145 | +``` |
| 146 | + |
| 147 | +### Findings detected (block mode) |
| 148 | + |
| 149 | +``` |
| 150 | +🔍 Scanning 3 modified file(s) for secrets... |
| 151 | +
|
| 152 | +⚠️ Found 1 potential secret(s) in modified files: |
| 153 | +
|
| 154 | + FILE LINE PATTERN SEVERITY |
| 155 | + ---- ---- ------- -------- |
| 156 | + lib/auth.py 45 AWS_ACCESS_KEY critical |
| 157 | +
|
| 158 | +🚫 Session blocked: resolve the findings above before committing. |
| 159 | + Set SCAN_MODE=warn to log without blocking, or add patterns to SECRETS_ALLOWLIST. |
| 160 | +``` |
| 161 | + |
| 162 | +## Log Format |
| 163 | + |
| 164 | +Scan events are written to `logs/copilot/secrets/scan.log` in JSON Lines format: |
| 165 | + |
| 166 | +```json |
| 167 | +{"timestamp":"2026-03-13T10:30:00Z","event":"secrets_found","mode":"warn","scope":"diff","files_scanned":3,"finding_count":2,"findings":[{"file":"src/config.ts","line":12,"pattern":"GITHUB_PAT","severity":"critical","match":"ghp_...xyz1"}]} |
| 168 | +``` |
| 169 | + |
| 170 | +```json |
| 171 | +{"timestamp":"2026-03-13T10:30:00Z","event":"scan_complete","mode":"warn","scope":"diff","status":"clean","files_scanned":5} |
| 172 | +``` |
| 173 | + |
| 174 | +## Pairing with Other Hooks |
| 175 | + |
| 176 | +This hook pairs well with the **Session Auto-Commit** hook. When both are installed, order them so that `secrets-scanner` runs first: |
| 177 | + |
| 178 | +1. Secrets scanner runs at `sessionEnd`, catches leaked secrets |
| 179 | +2. Auto-commit runs at `sessionEnd`, only commits if all previous hooks pass |
| 180 | + |
| 181 | +Set `SCAN_MODE=block` to prevent auto-commit when secrets are detected. |
| 182 | + |
| 183 | +## Customization |
| 184 | + |
| 185 | +- **Add custom patterns**: Edit the `PATTERNS` array in `scan-secrets.sh` to add project-specific secret formats |
| 186 | +- **Adjust sensitivity**: Change severity levels or remove patterns that generate false positives |
| 187 | +- **Allowlist known values**: Use `SECRETS_ALLOWLIST` for test fixtures or known safe patterns |
| 188 | +- **Change log location**: Set `SECRETS_LOG_DIR` to route logs to your preferred directory |
| 189 | + |
| 190 | +## Disabling |
| 191 | + |
| 192 | +To temporarily disable the scanner: |
| 193 | + |
| 194 | +- Set `SKIP_SECRETS_SCAN=true` in the hook environment |
| 195 | +- Or remove the `sessionEnd` entry from `hooks.json` |
| 196 | + |
| 197 | +## Limitations |
| 198 | + |
| 199 | +- Pattern-based detection; does not perform entropy analysis or contextual validation |
| 200 | +- May produce false positives for test fixtures or example code (use the allowlist to suppress these) |
| 201 | +- Scans only text files; binary secrets (keystores, certificates in DER format) are not detected |
| 202 | +- Requires `git` to be available in the execution environment |
0 commit comments