tenseleyflow/shithub / a894fbb

Browse files

S35: lint scripts — secret-logs (blocking) + csrf (review-aid)

Authored by espadonne
SHA
a894fbba660786d1680c9c1f76207a2e017efa96
Parents
5be109c
Tree
a0e4b5b

2 changed files

StatusFile+-
A scripts/lint-csrf.sh 66 0
A scripts/lint-secret-logs.sh 74 0
scripts/lint-csrf.shadded
@@ -0,0 +1,66 @@
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)"
scripts/lint-secret-logs.shadded
@@ -0,0 +1,74 @@
1
+#!/usr/bin/env bash
2
+# SPDX-License-Identifier: AGPL-3.0-or-later
3
+#
4
+# Fail when source code emits log lines containing token-prefix
5
+# patterns that would leak credentials into operator logs. The
6
+# canonical bad shape:
7
+#
8
+#   logger.Info("got authorization header", "auth", r.Header.Get("Authorization"))
9
+#
10
+# Even when the value happens to be empty, the format string itself
11
+# is a smell — anyone who edits the line to format the value back in
12
+# will silently start logging the secret.
13
+#
14
+# Patterns we reject (case-insensitive substring match in *.go):
15
+#   - "shithub_pat_"     — PAT token prefix
16
+#   - "otpauth://"       — TOTP URI containing the secret
17
+#   - "Authorization:"   — header literal that suggests dumping headers
18
+#
19
+# Carve-outs (whitelisted paths) live in EXEMPTS below. The S08 PAT
20
+# redactor for git-clone URLs and the auth-flow tests are the legit
21
+# call sites; everything else is suspicious.
22
+#
23
+# Run as part of `make ci`.
24
+
25
+set -euo pipefail
26
+
27
+cd "$(git rev-parse --show-toplevel)"
28
+
29
+EXEMPTS=(
30
+  # S08 redactor explicitly handles the prefix to strip it from URLs.
31
+  "internal/auth/pat/"
32
+  # Log redactor IS the canonical handler for the secret patterns.
33
+  "internal/infra/log/log.go"
34
+  # TOTP package builds the otpauth:// URI for the QR provisioning
35
+  # code — never logs it, just constructs.
36
+  "internal/auth/totp/totp.go"
37
+  # PAT middleware parses (not logs) the Authorization header.
38
+  "internal/web/middleware/pat.go"
39
+  # Git-HTTP auth handler mentions the prefix in doc comments.
40
+  "internal/web/handlers/githttp/auth.go"
41
+  # Tests legitimately mention these strings in fixtures.
42
+  "_test.go"
43
+  # The lint script itself documents the patterns.
44
+  "scripts/lint-secret-logs.sh"
45
+)
46
+
47
+RE='shithub_pat_|otpauth://|Authorization:'
48
+
49
+# grep -EIn: extended regex, ignore binary, show line numbers.
50
+# Search only shithub-owned trees — .refs/ vendored repos are docs.
51
+matches=$(grep -RIEn --include='*.go' "$RE" cmd internal scripts 2>/dev/null || true)
52
+
53
+filtered=""
54
+while IFS= read -r line; do
55
+  [ -z "$line" ] && continue
56
+  skip=false
57
+  for pat in "${EXEMPTS[@]}"; do
58
+    if [[ "$line" == *"$pat"* ]]; then
59
+      skip=true
60
+      break
61
+    fi
62
+  done
63
+  $skip || filtered="${filtered}${line}"$'\n'
64
+done <<< "$matches"
65
+
66
+if [ -n "${filtered// /}" ]; then
67
+  echo "lint-secret-logs: token-prefix patterns found in non-exempt source:" >&2
68
+  echo "$filtered" >&2
69
+  echo >&2
70
+  echo "If this is a legitimate use, add the file to EXEMPTS in scripts/lint-secret-logs.sh." >&2
71
+  exit 1
72
+fi
73
+
74
+echo "lint-secret-logs: ok"