01

The security target

Customer scans flagged missing response headers as critical. Clearing those findings is the floor, not the goal — the goal is a posture where the next finding is also a one-line code change. This is the header set the rebuild ships on day one:

# target response headers, sdata.us content-security-policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'self'; form-action 'self' https://*.hsforms.com; frame-ancestors 'self'; upgrade-insecure-requests strict-transport-security: max-age=63072000; includeSubDomains; preload x-content-type-options: nosniff x-frame-options: SAMEORIGIN referrer-policy: strict-origin-when-cross-origin permissions-policy: camera=(), microphone=(), geolocation=(), payment=() cross-origin-opener-policy: same-origin

Plus subresource integrity hashes on every remaining third-party script, and origin locking so the edge cannot be bypassed.

Why the current site cannot reach this

The site's custom code is inline today — 13 inline <script> blocks and 19 inline <style> blocks on the homepage alone — and the current platform emits no per-request nonces or hashes. That combination forces 'unsafe-inline' into script-src, and the form captcha additionally requires 'unsafe-eval'. A policy carrying both is a resource allowlist: it stops beacons to unapproved hosts, but it does not stop script injection. It is not XSS protection, and no plan upgrade changes that.

Moving to a build step is what removes the constraint. Every script becomes either a build-emitted file with an integrity hash or an inline block carrying a per-request nonce, so script-src can drop 'unsafe-inline' entirely. That is the single technical reason this rebuild is worth doing.

Already in hand

Every third-party host the site touches is already enumerated and verified — a tested policy covering the CDN, jQuery, webfonts, Swiper, the CMS loader, nine HubSpot domains, the form captcha, the events-page geocoder and the video lightbox, confirmed report-only across ten routes with zero violations. The new policy starts from a known-good allowlist rather than a discovery exercise, which is why Phase 05 is one week and not three.

This page is the demonstration

This document is served from the recommended stack — static output on Vercel, no JavaScript, no inline styles — with its headers set in a committed file. Run the command yourself against this URL:

$ curl -sSI https://sdata-stack-decision-prehealth.vercel.app/ HTTP/2 200 content-security-policy: default-src 'none'; script-src 'none'; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none' strict-transport-security: max-age=63072000; includeSubDomains; preload x-content-type-options: nosniff x-frame-options: DENY referrer-policy: no-referrer permissions-policy: camera=(), microphone=(), geolocation=(), payment=() cross-origin-opener-policy: same-origin cross-origin-resource-policy: same-origin

Set by 30 lines of vercel.json committed to the repository — no plan upgrade, no sales call, no support ticket. Reviewed in a pull request and rolled back like any other change.

What to notice

script-src 'none', and no 'unsafe-inline' anywhere. sdata.us will not reach 'none' — it needs its own JavaScript — but it reaches script-src 'self' 'nonce-…', which is the same class of protection. That is the difference between a policy that stops an injected script and a policy that merely logs where it phoned home.