Bash · 2836 bytes Raw Blame History
1 #!/usr/bin/env bash
2 # SPDX-License-Identifier: AGPL-3.0-or-later
3 #
4 # Fail when authorization decisions leak outside internal/auth/policy.
5 # After S15, every "is this actor allowed to do X?" check must flow
6 # through policy.Can(); handler/git/cmd code must not branch on
7 # ownership, visibility, or collaborator state inline.
8 #
9 # The check looks for these smells in handler/git/hook code:
10 # - `OwnerUserID == ` or `== row.OwnerUserID` style equality compares
11 # - `Visibility == reposdb.Repo` equality compares
12 # - `IsArchived` used as a control-flow predicate (not a parameter)
13 #
14 # Pure data-plumbing references (constructing sqlc.Params, returning
15 # the column from a query, logging) are allowed — those don't decide
16 # anything.
17 #
18 # Allowed locations (carved out below):
19 # internal/auth/policy/...
20 # internal/repos/... (repo-create + repo orchestration)
21 # internal/web/handlers/repo/ (constructs the policy actor; lookup
22 # wrapper is policy-aware)
23 # *_test.go everywhere (tests legitimately seed state)
24 #
25 # The script exits 0 when no violations are found, 1 otherwise. Run as
26 # part of `make ci`.
27
28 set -euo pipefail
29
30 cd "$(git rev-parse --show-toplevel)"
31
32 # Patterns that smell like an inline auth decision. The audit found
33 # negation forms (`!=`) slipping past the original equality-only set,
34 # so both directions are covered. Same for the visibility shape — both
35 # `== "private"` literal and the typed-enum compare.
36 PATTERNS=(
37 '\.OwnerUserID == '
38 '\.OwnerUserID != '
39 '\.OwnerUserID\.Int64 == '
40 '\.OwnerUserID\.Int64 != '
41 '== .*\.OwnerUserID'
42 '!= .*\.OwnerUserID'
43 '\.Visibility == .*RepoVisibility'
44 '\.Visibility != .*RepoVisibility'
45 '\.Visibility == "(public|private)"'
46 '\.Visibility != "(public|private)"'
47 'if .*\.IsArchived '
48 'if !.*\.IsArchived '
49 )
50
51 # Files we're guarding — anywhere a request handler or hook lives.
52 INCLUDE=(
53 ':!internal/auth/policy/*'
54 ':!internal/repos/*'
55 ':!internal/web/handlers/repo/*'
56 ':!**/*_test.go'
57 )
58
59 violations=0
60 for pat in "${PATTERNS[@]}"; do
61 matches=$(git grep -nE "$pat" -- \
62 'internal/web/handlers' 'internal/git' 'cmd/shithubd' \
63 "${INCLUDE[@]}" 2>/dev/null || true)
64 if [[ -n "$matches" ]]; then
65 echo "policy boundary violation — pattern: $pat"
66 echo "$matches" | sed 's/^/ /'
67 echo
68 violations=1
69 fi
70 done
71
72 if [[ "$violations" -ne 0 ]]; then
73 echo "------------------------------------------------------------"
74 echo "Authorization checks must flow through internal/auth/policy."
75 echo "If your case is legitimate data plumbing (e.g. a sqlc.Params"
76 echo "field), refactor to read the value from a struct field rather"
77 echo "than compare inline."
78 echo "------------------------------------------------------------"
79 exit 1
80 fi
81 echo "policy boundary: clean"