Skip to content

Commit 7a329ce

Browse files
committed
Dependency License Checker Hook
1 parent 8d91380 commit 7a329ce

4 files changed

Lines changed: 585 additions & 0 deletions

File tree

docs/README.hooks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-hooks) for guidelines on how to
3131

3232
| Name | Description | Events | Bundled Assets |
3333
| ---- | ----------- | ------ | -------------- |
34+
| [Dependency License Checker](../hooks/dependency-license-checker/README.md) | Scans newly added dependencies for license compliance (GPL, AGPL, etc.) at session end | sessionEnd | `check-licenses.sh`<br />`hooks.json` |
3435
| [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` |
3536
| [Secrets Scanner](../hooks/secrets-scanner/README.md) | Scans files modified during a Copilot coding agent session for leaked secrets, credentials, and sensitive data | sessionEnd | `hooks.json`<br />`scan-secrets.sh` |
3637
| [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` |
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
---
2+
name: 'Dependency License Checker'
3+
description: 'Scans newly added dependencies for license compliance (GPL, AGPL, etc.) at session end'
4+
tags: ['compliance', 'license', 'dependencies', 'session-end']
5+
---
6+
7+
# Dependency License Checker Hook
8+
9+
Scans newly added dependencies for license compliance at the end of a GitHub Copilot coding agent session, flagging copyleft and restrictive licenses (GPL, AGPL, SSPL, etc.) before they get committed.
10+
11+
## Overview
12+
13+
AI coding agents may add new dependencies during a session without considering license implications. This hook acts as a compliance safety net by detecting new dependencies across multiple ecosystems, looking up their licenses, and checking them against a configurable blocked list of copyleft and restrictive licenses.
14+
15+
## Features
16+
17+
- **Multi-ecosystem support**: npm, pip, Go, Ruby, and Rust dependency detection
18+
- **Two modes**: `warn` (log only) or `block` (exit non-zero to prevent commit)
19+
- **Configurable blocked list**: Default copyleft set with full SPDX variant coverage
20+
- **Allowlist support**: Skip known-acceptable packages via `LICENSE_ALLOWLIST`
21+
- **Smart detection**: Uses `git diff` to detect only newly added dependencies
22+
- **Multiple lookup strategies**: Local cache, package manager CLI, with fallback to UNKNOWN
23+
- **Structured logging**: JSON Lines output for integration with monitoring tools
24+
- **Timeout protection**: Each license lookup wrapped with 5-second timeout
25+
- **Zero mandatory dependencies**: Uses standard Unix tools; optional `jq` for better JSON parsing
26+
27+
## Installation
28+
29+
1. Copy the hook folder to your repository:
30+
31+
```bash
32+
cp -r hooks/dependency-license-checker .github/hooks/
33+
```
34+
35+
2. Ensure the script is executable:
36+
37+
```bash
38+
chmod +x .github/hooks/dependency-license-checker/check-licenses.sh
39+
```
40+
41+
3. Create the logs directory and add it to `.gitignore`:
42+
43+
```bash
44+
mkdir -p logs/copilot/license-checker
45+
echo "logs/" >> .gitignore
46+
```
47+
48+
4. Commit the hook configuration to your repository's default branch.
49+
50+
## Configuration
51+
52+
The hook is configured in `hooks.json` to run on the `sessionEnd` event:
53+
54+
```json
55+
{
56+
"version": 1,
57+
"hooks": {
58+
"sessionEnd": [
59+
{
60+
"type": "command",
61+
"bash": ".github/hooks/dependency-license-checker/check-licenses.sh",
62+
"cwd": ".",
63+
"env": {
64+
"LICENSE_MODE": "warn"
65+
},
66+
"timeoutSec": 60
67+
}
68+
]
69+
}
70+
}
71+
```
72+
73+
### Environment Variables
74+
75+
| Variable | Values | Default | Description |
76+
|----------|--------|---------|-------------|
77+
| `LICENSE_MODE` | `warn`, `block` | `warn` | `warn` logs violations only; `block` exits non-zero to prevent auto-commit |
78+
| `SKIP_LICENSE_CHECK` | `true` | unset | Disable the checker entirely |
79+
| `LICENSE_LOG_DIR` | path | `logs/copilot/license-checker` | Directory where check logs are written |
80+
| `BLOCKED_LICENSES` | comma-separated SPDX IDs | copyleft set | Licenses to flag as violations |
81+
| `LICENSE_ALLOWLIST` | comma-separated | unset | Package names to skip (e.g., `linux-headers,glibc`) |
82+
83+
## How It Works
84+
85+
1. When a Copilot coding agent session ends, the hook executes
86+
2. Runs `git diff HEAD` against manifest files (package.json, requirements.txt, go.mod, etc.)
87+
3. Extracts newly added package names from the diff output
88+
4. Looks up each package's license using local caches and package manager CLIs
89+
5. Checks each license against the blocked list using case-insensitive substring matching
90+
6. Skips packages in the allowlist before flagging
91+
7. Reports findings in a formatted table with package, ecosystem, license, and status
92+
8. Writes a structured JSON log entry for audit purposes
93+
9. In `block` mode, exits non-zero to signal the agent to stop before committing
94+
95+
## Supported Ecosystems
96+
97+
| Ecosystem | Manifest File | Primary Lookup | Fallback |
98+
|-----------|--------------|----------------|----------|
99+
| npm/yarn/pnpm | `package.json` | `node_modules/<pkg>/package.json` license field | `npm view <pkg> license` |
100+
| pip | `requirements.txt`, `pyproject.toml` | `pip show <pkg>` License field | UNKNOWN |
101+
| Go | `go.mod` | LICENSE file in module cache (keyword match) | UNKNOWN |
102+
| Ruby | `Gemfile` | `gem spec <pkg> license` | UNKNOWN |
103+
| Rust | `Cargo.toml` | `cargo metadata` license field | UNKNOWN |
104+
105+
## Default Blocked Licenses
106+
107+
The following licenses are blocked by default (copyleft and restrictive):
108+
109+
- **GPL**: GPL-2.0, GPL-2.0-only, GPL-2.0-or-later, GPL-3.0, GPL-3.0-only, GPL-3.0-or-later
110+
- **AGPL**: AGPL-1.0, AGPL-3.0, AGPL-3.0-only, AGPL-3.0-or-later
111+
- **LGPL**: LGPL-2.0, LGPL-2.1, LGPL-2.1-only, LGPL-2.1-or-later, LGPL-3.0, LGPL-3.0-only, LGPL-3.0-or-later
112+
- **Other**: SSPL-1.0, EUPL-1.1, EUPL-1.2, OSL-3.0, CPAL-1.0, CPL-1.0
113+
- **Creative Commons (restrictive)**: CC-BY-SA-4.0, CC-BY-NC-4.0, CC-BY-NC-SA-4.0
114+
115+
Override with `BLOCKED_LICENSES` to customize.
116+
117+
## Example Output
118+
119+
### Clean scan (no new dependencies)
120+
121+
```
122+
✅ No new dependencies detected
123+
```
124+
125+
### Clean scan (all compliant)
126+
127+
```
128+
🔍 Checking licenses for 3 new dependency(ies)...
129+
130+
PACKAGE ECOSYSTEM LICENSE STATUS
131+
------- --------- ------- ------
132+
express npm MIT OK
133+
lodash npm MIT OK
134+
axios npm MIT OK
135+
136+
✅ All 3 dependencies have compliant licenses
137+
```
138+
139+
### Violations detected (warn mode)
140+
141+
```
142+
🔍 Checking licenses for 2 new dependency(ies)...
143+
144+
PACKAGE ECOSYSTEM LICENSE STATUS
145+
------- --------- ------- ------
146+
react npm MIT OK
147+
readline-sync npm GPL-3.0 BLOCKED
148+
149+
⚠️ Found 1 license violation(s):
150+
151+
- readline-sync (npm): GPL-3.0
152+
153+
💡 Review the violations above. Set LICENSE_MODE=block to prevent commits with license issues.
154+
```
155+
156+
### Violations detected (block mode)
157+
158+
```
159+
🔍 Checking licenses for 2 new dependency(ies)...
160+
161+
PACKAGE ECOSYSTEM LICENSE STATUS
162+
------- --------- ------- ------
163+
flask pip BSD-3-Clause OK
164+
copyleft-lib pip AGPL-3.0 BLOCKED
165+
166+
⚠️ Found 1 license violation(s):
167+
168+
- copyleft-lib (pip): AGPL-3.0
169+
170+
🚫 Session blocked: resolve license violations above before committing.
171+
Set LICENSE_MODE=warn to log without blocking, or add packages to LICENSE_ALLOWLIST.
172+
```
173+
174+
## Log Format
175+
176+
Check events are written to `logs/copilot/license-checker/check.log` in JSON Lines format:
177+
178+
```json
179+
{"timestamp":"2026-03-17T10:30:00Z","event":"license_check_complete","mode":"warn","dependencies_checked":3,"violation_count":1,"violations":[{"package":"readline-sync","ecosystem":"npm","license":"GPL-3.0","status":"BLOCKED"}]}
180+
```
181+
182+
```json
183+
{"timestamp":"2026-03-17T10:30:00Z","event":"license_check_complete","mode":"warn","status":"clean","dependencies_checked":0}
184+
```
185+
186+
## Pairing with Other Hooks
187+
188+
This hook pairs well with:
189+
190+
- **Secrets Scanner**: Run secrets scanning first, then license checking, before auto-commit
191+
- **Session Auto-Commit**: When both are installed, order them so that `dependency-license-checker` runs first. Set `LICENSE_MODE=block` to prevent auto-commit when violations are detected.
192+
193+
## Customization
194+
195+
- **Modify blocked licenses**: Set `BLOCKED_LICENSES` to a custom comma-separated list of SPDX IDs
196+
- **Allowlist packages**: Use `LICENSE_ALLOWLIST` for known-acceptable packages with copyleft licenses
197+
- **Change log location**: Set `LICENSE_LOG_DIR` to route logs to your preferred directory
198+
- **Add ecosystems**: Extend the detection and lookup sections in `check-licenses.sh`
199+
200+
## Disabling
201+
202+
To temporarily disable the checker:
203+
204+
- Set `SKIP_LICENSE_CHECK=true` in the hook environment
205+
- Or remove the `sessionEnd` entry from `hooks.json`
206+
207+
## Limitations
208+
209+
- License detection relies on manifest file diffs; dependencies added outside standard manifest files are not detected
210+
- License lookup requires the package manager CLI or local cache to be available
211+
- Compound SPDX expressions (e.g., `MIT OR GPL-3.0`) are flagged if any component matches the blocked list
212+
- Does not perform deep transitive dependency license analysis
213+
- Network lookups (npm view, etc.) may fail in offline or restricted environments
214+
- Requires `git` to be available in the execution environment

0 commit comments

Comments
 (0)