docs: add design for security headers, coverage gates, and web vitals
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
116
docs/plans/2026-03-18-security-coverage-performance-design.md
Normal file
116
docs/plans/2026-03-18-security-coverage-performance-design.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user