Dependency License Checker Hook (#1046)

This commit is contained in:
Ajith Raghavan
2026-03-19 04:54:53 +05:30
committed by GitHub
parent 3b462df7b0
commit e8b1e9350c
4 changed files with 585 additions and 0 deletions

View File

@@ -31,6 +31,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-hooks) for guidelines on how to
| Name | Description | Events | Bundled Assets |
| ---- | ----------- | ------ | -------------- |
| [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` |
| [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` |
| [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` |
| [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` |

View File

@@ -0,0 +1,214 @@
---
name: 'Dependency License Checker'
description: 'Scans newly added dependencies for license compliance (GPL, AGPL, etc.) at session end'
tags: ['compliance', 'license', 'dependencies', 'session-end']
---
# Dependency License Checker Hook
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.
## Overview
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.
## Features
- **Multi-ecosystem support**: npm, pip, Go, Ruby, and Rust dependency detection
- **Two modes**: `warn` (log only) or `block` (exit non-zero to prevent commit)
- **Configurable blocked list**: Default copyleft set with full SPDX variant coverage
- **Allowlist support**: Skip known-acceptable packages via `LICENSE_ALLOWLIST`
- **Smart detection**: Uses `git diff` to detect only newly added dependencies
- **Multiple lookup strategies**: Local cache, package manager CLI, with fallback to UNKNOWN
- **Structured logging**: JSON Lines output for integration with monitoring tools
- **Timeout protection**: Each license lookup wrapped with 5-second timeout
- **Zero mandatory dependencies**: Uses standard Unix tools; optional `jq` for better JSON parsing
## Installation
1. Copy the hook folder to your repository:
```bash
cp -r hooks/dependency-license-checker .github/hooks/
```
2. Ensure the script is executable:
```bash
chmod +x .github/hooks/dependency-license-checker/check-licenses.sh
```
3. Create the logs directory and add it to `.gitignore`:
```bash
mkdir -p logs/copilot/license-checker
echo "logs/" >> .gitignore
```
4. Commit the hook configuration to your repository's default branch.
## Configuration
The hook is configured in `hooks.json` to run on the `sessionEnd` event:
```json
{
"version": 1,
"hooks": {
"sessionEnd": [
{
"type": "command",
"bash": ".github/hooks/dependency-license-checker/check-licenses.sh",
"cwd": ".",
"env": {
"LICENSE_MODE": "warn"
},
"timeoutSec": 60
}
]
}
}
```
### Environment Variables
| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `LICENSE_MODE` | `warn`, `block` | `warn` | `warn` logs violations only; `block` exits non-zero to prevent auto-commit |
| `SKIP_LICENSE_CHECK` | `true` | unset | Disable the checker entirely |
| `LICENSE_LOG_DIR` | path | `logs/copilot/license-checker` | Directory where check logs are written |
| `BLOCKED_LICENSES` | comma-separated SPDX IDs | copyleft set | Licenses to flag as violations |
| `LICENSE_ALLOWLIST` | comma-separated | unset | Package names to skip (e.g., `linux-headers,glibc`) |
## How It Works
1. When a Copilot coding agent session ends, the hook executes
2. Runs `git diff HEAD` against manifest files (package.json, requirements.txt, go.mod, etc.)
3. Extracts newly added package names from the diff output
4. Looks up each package's license using local caches and package manager CLIs
5. Checks each license against the blocked list using case-insensitive substring matching
6. Skips packages in the allowlist before flagging
7. Reports findings in a formatted table with package, ecosystem, license, and status
8. Writes a structured JSON log entry for audit purposes
9. In `block` mode, exits non-zero to signal the agent to stop before committing
## Supported Ecosystems
| Ecosystem | Manifest File | Primary Lookup | Fallback |
|-----------|--------------|----------------|----------|
| npm/yarn/pnpm | `package.json` | `node_modules/<pkg>/package.json` license field | `npm view <pkg> license` |
| pip | `requirements.txt`, `pyproject.toml` | `pip show <pkg>` License field | UNKNOWN |
| Go | `go.mod` | LICENSE file in module cache (keyword match) | UNKNOWN |
| Ruby | `Gemfile` | `gem spec <pkg> license` | UNKNOWN |
| Rust | `Cargo.toml` | `cargo metadata` license field | UNKNOWN |
## Default Blocked Licenses
The following licenses are blocked by default (copyleft and restrictive):
- **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
- **AGPL**: AGPL-1.0, AGPL-3.0, AGPL-3.0-only, AGPL-3.0-or-later
- **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
- **Other**: SSPL-1.0, EUPL-1.1, EUPL-1.2, OSL-3.0, CPAL-1.0, CPL-1.0
- **Creative Commons (restrictive)**: CC-BY-SA-4.0, CC-BY-NC-4.0, CC-BY-NC-SA-4.0
Override with `BLOCKED_LICENSES` to customize.
## Example Output
### Clean scan (no new dependencies)
```
✅ No new dependencies detected
```
### Clean scan (all compliant)
```
🔍 Checking licenses for 3 new dependency(ies)...
PACKAGE ECOSYSTEM LICENSE STATUS
------- --------- ------- ------
express npm MIT OK
lodash npm MIT OK
axios npm MIT OK
✅ All 3 dependencies have compliant licenses
```
### Violations detected (warn mode)
```
🔍 Checking licenses for 2 new dependency(ies)...
PACKAGE ECOSYSTEM LICENSE STATUS
------- --------- ------- ------
react npm MIT OK
readline-sync npm GPL-3.0 BLOCKED
⚠️ Found 1 license violation(s):
- readline-sync (npm): GPL-3.0
💡 Review the violations above. Set LICENSE_MODE=block to prevent commits with license issues.
```
### Violations detected (block mode)
```
🔍 Checking licenses for 2 new dependency(ies)...
PACKAGE ECOSYSTEM LICENSE STATUS
------- --------- ------- ------
flask pip BSD-3-Clause OK
copyleft-lib pip AGPL-3.0 BLOCKED
⚠️ Found 1 license violation(s):
- copyleft-lib (pip): AGPL-3.0
🚫 Session blocked: resolve license violations above before committing.
Set LICENSE_MODE=warn to log without blocking, or add packages to LICENSE_ALLOWLIST.
```
## Log Format
Check events are written to `logs/copilot/license-checker/check.log` in JSON Lines format:
```json
{"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"}]}
```
```json
{"timestamp":"2026-03-17T10:30:00Z","event":"license_check_complete","mode":"warn","status":"clean","dependencies_checked":0}
```
## Pairing with Other Hooks
This hook pairs well with:
- **Secrets Scanner**: Run secrets scanning first, then license checking, before auto-commit
- **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.
## Customization
- **Modify blocked licenses**: Set `BLOCKED_LICENSES` to a custom comma-separated list of SPDX IDs
- **Allowlist packages**: Use `LICENSE_ALLOWLIST` for known-acceptable packages with copyleft licenses
- **Change log location**: Set `LICENSE_LOG_DIR` to route logs to your preferred directory
- **Add ecosystems**: Extend the detection and lookup sections in `check-licenses.sh`
## Disabling
To temporarily disable the checker:
- Set `SKIP_LICENSE_CHECK=true` in the hook environment
- Or remove the `sessionEnd` entry from `hooks.json`
## Limitations
- License detection relies on manifest file diffs; dependencies added outside standard manifest files are not detected
- License lookup requires the package manager CLI or local cache to be available
- Compound SPDX expressions (e.g., `MIT OR GPL-3.0`) are flagged if any component matches the blocked list
- Does not perform deep transitive dependency license analysis
- Network lookups (npm view, etc.) may fail in offline or restricted environments
- Requires `git` to be available in the execution environment

View File

@@ -0,0 +1,354 @@
#!/bin/bash
# Dependency License Checker Hook
# Scans newly added dependencies for license compliance (GPL, AGPL, etc.)
# at session end, before they get committed.
#
# Environment variables:
# LICENSE_MODE - "warn" (log only) or "block" (exit non-zero on violations) (default: warn)
# SKIP_LICENSE_CHECK - "true" to disable entirely (default: unset)
# LICENSE_LOG_DIR - Directory for check logs (default: logs/copilot/license-checker)
# BLOCKED_LICENSES - Comma-separated SPDX IDs to flag (default: copyleft set)
# LICENSE_ALLOWLIST - Comma-separated package names to skip (default: unset)
set -euo pipefail
# ---------------------------------------------------------------------------
# Early exit if disabled
# ---------------------------------------------------------------------------
if [[ "${SKIP_LICENSE_CHECK:-}" == "true" ]]; then
echo "⏭️ License check skipped (SKIP_LICENSE_CHECK=true)"
exit 0
fi
# Ensure we are in a git repository
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
echo "⚠️ Not in a git repository, skipping license check"
exit 0
fi
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
MODE="${LICENSE_MODE:-warn}"
LOG_DIR="${LICENSE_LOG_DIR:-logs/copilot/license-checker}"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
FINDING_COUNT=0
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/check.log"
# Default blocked licenses (copyleft / restrictive)
DEFAULT_BLOCKED="GPL-2.0,GPL-2.0-only,GPL-2.0-or-later,GPL-3.0,GPL-3.0-only,GPL-3.0-or-later,AGPL-1.0,AGPL-3.0,AGPL-3.0-only,AGPL-3.0-or-later,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,SSPL-1.0,EUPL-1.1,EUPL-1.2,OSL-3.0,CPAL-1.0,CPL-1.0,CC-BY-SA-4.0,CC-BY-NC-4.0,CC-BY-NC-SA-4.0"
BLOCKED_LIST=()
IFS=',' read -ra BLOCKED_LIST <<< "${BLOCKED_LICENSES:-$DEFAULT_BLOCKED}"
# Parse allowlist
ALLOWLIST=()
if [[ -n "${LICENSE_ALLOWLIST:-}" ]]; then
IFS=',' read -ra ALLOWLIST <<< "$LICENSE_ALLOWLIST"
fi
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
json_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g'
}
is_allowlisted() {
local pkg="$1"
for entry in "${ALLOWLIST[@]}"; do
entry=$(printf '%s' "$entry" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$entry" ]] && continue
if [[ "$pkg" == "$entry" ]]; then
return 0
fi
done
return 1
}
is_blocked_license() {
local license="$1"
local license_lower
license_lower=$(printf '%s' "$license" | tr '[:upper:]' '[:lower:]')
for blocked in "${BLOCKED_LIST[@]}"; do
blocked=$(printf '%s' "$blocked" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$blocked" ]] && continue
local blocked_lower
blocked_lower=$(printf '%s' "$blocked" | tr '[:upper:]' '[:lower:]')
# Substring match to handle SPDX variants and compound expressions
if [[ "$license_lower" == *"$blocked_lower"* ]]; then
return 0
fi
done
return 1
}
# ---------------------------------------------------------------------------
# Phase 1: Detect new dependencies per ecosystem
# ---------------------------------------------------------------------------
NEW_DEPS=()
# npm / yarn / pnpm — package.json
if git diff HEAD -- package.json &>/dev/null; then
while IFS= read -r line; do
# Match added lines like: "package-name": "^1.0.0"
pkg=$(printf '%s' "$line" | sed -n 's/^+[[:space:]]*"\([^"]*\)"[[:space:]]*:[[:space:]]*"[^"]*".*/\1/p')
if [[ -n "$pkg" && "$pkg" != "name" && "$pkg" != "version" && "$pkg" != "description" && "$pkg" != "main" && "$pkg" != "scripts" && "$pkg" != "dependencies" && "$pkg" != "devDependencies" && "$pkg" != "peerDependencies" && "$pkg" != "optionalDependencies" ]]; then
NEW_DEPS+=("npm:$pkg")
fi
done < <(git diff HEAD -- package.json 2>/dev/null | grep '^+' | grep -v '^+++')
fi
# pip — requirements.txt
if git diff HEAD -- requirements.txt &>/dev/null; then
while IFS= read -r line; do
# Skip comments and blank lines
clean=$(printf '%s' "$line" | sed 's/^+//')
[[ "$clean" =~ ^[[:space:]]*# ]] && continue
[[ -z "$clean" ]] && continue
# Extract package name before ==, >=, <=, ~=, !=, etc.
pkg=$(printf '%s' "$clean" | sed 's/[[:space:]]*[><=!~].*//' | sed 's/[[:space:]]*//')
if [[ -n "$pkg" ]]; then
NEW_DEPS+=("pip:$pkg")
fi
done < <(git diff HEAD -- requirements.txt 2>/dev/null | grep '^+' | grep -v '^+++')
fi
# pip — pyproject.toml
if git diff HEAD -- pyproject.toml &>/dev/null; then
while IFS= read -r line; do
# Match added lines with quoted dependency strings
pkg=$(printf '%s' "$line" | sed -n 's/^+[[:space:]]*"\([A-Za-z0-9_-]*\).*/\1/p')
if [[ -n "$pkg" ]]; then
NEW_DEPS+=("pip:$pkg")
fi
done < <(git diff HEAD -- pyproject.toml 2>/dev/null | grep '^+' | grep -v '^+++')
fi
# Go — go.mod
if git diff HEAD -- go.mod &>/dev/null; then
while IFS= read -r line; do
# Match added require entries like: + github.com/foo/bar v1.2.3
pkg=$(printf '%s' "$line" | sed -n 's/^+[[:space:]]*\([a-zA-Z0-9._/-]*\.[a-zA-Z0-9._/-]*\)[[:space:]].*/\1/p')
if [[ -n "$pkg" && "$pkg" != "module" && "$pkg" != "go" && "$pkg" != "require" ]]; then
NEW_DEPS+=("go:$pkg")
fi
done < <(git diff HEAD -- go.mod 2>/dev/null | grep '^+' | grep -v '^+++')
fi
# Ruby — Gemfile
if git diff HEAD -- Gemfile &>/dev/null; then
while IFS= read -r line; do
# Match added gem lines like: +gem 'package-name'
pkg=$(printf '%s' "$line" | sed -n "s/^+[[:space:]]*gem[[:space:]]*['\"\`]\([^'\"\`]*\)['\"\`].*/\1/p")
if [[ -n "$pkg" ]]; then
NEW_DEPS+=("ruby:$pkg")
fi
done < <(git diff HEAD -- Gemfile 2>/dev/null | grep '^+' | grep -v '^+++')
fi
# Rust — Cargo.toml
if git diff HEAD -- Cargo.toml &>/dev/null; then
while IFS= read -r line; do
# Match added dependency entries like: +package-name = "1.0" or +package-name = { version = "1.0" }
pkg=$(printf '%s' "$line" | sed -n 's/^+[[:space:]]*\([a-zA-Z0-9_-]*\)[[:space:]]*=.*/\1/p')
if [[ -n "$pkg" && "$pkg" != "name" && "$pkg" != "version" && "$pkg" != "edition" && "$pkg" != "authors" && "$pkg" != "description" && "$pkg" != "license" && "$pkg" != "repository" && "$pkg" != "rust-version" ]]; then
NEW_DEPS+=("rust:$pkg")
fi
done < <(git diff HEAD -- Cargo.toml 2>/dev/null | grep '^+' | grep -v '^+++')
fi
# Exit clean if no new dependencies found
if [[ ${#NEW_DEPS[@]} -eq 0 ]]; then
echo "✅ No new dependencies detected"
printf '{"timestamp":"%s","event":"license_check_complete","mode":"%s","status":"clean","dependencies_checked":0}\n' \
"$TIMESTAMP" "$MODE" >> "$LOG_FILE"
exit 0
fi
echo "🔍 Checking licenses for ${#NEW_DEPS[@]} new dependency(ies)..."
# ---------------------------------------------------------------------------
# Phase 2: Check license per dependency
# ---------------------------------------------------------------------------
RESULTS=()
get_license() {
local ecosystem="$1"
local pkg="$2"
local license="UNKNOWN"
case "$ecosystem" in
npm)
# Primary: check node_modules
if [[ -f "node_modules/$pkg/package.json" ]]; then
if command -v jq &>/dev/null; then
license=$(jq -r '.license // "UNKNOWN"' "node_modules/$pkg/package.json" 2>/dev/null || echo "UNKNOWN")
else
license=$(grep -oE '"license"\s*:\s*"[^"]*"' "node_modules/$pkg/package.json" 2>/dev/null | head -1 | sed 's/.*"license"\s*:\s*"//;s/"//' || echo "UNKNOWN")
fi
fi
# Fallback: npm view
if [[ "$license" == "UNKNOWN" ]] && command -v npm &>/dev/null; then
license=$(timeout 5 npm view "$pkg" license 2>/dev/null || echo "UNKNOWN")
fi
;;
pip)
# Primary: pip show
if command -v pip &>/dev/null; then
license=$(timeout 5 pip show "$pkg" 2>/dev/null | grep -i '^License:' | sed 's/^[Ll]icense:[[:space:]]*//' || echo "UNKNOWN")
elif command -v pip3 &>/dev/null; then
license=$(timeout 5 pip3 show "$pkg" 2>/dev/null | grep -i '^License:' | sed 's/^[Ll]icense:[[:space:]]*//' || echo "UNKNOWN")
fi
;;
go)
# Check module cache for LICENSE file
local gopath="${GOPATH:-$HOME/go}"
local mod_dir="$gopath/pkg/mod/$pkg"
# Try to find the latest version directory
if [[ -d "$gopath/pkg/mod" ]]; then
local found_dir
found_dir=$(find "$gopath/pkg/mod" -maxdepth 4 -path "*${pkg}@*" -type d 2>/dev/null | head -1)
if [[ -n "$found_dir" ]]; then
local lic_file
lic_file=$(find "$found_dir" -maxdepth 1 -iname 'LICENSE*' -type f 2>/dev/null | head -1)
if [[ -n "$lic_file" ]]; then
# Keyword match against common license identifiers
if grep -qiE 'GNU GENERAL PUBLIC LICENSE' "$lic_file" 2>/dev/null; then
if grep -qiE 'Version 3' "$lic_file" 2>/dev/null; then
license="GPL-3.0"
elif grep -qiE 'Version 2' "$lic_file" 2>/dev/null; then
license="GPL-2.0"
else
license="GPL"
fi
elif grep -qiE 'GNU LESSER GENERAL PUBLIC' "$lic_file" 2>/dev/null; then
license="LGPL"
elif grep -qiE 'GNU AFFERO GENERAL PUBLIC' "$lic_file" 2>/dev/null; then
license="AGPL-3.0"
elif grep -qiE 'MIT License' "$lic_file" 2>/dev/null; then
license="MIT"
elif grep -qiE 'Apache License' "$lic_file" 2>/dev/null; then
license="Apache-2.0"
elif grep -qiE 'BSD' "$lic_file" 2>/dev/null; then
license="BSD"
fi
fi
fi
fi
;;
ruby)
# gem spec
if command -v gem &>/dev/null; then
license=$(timeout 5 gem spec "$pkg" license 2>/dev/null | grep -v '^---' | grep -v '^\.\.\.' | sed 's/^- //' | head -1 || echo "UNKNOWN")
[[ -z "$license" ]] && license="UNKNOWN"
fi
;;
rust)
# cargo metadata
if command -v cargo &>/dev/null; then
if command -v jq &>/dev/null; then
license=$(timeout 5 cargo metadata --format-version 1 2>/dev/null | jq -r ".packages[] | select(.name == \"$pkg\") | .license // \"UNKNOWN\"" 2>/dev/null | head -1 || echo "UNKNOWN")
fi
fi
;;
esac
# Normalize empty / whitespace-only to UNKNOWN
license=$(printf '%s' "$license" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$license" ]] && license="UNKNOWN"
printf '%s' "$license"
}
for dep in "${NEW_DEPS[@]}"; do
ecosystem="${dep%%:*}"
pkg="${dep#*:}"
license=$(get_license "$ecosystem" "$pkg")
RESULTS+=("$ecosystem $pkg $license")
done
# ---------------------------------------------------------------------------
# Phase 3 & 4: Check against blocked list and allowlist
# ---------------------------------------------------------------------------
VIOLATIONS=()
for result in "${RESULTS[@]}"; do
IFS=$'\t' read -r ecosystem pkg license <<< "$result"
# Phase 4: Skip allowlisted packages
if [[ ${#ALLOWLIST[@]} -gt 0 ]] && is_allowlisted "$pkg"; then
continue
fi
# Phase 3: Check against blocked list
if is_blocked_license "$license"; then
VIOLATIONS+=("$pkg $ecosystem $license BLOCKED")
FINDING_COUNT=$((FINDING_COUNT + 1))
fi
done
# ---------------------------------------------------------------------------
# Phase 5: Output & logging
# ---------------------------------------------------------------------------
echo ""
printf " %-30s %-12s %-30s %s\n" "PACKAGE" "ECOSYSTEM" "LICENSE" "STATUS"
printf " %-30s %-12s %-30s %s\n" "-------" "---------" "-------" "------"
for result in "${RESULTS[@]}"; do
IFS=$'\t' read -r ecosystem pkg license <<< "$result"
status="OK"
if [[ ${#ALLOWLIST[@]} -gt 0 ]] && is_allowlisted "$pkg"; then
status="ALLOWLISTED"
elif is_blocked_license "$license"; then
status="BLOCKED"
fi
printf " %-30s %-12s %-30s %s\n" "$pkg" "$ecosystem" "$license" "$status"
done
echo ""
# Build JSON findings array
FINDINGS_JSON="["
FIRST=true
for violation in "${VIOLATIONS[@]}"; do
IFS=$'\t' read -r pkg ecosystem license status <<< "$violation"
if [[ "$FIRST" != "true" ]]; then
FINDINGS_JSON+=","
fi
FIRST=false
FINDINGS_JSON+="{\"package\":\"$(json_escape "$pkg")\",\"ecosystem\":\"$(json_escape "$ecosystem")\",\"license\":\"$(json_escape "$license")\",\"status\":\"$(json_escape "$status")\"}"
done
FINDINGS_JSON+="]"
# Write structured log entry
printf '{"timestamp":"%s","event":"license_check_complete","mode":"%s","dependencies_checked":%d,"violation_count":%d,"violations":%s}\n' \
"$TIMESTAMP" "$MODE" "${#RESULTS[@]}" "$FINDING_COUNT" "$FINDINGS_JSON" >> "$LOG_FILE"
if [[ $FINDING_COUNT -gt 0 ]]; then
echo "⚠️ Found $FINDING_COUNT license violation(s):"
echo ""
for violation in "${VIOLATIONS[@]}"; do
IFS=$'\t' read -r pkg ecosystem license status <<< "$violation"
echo " - $pkg ($ecosystem): $license"
done
echo ""
if [[ "$MODE" == "block" ]]; then
echo "🚫 Session blocked: resolve license violations above before committing."
echo " Set LICENSE_MODE=warn to log without blocking, or add packages to LICENSE_ALLOWLIST."
exit 1
else
echo "💡 Review the violations above. Set LICENSE_MODE=block to prevent commits with license issues."
fi
else
echo "✅ All ${#RESULTS[@]} dependencies have compliant licenses"
fi
exit 0

View File

@@ -0,0 +1,16 @@
{
"version": 1,
"hooks": {
"sessionEnd": [
{
"type": "command",
"bash": ".github/hooks/dependency-license-checker/check-licenses.sh",
"cwd": ".",
"env": {
"LICENSE_MODE": "warn"
},
"timeoutSec": 60
}
]
}
}