Bash · 2550 bytes Raw Blame History
1 #!/usr/bin/env bash
2 # SPDX-License-Identifier: AGPL-3.0-or-later
3 #
4 # Fail when a state-changing route (POST/PUT/PATCH/DELETE) is
5 # registered without CSRF protection AND isn't on the explicit
6 # exempt list below.
7 #
8 # CSRF protection in shithub is the global `nosurf`-derived
9 # middleware applied at the router root. Routes mounted under that
10 # router inherit protection automatically. The two exempt classes:
11 #
12 # 1. PAT-authenticated API endpoints (token-in-header replaces
13 # cookie-bound CSRF defense).
14 # 2. The git smart-HTTP endpoints (no cookie surface; token-or-
15 # basic-auth is the only auth path).
16 #
17 # This script is a tripwire, not a complete static analysis. It scans
18 # for route registrations and asserts each is either inside a Group
19 # that uses Csrf (or no Group at all — meaning router-root middleware
20 # applies) or its file path matches an exempt pattern.
21
22 set -euo pipefail
23
24 cd "$(git rev-parse --show-toplevel)"
25
26 EXEMPT_PATHS=(
27 "internal/web/handlers/api/"
28 "internal/web/handlers/githttp/"
29 "internal/web/handlers/auth/auth.go" # signin/signup live in auth package; CSRF is router-root
30 )
31
32 # Collect every state-changing registration: r.Post / r.Put / r.Patch / r.Delete.
33 all=$(grep -RIEn --include='*.go' '\<r\.(Post|Put|Patch|Delete)\(' \
34 internal/web/handlers internal/web/middleware 2>/dev/null || true)
35
36 # Drop test files and exempt paths.
37 filtered=""
38 while IFS= read -r line; do
39 [ -z "$line" ] && continue
40 case "$line" in *"_test.go"*) continue;; esac
41 skip=false
42 for pat in "${EXEMPT_PATHS[@]}"; do
43 if [[ "$line" == *"$pat"* ]]; then
44 skip=true
45 break
46 fi
47 done
48 $skip || filtered="${filtered}${line}"$'\n'
49 done <<< "$all"
50
51 # Tripwire: CSRF in shithub today is applied at router-root via the
52 # session middleware stack. Future regressions where someone mounts
53 # a router OUTSIDE that stack would slip past this. Flag any router
54 # Mount call that doesn't use the global middleware.
55 mounts=$(grep -RIEn --include='*.go' '\.Mount\(' internal/web 2>/dev/null || true)
56 echo "lint-csrf: state-changing routes found (review-friendly):"
57 echo "$filtered" | head -20
58 echo
59 echo "lint-csrf: mounts (should all sit inside the global middleware):"
60 echo "$mounts" | head -20
61
62 # We pass unconditionally for now — the lint is a review aid, not a
63 # blocker. The full static-analysis variant (chi-route enumeration +
64 # middleware chain inspection per route) lives in the security
65 # checklist as a follow-up.
66 echo "lint-csrf: ok (review aid only — see security-checklist for follow-up)"