# Security Headers, Coverage Gates & Web Vitals Design

> **Date:** 2026-03-18
> **Product:** ResolutionFlow
> **Branch:** `feat/security-headers-coverage-performance`
> **Purpose:** Add HTTP security headers, enforce test coverage gates, and instrument Core Web Vitals reporting

---

## Decisions

| Decision | Choice | Rationale |
|----------|--------|-----------|
| Security headers priority | First | Highest trust signal for MSP buyers, real protection |
| CSP rollout strategy | Report-only first, then enforce | Avoids breaking third-party integrations (PostHog, Sentry, Google Fonts, React Flow) |
| Backend coverage gate | 80% fail threshold | Already near this level, prevents drift |
| Frontend coverage gate | Report-only (no gate yet) | Starting from zero — establish baseline first |
| Web Vitals destination | PostHog | 100% of sessions captured (vs Sentry's 20% sample), correlate with product analytics |

---

## 1. Security Headers Middleware

### New file: `backend/app/core/security_headers.py`

Starlette middleware that adds security headers to every response.

### Headers

| Header | Value | Purpose |
|--------|-------|---------|
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` | Force HTTPS for 1 year |
| `X-Content-Type-Options` | `nosniff` | Prevent MIME sniffing |
| `X-Frame-Options` | `DENY` | Block iframe embedding |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Limit referrer leakage |
| `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` | Disable unused browser APIs |
| `Content-Security-Policy-Report-Only` | *(see below)* | CSP in report-only mode |

### CSP Directive (report-only)

```
default-src 'self';
script-src 'self' https://us.i.posthog.com https://*.sentry.io;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: blob:;
connect-src 'self' https://us.posthog.com https://us.i.posthog.com https://*.sentry.io https://api.resolutionflow.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
```

### Wiring

- Add middleware in `main.py` after CORS middleware (so CORS preflight responses aren't affected)
- CSP directives configurable via `config.py` so promotion to enforcing mode is a config change, not a code change
- HSTS only sent when `DEBUG=false` (avoid locking localhost into HTTPS)

### Tests

Integration test that hits an endpoint and asserts all expected headers are present with correct values.

---

## 2. Coverage Gates

### Backend: Enforce at 80%

- Add `--cov-fail-under=80` to the pytest command in CI
- One-line change — reporting already wired up with `pytest-cov`

### Frontend: Report-only (establish baseline)

- Install `@vitest/coverage-v8` as dev dependency
- Add coverage config to `vite.config.ts`:
  - Reporters: `text` + `json-summary` + `html`
  - Include: `src/**/*.{ts,tsx}`
  - Exclude: `src/test/`, `src/types/`, `**/*.d.ts`
- Add `test:coverage` script to `package.json`
- Update CI to run `npm run test:coverage` instead of `npm test`
- Display summary in CI output — no failure threshold yet
- Add `coverage/` to `.gitignore`

---

## 3. Web Vitals → PostHog

### Install

`web-vitals` npm package.

### New file: `frontend/src/lib/webVitals.ts`

- Import `onLCP`, `onINP`, `onCLS`, `onFCP`, `onTTFB` from `web-vitals`
- Each callback sends a PostHog event (`web_vitals`) with properties:
  - `metric_name` — LCP, INP, CLS, FCP, TTFB
  - `metric_value` — numeric value
  - `metric_rating` — good / needs-improvement / poor
  - `page_path` — current route
- Single `initWebVitals()` function that registers all observers

### Wiring

Call `initWebVitals()` in `main.tsx` after PostHog initialization.

---

## Scope Summary

| Area | Scope | Files |
|------|-------|-------|
| Security headers | New middleware + config + test | 3-4 backend files |
| Coverage gates | CI config + vitest coverage setup | CI workflow + 3 frontend config files |
| Web Vitals | New lib + dependency + main.tsx wiring | 2-3 frontend files |

Small, contained changes across all three. No architectural changes or new database models.
