|
| 1 | +#!/usr/bin/env bash |
| 2 | +# WAF Smoke Tests — validates Cloudflare WAF rules for gh.gordoncode.dev |
| 3 | +# |
| 4 | +# Usage: pnpm test:waf |
| 5 | +# |
| 6 | +# Rules validated: |
| 7 | +# 1. Path Allowlist — blocks all paths except known SPA routes, /assets/*, /api/* |
| 8 | +# 2. Scanner User-Agents — challenges empty/malicious User-Agent strings |
| 9 | +# Rate limit rule exists but is not tested here (triggers a 10-minute IP block). |
| 10 | + |
| 11 | +set -euo pipefail |
| 12 | + |
| 13 | +BASE="https://gh.gordoncode.dev" |
| 14 | +PASS=0 |
| 15 | +FAIL=0 |
| 16 | + |
| 17 | +assert_status() { |
| 18 | + local expected="$1" actual="$2" label="$3" |
| 19 | + if [[ "$actual" == "$expected" ]]; then |
| 20 | + echo " PASS [${actual}] ${label}" |
| 21 | + PASS=$((PASS + 1)) |
| 22 | + else |
| 23 | + echo " FAIL [${actual}] ${label} (expected ${expected})" |
| 24 | + FAIL=$((FAIL + 1)) |
| 25 | + fi |
| 26 | +} |
| 27 | + |
| 28 | +fetch() { |
| 29 | + curl -s -o /dev/null -w "%{http_code}" "$@" |
| 30 | +} |
| 31 | + |
| 32 | +# ============================================================ |
| 33 | +# Rule 1: Path Allowlist |
| 34 | +# ============================================================ |
| 35 | +echo "=== Rule 1: Path Allowlist ===" |
| 36 | +echo "--- Allowed paths (should pass) ---" |
| 37 | + |
| 38 | +for path in "/" "/login" "/oauth/callback" "/onboarding" "/dashboard" "/settings" "/privacy"; do |
| 39 | + status=$(fetch "${BASE}${path}") |
| 40 | + assert_status "200" "$status" "GET ${path}" |
| 41 | +done |
| 42 | + |
| 43 | +status=$(fetch "${BASE}/index.html") |
| 44 | +assert_status "307" "$status" "GET /index.html (html_handling redirect)" |
| 45 | + |
| 46 | +status=$(fetch "${BASE}/assets/nonexistent.js") |
| 47 | +assert_status "200" "$status" "GET /assets/nonexistent.js" |
| 48 | + |
| 49 | +status=$(fetch "${BASE}/api/health") |
| 50 | +assert_status "200" "$status" "GET /api/health" |
| 51 | + |
| 52 | +status=$(fetch -X POST "${BASE}/api/oauth/token") |
| 53 | +assert_status "400" "$status" "POST /api/oauth/token (no body)" |
| 54 | + |
| 55 | +status=$(fetch "${BASE}/api/nonexistent") |
| 56 | +assert_status "404" "$status" "GET /api/nonexistent" |
| 57 | + |
| 58 | +echo "--- Blocked paths (should be 403) ---" |
| 59 | + |
| 60 | +for path in "/wp-admin" "/wp-login.php" "/.env" "/.env.production" \ |
| 61 | + "/.git/config" "/.git/HEAD" "/xmlrpc.php" \ |
| 62 | + "/phpmyadmin/" "/phpMyAdmin/" "/.htaccess" "/.htpasswd" \ |
| 63 | + "/cgi-bin/" "/admin/" "/wp-content/debug.log" \ |
| 64 | + "/config.php" "/backup.zip" "/actuator/health" \ |
| 65 | + "/manager/html" "/wp-config.php" "/eval-stdin.php" \ |
| 66 | + "/.aws/credentials" "/.ssh/id_rsa" "/robots.txt" \ |
| 67 | + "/sitemap.xml" "/favicon.ico" "/random/garbage/path"; do |
| 68 | + status=$(fetch "${BASE}${path}") |
| 69 | + assert_status "403" "$status" "GET ${path}" |
| 70 | +done |
| 71 | + |
| 72 | +# ============================================================ |
| 73 | +# Rule 2: Scanner User-Agents |
| 74 | +# ============================================================ |
| 75 | +echo "" |
| 76 | +echo "=== Rule 2: Scanner User-Agents ===" |
| 77 | +echo "--- Normal UAs (should pass) ---" |
| 78 | + |
| 79 | +status=$(fetch -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" "${BASE}/") |
| 80 | +assert_status "200" "$status" "Normal browser UA" |
| 81 | + |
| 82 | +status=$(fetch "${BASE}/") |
| 83 | +assert_status "200" "$status" "Default curl UA" |
| 84 | + |
| 85 | +echo "--- Malicious UAs (should be 403 — managed challenge, no JS) ---" |
| 86 | + |
| 87 | +status=$(fetch -H "User-Agent:" "${BASE}/") |
| 88 | +assert_status "403" "$status" "Empty User-Agent" |
| 89 | + |
| 90 | +for ua in "sqlmap/1.7" "Nikto/2.1.6" "Nmap Scripting Engine" "masscan/1.3" "Mozilla/5.0 zgrab/0.x"; do |
| 91 | + status=$(fetch -H "User-Agent: ${ua}" "${BASE}/") |
| 92 | + assert_status "403" "$status" "UA: ${ua}" |
| 93 | +done |
| 94 | + |
| 95 | +# ============================================================ |
| 96 | +# Summary |
| 97 | +# ============================================================ |
| 98 | +echo "" |
| 99 | +TOTAL=$((PASS + FAIL)) |
| 100 | +echo "=== Results: ${PASS}/${TOTAL} passed, ${FAIL} failed ===" |
| 101 | +if [[ $FAIL -gt 0 ]]; then |
| 102 | + exit 1 |
| 103 | +fi |
0 commit comments