Security Basics

9 min

Web Security Headers Checklist for SaaS Apps

Web Security Headers Checklist for SaaS Apps

A missing header rarely looks urgent until it turns a small frontend mistake into account compromise. The headers that matter for live SaaS products, and the trade-offs that break a rushed rollout.

A missing header rarely looks urgent until it turns a small frontend mistake into account compromise. The headers that matter for live SaaS products, and the trade-offs that break a rushed rollout.

A missing header rarely looks urgent in a pull request. The app loads, users can sign in, payments complete, and the release ships. But a weak Content Security Policy, absent clickjacking protection, or overly permissive cross-origin rule can turn a small frontend mistake into account compromise, data exposure, or an avoidable support incident. This web security headers checklist focuses on the headers that matter for live SaaS products, along with the trade-offs that can break a rapidly assembled application if you deploy them carelessly.

Security headers are browser instructions sent with HTTP responses. They do not replace authentication, authorisation, Supabase RLS, secure API design, or server-side validation. They reduce the harm when something else goes wrong. That makes them especially useful for products built quickly with AI assistance, third-party scripts, hosted authentication, and JavaScript-heavy front ends.

Start with the routes your users actually reach

Do not assume one header policy fits every response. Your marketing pages, authenticated dashboard, OAuth callback, API routes, file downloads, payment checkout, and embedded widgets can have different requirements. A global policy is usually the right starting point, but exceptions should be deliberate, narrow, and documented.

First, inspect response headers from the live production domain, not only localhost. Check the main application domain, www redirects, API subdomains, preview deployments, and any custom domains customers use. A secure configuration in a framework file does nothing if a CDN, reverse proxy, serverless platform, or separate API gateway overwrites it.

Also identify services the browser must contact. Common examples include a Supabase endpoint, authentication provider, payment processor, error-monitoring platform, product analytics, image CDN, transactional-email domain, or WebSocket service. This inventory is essential before enforcing a Content Security Policy.

Web security headers checklist

The following headers should be reviewed as a set. Copying a generic configuration without testing can block sign-in, payment flows, fonts, images, or embedded content. The goal is a defensible policy that supports the application you actually run.

Enforce HTTPS with Strict-Transport-Security

Strict-Transport-Security, usually called HSTS, tells browsers to use HTTPS for future visits. It prevents a visitor being silently sent to an insecure HTTP version of your site after they have received the header once.

A typical production value is max-age=31536000; includeSubDomains. Only add preload after you are certain every current and future subdomain supports HTTPS. That choice is difficult to reverse and can cause outages for overlooked staging, customer, or legacy subdomains.

HSTS should be served only over HTTPS. It is not a substitute for redirecting HTTP traffic to HTTPS at your edge or hosting provider.

Set a strict Content-Security-Policy

Content Security Policy, or CSP, is the header that deserves the most attention. It controls where a page may load scripts, styles, images, fonts, frames, and network connections from. A well-designed CSP significantly limits cross-site scripting impact, including script injection caused by unsafe user-generated content or an AI-generated UI component that renders untrusted HTML.

Start from a restrictive baseline such as default-src 'self', then explicitly allow only the sources your application needs. Pay close attention to these directives:

  • script-src controls JavaScript. Avoid 'unsafe-inline' and 'unsafe-eval' where possible. Use nonces or hashes for required inline scripts.

  • connect-src controls browser requests to APIs, WebSockets, analytics, and authentication services. Missing a Supabase or payment domain here often causes production failures.

  • frame-ancestors prevents unauthorised sites embedding your app. For most SaaS dashboards, 'none' is appropriate. Use specific trusted origins only when embedding is a real product requirement.

  • img-src, style-src, and font-src should include only the CDNs and providers you use.

  • object-src 'none' disables old browser plugin content that modern applications do not need.

  • base-uri 'self' prevents injected markup changing how relative URLs resolve.

For a first rollout, use Content-Security-Policy-Report-Only and collect violations. It lets you see which legitimate resources would be blocked before enforcing the policy. Reporting is not permission to leave the policy in report-only mode indefinitely. Review the reports, remove unnecessary third-party scripts, fix the exceptions, then enforce it.

Be particularly suspicious of a CSP that permits *, broad third-party wildcards, or both 'unsafe-inline' and 'unsafe-eval'. Such a policy may satisfy a scanner while delivering little protection. Some frameworks and legacy libraries need exceptions, but each exception should have an owner and a reason.

Prevent MIME sniffing with X-Content-Type-Options

Set X-Content-Type-Options: nosniff. This tells browsers not to reinterpret a response as a different content type. It helps prevent a file intended as text or a download being treated as executable script in the wrong context.

This is low risk for most applications. The operational work is making sure your server sends correct Content-Type values for JavaScript bundles, stylesheets, JSON responses, uploads, and downloadable files.

Control referrer data with Referrer-Policy

A Referrer-Policy limits what URL information the browser sends when a user follows a link or loads a third-party resource. strict-origin-when-cross-origin is a sensible default for many applications. It keeps full referrer paths within your own site but sends only the origin to a different HTTPS site.

This matters when URLs contain identifiers, search terms, invitation tokens, or other values that should not be exposed to an analytics provider or external destination. Sensitive tokens should never be placed in URLs in the first place, but the header provides useful containment.

Limit browser features with Permissions-Policy

Permissions-Policy controls access to browser capabilities such as camera, microphone, geolocation, payment, USB, and fullscreen. Disable features your product does not use. For example, a typical B2B dashboard may have no reason to request a user’s location, camera, or microphone.

Do not disable payment capability blindly if your checkout implementation depends on it. The same applies to camera access for identity verification or document capture. Check the actual product flow, then allow only the required feature and origin.

Add isolation headers where they fit

Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy can reduce risky interactions between your site and cross-origin content. They are valuable, but they are more likely than the basic headers to affect integrations, pop-up authentication, embedded tools, and cross-origin assets.

Use Cross-Origin-Opener-Policy: same-origin only after testing OAuth and payment pop-ups. If your sign-in flow opens an external identity-provider window, an overly strict setting can interfere with the return path or window communication. Treat these headers as an integration-testing task, not a box-ticking exercise.

Headers that should not be your priority

You may still see X-Frame-Options, X-XSS-Protection, and Expect-CT in old security guidance. X-Frame-Options can provide compatibility protection against framing, but frame-ancestors in CSP is more flexible and modern. X-XSS-Protection is obsolete and should not be relied on. Expect-CT is no longer useful for current browser behaviour.

Do not spend a day tuning legacy headers while an API accepts unsigned JWTs, a Supabase service-role key is exposed in a frontend bundle, RLS is disabled, or an IDOR lets one customer fetch another customer’s records. Headers reduce browser-side attack surface. They do not repair broken access control.

Test the policy as a product change

Treat header changes like code changes. Test unauthenticated pages, sign-up, password reset, magic-link and OAuth callbacks, dashboard navigation, file upload and download, payment checkout, webhooks with a browser-facing status page, and any embedded customer-facing experience.

Use browser developer tools to watch CSP violations and failed network requests. Check both desktop and mobile browsers if your users rely on them. Then test negative cases: attempt to embed the dashboard from an untrusted domain, load an unexpected script, and request a sensitive route over HTTP.

For framework-based applications, keep the configuration in version control and review it alongside infrastructure changes. If headers are set in a CDN dashboard, capture the exact policy in deployment documentation as well. The common failure mode is not choosing a bad value. It is forgetting where the active value is configured.

Roll out without creating a Friday incident

Apply the baseline headers first: HSTS, nosniff, referrer policy, sensible permissions policy, and clickjacking controls. Introduce CSP in report-only mode, fix legitimate violations, and enforce it once the required sources are known. Add cross-origin isolation headers only after testing the integrations they may affect.

Recheck after adding a new analytics tool, payment provider, customer-support widget, AI service, or authentication method. Every new browser-side dependency changes the policy you need and creates another third party that can influence your users’ sessions.

If your app is live and the header configuration is unclear, start with evidence rather than guesswork. Review the real responses, map the critical flows, and fix the highest-impact gaps first. HollowByte’s audits assess headers alongside the controls that matter more when they fail: credentials, RLS, JWT handling, API exposure, IDOR, and payment logic. A secure header set is most useful when it is part of a product that earns the same level of scrutiny throughout.

Back to blog

(function() { function applyMainRole() { var hero = document.getElementById('hero'); if (!hero) return false; var node = hero; while (node.parentElement && node.parentElement !== document.body) { node = node.parentElement; } if (node && node.parentElement === document.body) { node.setAttribute('role', 'main'); return true; } return false; } if (applyMainRole()) return; var attempts = 0; var interval = setInterval(function() { attempts++; if (applyMainRole() || attempts > 20) { clearInterval(interval); } }, 250); })();