chore: import upstream snapshot with attribution
This commit is contained in:
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# CI license-compliance gate (runs in the security workflow for both dry
|
||||
# runs and releases). Two layers:
|
||||
# 1) Structural: every vendored component directory must physically carry
|
||||
# a license file.
|
||||
# 2) ScanCode Toolkit license detection over every vendored license text
|
||||
# and all first-party sources; ONE detection outside
|
||||
# scripts/license-policy.json fails the gate (see license-gate-check.py).
|
||||
#
|
||||
# Grammar parser bodies (generated parser.c, no headers) are excluded from
|
||||
# the ScanCode pass for runtime; their per-directory license files ARE
|
||||
# scanned, and layer 1 guarantees each grammar carries one.
|
||||
#
|
||||
# Requires: scancode (pipx install scancode-toolkit), python3.
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
# --selftest: plant a violation and assert the structural layer catches it.
|
||||
# Run in CI before the real gate so a silently-broken gate cannot pass.
|
||||
if [ "${1:-}" = "--selftest" ]; then
|
||||
TESTDIR="$ROOT/vendored/.gate-selftest-$$"
|
||||
mkdir -p "$TESTDIR"
|
||||
echo "int x;" > "$TESTDIR/x.c"
|
||||
# The child gate exits 1 on the planted violation (that is the point) —
|
||||
# capture its output first so pipefail cannot mask the grep result.
|
||||
GATE_OUT="$("$0" 2>/dev/null || true)"
|
||||
rm -rf "$TESTDIR"
|
||||
if printf '%s' "$GATE_OUT" | grep -q "BLOCKED: vendored code in .*gate-selftest"; then
|
||||
echo "OK: gate self-test — planted violation was detected"
|
||||
exit 0
|
||||
fi
|
||||
echo "FAIL: gate self-test — planted unlicensed file was NOT detected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== License gate 1/2: structural coverage ==="
|
||||
# Rule: every directory under either vendored tree that contains source or
|
||||
# data files must have a license file in itself or an ancestor directory
|
||||
# WITHIN the vendored tree. No hardcoded component list — newly vendored
|
||||
# code with no license is flagged immediately.
|
||||
MISS=0
|
||||
has_license_dir() {
|
||||
ls "$1" 2>/dev/null | grep -qiE '^(LICENSE|LICENCE|COPYING|UNLICENSE|NOTICE)'
|
||||
}
|
||||
for root in vendored internal/cbm/vendored; do
|
||||
find "$root" -type f \
|
||||
\( -name '*.c' -o -name '*.h' -o -name '*.cpp' -o -name '*.hpp' \
|
||||
-o -name '*.S' -o -name '*.bin' -o -name '*.txt' \) \
|
||||
! -iname 'LICENSE*' ! -iname 'COPYING*' ! -iname 'NOTICE*' \
|
||||
-exec dirname {} \; | LC_ALL=C sort -u | while IFS= read -r d; do
|
||||
cur="$d"
|
||||
covered=1
|
||||
while :; do
|
||||
if has_license_dir "$cur"; then
|
||||
covered=0
|
||||
break
|
||||
fi
|
||||
[ "$cur" = "$root" ] && break
|
||||
cur="$(dirname "$cur")"
|
||||
done
|
||||
if [ $covered -ne 0 ]; then
|
||||
echo "BLOCKED: vendored code in $d has no license file in itself or any ancestor within $root/"
|
||||
fi
|
||||
done > /tmp/license-gate-structural.$$
|
||||
if [ -s /tmp/license-gate-structural.$$ ]; then
|
||||
cat /tmp/license-gate-structural.$$
|
||||
MISS=1
|
||||
fi
|
||||
rm -f /tmp/license-gate-structural.$$
|
||||
done
|
||||
if [ $MISS -ne 0 ]; then
|
||||
echo "=== LICENSE GATE FAILED (structural) ==="
|
||||
exit 1
|
||||
fi
|
||||
echo "OK: every vendored source directory is covered by a license file"
|
||||
|
||||
echo "=== License gate 2/2: ScanCode detection ==="
|
||||
if ! command -v scancode &>/dev/null; then
|
||||
echo "FAIL: scancode not installed (pipx install scancode-toolkit)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STAGE="$(mktemp -d)"
|
||||
trap 'rm -rf "$STAGE"' EXIT
|
||||
|
||||
# Inputs: all vendored license/notice texts + all first-party sources.
|
||||
find vendored internal/cbm/vendored -type f \
|
||||
\( -iname 'LICENSE*' -o -iname 'COPYING*' -o -iname 'NOTICE*' -o -iname 'UNLICENSE*' \) \
|
||||
> "$STAGE/files.txt"
|
||||
find src pkg scripts -type f \
|
||||
\( -name '*.c' -o -name '*.h' -o -name '*.sh' -o -name '*.js' \
|
||||
-o -name '*.py' -o -name '*.rb' -o -name '*.toml' -o -name '*.json' \) \
|
||||
>> "$STAGE/files.txt"
|
||||
|
||||
mkdir -p "$STAGE/tree"
|
||||
tar cf - -T "$STAGE/files.txt" | tar xf - -C "$STAGE/tree"
|
||||
|
||||
scancode --license --quiet --processes 2 --json-pp "$STAGE/scan.json" "$STAGE/tree" \
|
||||
> "$STAGE/scancode.log" 2>&1 || {
|
||||
echo "FAIL: scancode run failed:"
|
||||
tail -20 "$STAGE/scancode.log"
|
||||
exit 1
|
||||
}
|
||||
|
||||
python3 scripts/license-gate-check.py "$STAGE/scan.json" scripts/license-policy.json
|
||||
|
||||
echo "=== License gate 3/3: UI npm production tree ==="
|
||||
# The -ui binaries embed the compiled frontend bundle; its production
|
||||
# dependency tree must be allow-listed too. --ignore-scripts: no dependency
|
||||
# postinstall code runs inside the security job.
|
||||
if command -v npm &>/dev/null && [ -f graph-ui/package-lock.json ]; then
|
||||
if [ ! -d graph-ui/node_modules ]; then
|
||||
(cd graph-ui && npm ci --ignore-scripts --silent)
|
||||
fi
|
||||
python3 scripts/license-gate-check-npm.py graph-ui scripts/license-policy.json
|
||||
else
|
||||
echo "FAIL: npm or graph-ui/package-lock.json unavailable — UI tree unchecked"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== License gate passed ==="
|
||||
Reference in New Issue
Block a user