Add API CSRF protection via X-Requested-With header (closes #112) #116

Closed
clawbot wants to merge 1 commits from fix/112-api-csrf-protection into main
Collaborator

Summary

Fixes #112 — API v1 CSRF vulnerability.

The API v1 endpoints use cookie-based session auth without CSRF protection. A malicious website could make authenticated requests (delete apps, trigger deploys, read secrets) via the user's session cookie.

Changes

  1. APICSRFProtection middleware — requires X-Requested-With header on state-changing requests (POST/PUT/DELETE/PATCH). Browsers cannot send custom headers cross-origin without CORS preflight, blocking CSRF.
  2. Session cookie SameSite=Strict — upgraded from Lax as defense-in-depth.
  3. CORS allowed headers — added X-Requested-With for legitimate cross-origin API clients.
  4. Tests — 4 test cases covering safe methods, blocked POST/DELETE, and allowed POST with header.

make check output

==> Checking formatting...
==> Running linter...
==> Running tests...
--- PASS (all tests pass)
==> Building...
==> All checks passed!

All checks pass with zero failures.

## Summary Fixes #112 — API v1 CSRF vulnerability. The API v1 endpoints use cookie-based session auth without CSRF protection. A malicious website could make authenticated requests (delete apps, trigger deploys, read secrets) via the user's session cookie. ## Changes 1. **`APICSRFProtection` middleware** — requires `X-Requested-With` header on state-changing requests (POST/PUT/DELETE/PATCH). Browsers cannot send custom headers cross-origin without CORS preflight, blocking CSRF. 2. **Session cookie `SameSite=Strict`** — upgraded from `Lax` as defense-in-depth. 3. **CORS allowed headers** — added `X-Requested-With` for legitimate cross-origin API clients. 4. **Tests** — 4 test cases covering safe methods, blocked POST/DELETE, and allowed POST with header. ## `make check` output ``` ==> Checking formatting... ==> Running linter... ==> Running tests... --- PASS (all tests pass) ==> Building... ==> All checks passed! ``` All checks pass with zero failures.
clawbot added 1 commit 2026-02-20 14:33:56 +01:00
Add API CSRF protection via X-Requested-With header (closes #112)
All checks were successful
Check / check (pull_request) Successful in 11m36s
efa8f51310
- Add APICSRFProtection middleware requiring X-Requested-With header on
  state-changing API requests (POST, PUT, DELETE, PATCH)
- Apply middleware to all /api/v1 routes
- Upgrade session cookie SameSite from Lax to Strict (defense-in-depth)
- Add X-Requested-With to CORS allowed headers
- Add tests for the new middleware

Browsers cannot send custom headers cross-origin without CORS preflight,
which effectively blocks CSRF attacks via cookie-based session auth.
Author
Collaborator

Self-Review

Approach: Requires X-Requested-With custom header on non-safe API methods. Browsers cannot send custom headers cross-origin without CORS preflight approval, which prevents CSRF. This is the same pattern used by frameworks like Django REST and Angular.

Reviewed items:

  • Middleware correctly distinguishes safe (GET/HEAD/OPTIONS) vs unsafe methods
  • Returns proper JSON 403 with descriptive error message
  • Applied at the /api/v1 route group level (covers all API endpoints including login)
  • CORS config updated to allow X-Requested-With for legitimate cross-origin clients
  • SameSite=Strict as defense-in-depth (was Lax)
  • 4 test cases with good coverage
  • make check passes with zero failures
  • No linter config changes

Note: The X-Requested-With header requirement applies to the login endpoint too (POST /api/v1/login). This is intentional — API clients must include the header. The web UI login form uses the HTML routes (/login), not the API.

## Self-Review **Approach:** Requires `X-Requested-With` custom header on non-safe API methods. Browsers cannot send custom headers cross-origin without CORS preflight approval, which prevents CSRF. This is the same pattern used by frameworks like Django REST and Angular. **Reviewed items:** - ✅ Middleware correctly distinguishes safe (GET/HEAD/OPTIONS) vs unsafe methods - ✅ Returns proper JSON 403 with descriptive error message - ✅ Applied at the `/api/v1` route group level (covers all API endpoints including login) - ✅ CORS config updated to allow `X-Requested-With` for legitimate cross-origin clients - ✅ `SameSite=Strict` as defense-in-depth (was `Lax`) - ✅ 4 test cases with good coverage - ✅ `make check` passes with zero failures - ✅ No linter config changes **Note:** The `X-Requested-With` header requirement applies to the login endpoint too (`POST /api/v1/login`). This is intentional — API clients must include the header. The web UI login form uses the HTML routes (`/login`), not the API.
clawbot closed this pull request 2026-02-20 14:34:39 +01:00
sneak was assigned by clawbot 2026-02-20 14:34:47 +01:00
All checks were successful
Check / check (pull_request) Successful in 11m36s
Required
Details

Pull request closed

Sign in to join this conversation.
No description provided.