diff --git a/.github/workflows/capabilities.yml b/.github/workflows/capabilities.yml index c67c91e..19ad6f7 100644 --- a/.github/workflows/capabilities.yml +++ b/.github/workflows/capabilities.yml @@ -18,8 +18,8 @@ jobs: - name: Setup go uses: actions/setup-go@v6 with: - go-version: "1.24" + go-version: "1.26" - name: Install capslock - run: go install github.com/google/capslock/cmd/capslock@v0.3.1 + run: go install github.com/google/capslock/cmd/capslock@latest - name: Check for new capabilities run: ./caps.sh check diff --git a/caps.sh b/caps.sh index 88d31f4..464f3bd 100755 --- a/caps.sh +++ b/caps.sh @@ -4,7 +4,7 @@ # # Usage: # ./caps.sh generate # regenerate capability_baseline.txt -# ./caps.sh check # check that capabilities haven't grown +# ./caps.sh check # check that no new capabilities have been added # # Requires: go, capslock (go install github.com/google/capslock/cmd/capslock@latest) @@ -13,10 +13,21 @@ set -euo pipefail BASELINE="capability_baseline.txt" CAPSLOCK="${CAPSLOCK:-capslock}" -generate() { +# Capabilities that must never appear in any package. +FORBIDDEN_CAPS=( + CAPABILITY_UNSAFE_POINTER + CAPABILITY_NETWORK + CAPABILITY_CGO + CAPABILITY_EXEC +) + +capslock_to_baseline() { "$CAPSLOCK" -packages=./... -output=package -granularity=package \ - | jq -r 'to_entries | sort_by(.key) | .[] | .key + ": " + (.value | sort | join(", "))' \ - > "$BASELINE" + | jq -r 'to_entries | sort_by(.key) | .[] | .key + ": " + (.value | sort | join(", "))' +} + +generate() { + capslock_to_baseline > "$BASELINE" echo "Wrote $BASELINE" } @@ -29,18 +40,56 @@ check() { current=$(mktemp) trap 'rm -f "$current"' EXIT - "$CAPSLOCK" -packages=./... -output=package -granularity=package \ - | jq -r 'to_entries | sort_by(.key) | .[] | .key + ": " + (.value | sort | join(", "))' \ - > "$current" + capslock_to_baseline > "$current" - if diff -u "$BASELINE" "$current"; then - echo "OK: capabilities unchanged." - else + failed=0 + + # Check for forbidden capabilities in current output. + for cap in "${FORBIDDEN_CAPS[@]}"; do + if grep -q "$cap" "$current"; then + echo "FORBIDDEN capability found: $cap" + grep "$cap" "$current" + failed=1 + fi + done + + # Extract all unique capability names from baseline and current. + baseline_caps=$(grep -oE 'CAPABILITY_[A-Z_]+' "$BASELINE" | sort -u) + current_caps=$(grep -oE 'CAPABILITY_[A-Z_]+' "$current" | sort -u) + + # Check for new capability names not in the baseline. + new_caps=$(comm -13 <(echo "$baseline_caps") <(echo "$current_caps")) + if [ -n "$new_caps" ]; then + echo "NEW capabilities detected (not in baseline):" + echo "$new_caps" + failed=1 + fi + + # Check for new per-package capabilities (a package gained a capability it didn't have before). + while IFS=': ' read -r pkg caps; do + baseline_pkg_caps=$(grep "^${pkg}:" "$BASELINE" 2>/dev/null | sed 's/^[^:]*: //' || true) + if [ -z "$baseline_pkg_caps" ]; then + echo "NEW package with capabilities: $pkg: $caps" + failed=1 + continue + fi + # Check each capability in current for this package + for cap in $(echo "$caps" | tr ', ' '\n' | grep -v '^$'); do + if ! echo "$baseline_pkg_caps" | grep -q "$cap"; then + echo "NEW capability for $pkg: $cap" + failed=1 + fi + done + done < "$current" + + if [ "$failed" -eq 1 ]; then echo "" - echo "FAILED: capabilities have changed." + echo "FAILED: capabilities have grown." echo "If this is intentional, run '$0 generate' and commit the updated $BASELINE." exit 1 fi + + echo "OK: no new capabilities detected." } case "${1:-}" in