diff --git a/caps.sh b/caps.sh index e755d31..88b0038 100755 --- a/caps.sh +++ b/caps.sh @@ -4,7 +4,7 @@ # # Usage: # ./caps.sh generate # regenerate capability_baseline.txt -# ./caps.sh check # check that no new capabilities have been added +# ./caps.sh check # check that capabilities haven't grown # # Requires: go, capslock (go install github.com/google/capslock/cmd/capslock@latest) @@ -15,20 +15,13 @@ CAPSLOCK="${CAPSLOCK:-capslock}" # Capabilities that must never appear in any package. FORBIDDEN_CAPS=( - CAPABILITY_UNSAFE_POINTER CAPABILITY_NETWORK CAPABILITY_CGO CAPABILITY_EXEC ) -# Capabilities to exclude from capslock analysis. UNSAFE_POINTER is excluded -# because go-toml has no direct unsafe imports — capslock reports it only due -# to stdlib internals (e.g. reflect -> unsafe) which is outside our control. -CAPSLOCK_IGNORE="-CAPABILITY_UNSAFE_POINTER" - capslock_to_baseline() { "$CAPSLOCK" -packages=./... -output=package -granularity=package \ - -capabilities="$CAPSLOCK_IGNORE" \ | jq -r 'to_entries | sort_by(.key) | .[] | .key + ": " + (.value | sort | join(", "))' } @@ -50,6 +43,18 @@ check() { failed=0 + # Verify go-toml source never directly imports "unsafe". + # Capslock may report CAPABILITY_UNSAFE_POINTER due to stdlib internals + # (e.g. reflect -> unsafe), which is a false positive. Instead of relying + # on capslock for this, we check the source directly. + unsafe_imports=$(find . -name '*.go' -not -name '*_test.go' -not -path './vendor/*' \ + -exec grep -l '"unsafe"' {} +) || true + if [ -n "$unsafe_imports" ]; then + echo "FORBIDDEN: direct unsafe import found in:" + echo "$unsafe_imports" + failed=1 + fi + # Check for forbidden capabilities in current output. for cap in "${FORBIDDEN_CAPS[@]}"; do if grep -q "$cap" "$current"; then @@ -60,8 +65,13 @@ check() { 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) + # Exclude CAPABILITY_UNSAFE_POINTER from comparison — capslock reports it + # as a false positive from stdlib internals (reflect, sync, etc. use + # unsafe.Pointer internally). Go 1.26+ triggers this due to changes in + # how capslock traces through unclassified reflect functions. The direct + # source check above is the real guard against unsafe usage. + baseline_caps=$(grep -oE 'CAPABILITY_[A-Z_]+' "$BASELINE" | grep -v CAPABILITY_UNSAFE_POINTER | sort -u) + current_caps=$(grep -oE 'CAPABILITY_[A-Z_]+' "$current" | grep -v CAPABILITY_UNSAFE_POINTER | sort -u) # Check for new capability names not in the baseline. new_caps=$(comm -13 <(echo "$baseline_caps") <(echo "$current_caps")) @@ -80,7 +90,7 @@ check() { continue fi # Check each capability in current for this package - for cap in $(echo "$caps" | tr ', ' '\n' | grep -v '^$'); do + for cap in $(echo "$caps" | tr ', ' '\n' | grep -v '^$' | grep -v CAPABILITY_UNSAFE_POINTER); do if ! echo "$baseline_pkg_caps" | grep -q "$cap"; then echo "NEW capability for $pkg: $cap" failed=1