Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ PAT instead), and Turnstile is only used by the planned Jira integration.

**Verify configuration:** Run `pnpm validate:deploy` locally to check that all required
Cloudflare Worker secrets are set. In CI, the deploy workflow runs
`pnpm validate:deploy --ci` automatically before building.
`pnpm validate:deploy` automatically before building.

### Optional (both deployment paths)

Expand Down Expand Up @@ -230,7 +230,7 @@ Configure these rules in the Cloudflare dashboard under **Security → WAF**.
**Where:** Security → WAF → Custom Rules
**Expression:**
```
(http.request.uri.path starts_with "/api/") and
(starts_with(http.request.uri.path, "/api/")) and
not (any(http.request.headers["origin"][*] in {"https://YOUR-DOMAIN"})) and
not (http.request.uri.path eq "/api/csp-report") and
not (http.request.uri.path eq "/api/error-reporting")
Expand All @@ -251,7 +251,7 @@ not (http.request.uri.path eq "/api/error-reporting")
**Where:** Security → WAF → Rate Limiting Rules
**Matching expression:**
```
(http.request.uri.path starts_with "/api/") and
(starts_with(http.request.uri.path, "/api/")) and
(http.request.method ne "OPTIONS")
```
**Rate:** 60 requests per 10 seconds per IP
Expand Down
10 changes: 7 additions & 3 deletions scripts/waf-smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Rules validated:
# 1. Path Allowlist — blocks all paths except known SPA routes, /assets/*, /api/*
# 2. Scanner User-Agents — challenges empty/malicious User-Agent strings
# 3. Origin Gate — blocks /api/* requests without valid Origin header
# Rate limit rule exists but is not tested here (triggers a 10-minute IP block).

set -euo pipefail
Expand Down Expand Up @@ -58,9 +59,12 @@ TESTS=(
"200|GET /privacy|${BASE}/privacy"
"307|GET /index.html (html_handling redirect)|${BASE}/index.html"
"200|GET /assets/nonexistent.js|${BASE}/assets/nonexistent.js"
"200|GET /api/health|${BASE}/api/health"
"400|POST /api/oauth/token (no body)|-X|POST|${BASE}/api/oauth/token"
"404|GET /api/nonexistent|${BASE}/api/nonexistent"
"200|GET /api/health (with Origin)|-H|Origin: ${BASE}|${BASE}/api/health"
"400|POST /api/oauth/token (no body)|-X|POST|-H|Origin: ${BASE}|${BASE}/api/oauth/token"
"404|GET /api/nonexistent|-H|Origin: ${BASE}|${BASE}/api/nonexistent"
# Rule 3: Origin gate — API requests without valid Origin are blocked at WAF
"403|GET /api/health (no Origin)|${BASE}/api/health"
"403|POST /api/oauth/token (wrong Origin)|-X|POST|-H|Origin: https://evil.example.com|${BASE}/api/oauth/token"
# Rule 1: Path Allowlist — blocked paths
"403|GET /wp-admin|${BASE}/wp-admin"
"403|GET /wp-login.php|${BASE}/wp-login.php"
Expand Down