4.1 KiB
4.1 KiB
Security Headers, Coverage Gates & Web Vitals Design
Date: 2026-03-18 Product: ResolutionFlow Branch:
feat/security-headers-coverage-performancePurpose: 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.pyafter CORS middleware (so CORS preflight responses aren't affected) - CSP directives configurable via
config.pyso 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=80to the pytest command in CI - One-line change — reporting already wired up with
pytest-cov
Frontend: Report-only (establish baseline)
- Install
@vitest/coverage-v8as 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
- Reporters:
- Add
test:coveragescript topackage.json - Update CI to run
npm run test:coverageinstead ofnpm 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,onTTFBfromweb-vitals - Each callback sends a PostHog event (
web_vitals) with properties:metric_name— LCP, INP, CLS, FCP, TTFBmetric_value— numeric valuemetric_rating— good / needs-improvement / poorpage_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.