feat(security): rate-limit /api/flows/start, CSRF double-submit cookie, CSP headers

- Rate-limit /api/flows/start to 10 req/min per user (in-memory, matches login pattern)
- Add middleware.ts: validates x-csrf-token header against csrf_token cookie on all
  API POST requests; issues the cookie on GET if missing; sets CSP, X-Frame-Options,
  X-Content-Type-Options, and Referrer-Policy on all responses
- Add lib/csrf.ts: client-side apiFetch() wrapper that injects the CSRF header
- Update all client components (login, useFlowRun, docker, caddy, git, systemd)
  to use apiFetch() for POST requests
- Cookie config in login route already correct (NODE_ENV check, httpOnly, sameSite=strict)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scrum4Me Agent 2026-05-13 20:01:43 +02:00
parent 1e31e3b584
commit aa1fd41bec
11 changed files with 108 additions and 8 deletions

21
lib/csrf.ts Normal file
View file

@ -0,0 +1,21 @@
'use client'
function getCsrfToken(): string {
if (typeof document === 'undefined') return ''
return (
document.cookie
.split('; ')
.find((c) => c.startsWith('csrf_token='))
?.split('=')[1] ?? ''
)
}
/** Drop-in replacement for fetch() that automatically injects the CSRF token on POST requests. */
export function apiFetch(url: string, init: RequestInit = {}): Promise<Response> {
if ((init.method ?? 'GET').toUpperCase() !== 'POST') {
return fetch(url, init)
}
const headers = new Headers(init.headers)
headers.set('x-csrf-token', getCsrfToken())
return fetch(url, { ...init, headers })
}