Files
resolutionflow/docs/plans/2026-03-18-security-coverage-performance-design.md
2026-03-18 02:06:10 +00:00

4.1 KiB

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.