Deploy to Staging / Build Images (push) Successful in 5m32s
Deploy to Staging / Deploy to Staging (push) Successful in 44s
Deploy to Staging / Verify Staging (push) Successful in 5s
Deploy to Staging / Notify Staging Ready (push) Successful in 4s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Removes the old planner/decision-critic/role-agents/domain-agents system (step-injector scripts, stale scopes, sprint-era workflow) and replaces it with 16 ground-truth-verified skills under .claude/skills/: change control, debugging playbook, failure archaeology, architecture contract, domain reference, OCR/Gemini pipeline, config and secrets, build and env, run and operate, diagnostics (with tested scripts), validation and QA, docs and writing, launch readiness, deploy-safety campaign, proof and analysis toolkit, and research frontier. RULE 0/1/2, the temporal-contamination rule, and the decision stress-test protocol are carried forward into the new skills; the retired content remains in git history. Co-Authored-By: Claude Fable 5 <[email protected]>
91 lines
3.8 KiB
Bash
Executable File
91 lines
3.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# check-route-auth.sh - List backend routes with NO auth guard in their
|
|
# registration block, so unintentionally-public endpoints are visible.
|
|
#
|
|
# Why: there is no global auth hook. Every route must opt in via
|
|
# preHandler: [fastify.authenticate] / requireAdmin / requireTier
|
|
# (backend/src/core/plugins/*.plugin.ts). A forgotten preHandler ships a
|
|
# public endpoint silently.
|
|
#
|
|
# Usage: ./check-route-auth.sh (read-only; run from anywhere in repo)
|
|
# Exit: 0 = only known-public routes unguarded, 1 = unexpected unguarded route
|
|
set -u
|
|
|
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
[ -z "$ROOT" ] && { echo "ERROR: not inside a git repo" >&2; exit 2; }
|
|
cd "$ROOT" || exit 2
|
|
|
|
# CANONICAL intentional-public-route list (verified 2026-07-09). This comment
|
|
# is the ONE home for it; mvp-architecture-contract invariant 2.1 and
|
|
# mvp-debugging-playbook section 7 point here instead of keeping their own lists.
|
|
#
|
|
# Within this script's scan scope (backend/src/features/*/api/*.routes.ts),
|
|
# allowlisted via KNOWN_PUBLIC below:
|
|
# /webhooks/stripe, /webhooks/resend/inbound - provider signature auth
|
|
# /auth/signup, /auth/resend-verification-public - pre-auth flows
|
|
# (comments in auth.routes.ts: "public, no JWT required")
|
|
#
|
|
# OUTSIDE this script's scan scope (registered in core code, also intentionally
|
|
# public - the script can neither flag nor clear them):
|
|
# /health, /api/health - backend/src/app.ts health endpoints
|
|
# /auth/verify - backend/src/app.ts (Traefik forward-auth)
|
|
# GET /api/config/feature-tiers - backend/src/core/config/config.routes.ts
|
|
# (no preHandler; config is not sensitive)
|
|
KNOWN_PUBLIC='/webhooks/stripe /webhooks/resend/inbound /auth/signup /auth/resend-verification-public'
|
|
|
|
OUT=$(awk '
|
|
function flush() {
|
|
if (inblock && !guarded) printf "%s:%d %s %s\n", f, startline, method, path
|
|
inblock = 0
|
|
}
|
|
FNR == 1 { flush() }
|
|
/fastify\.(get|post|put|patch|delete)[<(]/ {
|
|
flush()
|
|
inblock = 1; guarded = 0; startline = FNR; path = "?"; f = FILENAME
|
|
match($0, /fastify\.(get|post|put|patch|delete)/)
|
|
method = toupper(substr($0, RSTART + 8, RLENGTH - 8))
|
|
}
|
|
inblock && path == "?" {
|
|
if (match($0, "\047/[^\047]*\047")) path = substr($0, RSTART + 1, RLENGTH - 2)
|
|
}
|
|
# requireAuth is the common local alias: const requireAuth = fastify.authenticate.bind(fastify)
|
|
inblock && /[ .\[](authenticate|requireAuth|requireAdmin|requireTier)[ ,\]\}\)\(]/ { guarded = 1 }
|
|
END { flush() }
|
|
' backend/src/features/*/api/*.routes.ts)
|
|
|
|
TOTAL=$(grep -c 'fastify\.\(get\|post\|put\|patch\|delete\)[<(]' \
|
|
backend/src/features/*/api/*.routes.ts | awk -F: '{s+=$2} END {print s}')
|
|
|
|
echo "Route registrations scanned: $TOTAL"
|
|
echo
|
|
|
|
FAIL=0
|
|
if [ -z "$OUT" ]; then
|
|
echo "PASS: every route block contains an auth guard."
|
|
exit 0
|
|
fi
|
|
|
|
echo "Routes with NO authenticate/requireAdmin/requireTier in their block:"
|
|
echo "$OUT" | while read -r line; do
|
|
p=$(echo "$line" | awk '{print $3}')
|
|
case " $KNOWN_PUBLIC " in
|
|
*" $p "*) echo " OK (known-public, allowlisted): $line" ;;
|
|
*) echo " UNGUARDED: $line" ;;
|
|
esac
|
|
done
|
|
# Re-count outside the pipe subshell (bash 3.2: while-in-pipe loses vars)
|
|
FAIL=$(echo "$OUT" | awk -v kp=" $KNOWN_PUBLIC " '{ if (index(kp, " " $3 " ") == 0) n++ } END {print n+0}')
|
|
|
|
echo
|
|
if [ "$FAIL" -eq 0 ]; then
|
|
echo "PASS: all unguarded routes are on the known-public allowlist."
|
|
exit 0
|
|
else
|
|
echo "FAIL: $FAIL route(s) unguarded and not on the allowlist."
|
|
echo "Either add a preHandler guard or, if intentionally public, add the"
|
|
echo "path to KNOWN_PUBLIC in this script with a justification comment."
|
|
echo "Limitation: block-scope heuristic; a guard mentioned in a comment"
|
|
echo "inside the block can mask a real gap - verify hits by reading code."
|
|
exit 1
|
|
fi
|