Our story (or, a vulnerability mitigated)

A few months ago, we were adding Markdown support to reports on Federacy (we're a vulnerability research and disclosure platform) as part of a major release and used a feature flag to roll it out selectively. Unfortunately, when we implemented the flag, we accidentally removed sanitization from the non-Markdown version. This resulted in a cross-site scripting vulnerability. Luckily, one of our researchers found it quickly (thanks, Rey Mark)!

We were surprised that despite having several layers of theoretical protection against XSS, including an external WAF, in-app WAF, and RASP, the vulnerability was not mitigated.

What did protect us was a HTTP security header: Content Security Policy. Our script-src attribute did not include unsafe-inline, which meant that any modern browser would block exploitation of the vulnerability.

Because we think a lot of engineering teams might find this helpful, we wrote a quick intro to the whys and hows of security headers.


Security headers are one of the least understood, yet most impactful safeguards you can easily add to improve your application security.

They can be used to enable or disable specific features to improve the security of the context within which your application is run. This provides a layer of protection against vulnerabilities that could be introduced to your application by one of your developers or any of the hundreds of open source dependencies you likely rely upon.

What are security headers?

Most security headers are preventative controls. They defend against a multitude of vulnerabilities, including cross-site scripting and request forgery, cookie hijacking, session fixation, and clickjacking attacks. Many of these vulnerabilities could lead to leaking your user's private information.

A brief overview

Following is a simplified explanation of each of the security headers.

Startlingly low adoption rates

Security header adoption remains remarkably low despite their inherent value. For instance, content security policies (CSPs) are used by only 4% of the Alexa Top 1 million. Within that 4%, very few CSPs utilize nonces or hashes, which are important to mitigate cross-site scripting attacks, and a vast majority continue to use unsafe attributes.

The OWASP Application Security Verification Standard (ASVS) is an open standard for application security with at least 19 tests relating to security headers.

The most widely utilized security headers are HSTS, X-Content-Type-Options, X-Frame-Options, and X-XSS-Protection—used by between 14 and 17% of sites. These headers are enabled by default in many WAFs, firewalls, and web application frameworks.

What you can do today

1. Check your current posture

You can use the following tools to analyze your headers:

Mozilla Observatory: https://observatory.mozilla.org
SecurityHeaders.com: https://securityheaders.com
Google's CSP Evaluator: https://csp-evaluator.withgoogle.com/

2. Utilize headers

A majority of the headers are static and can be appended in numerous places: WAF, proxy, web framework or server, specialized libraries, or a few lines of code. We've created a few charts (shared below) to show some of your options. Fellow YC companies Templarbit and Sqreen will add static security headers when the application itself can't be significantly modified, as will Cloudflare Workers and Lambda@Edge on Amazon Cloudfront.

3. Review your cookie attributes

httpOnly, Secure and sameSite attributes can help to improve cookie security and are generally very simple to implement; however, it should be noted that they only provide some protection against XSS. The libraries and frameworks (as shown below) that include Set-Cookie support some or all of these attributes.

In terms of adoption, only 57% of session cookies use the httpOnly attribute, and as few as 20% use the Secure attribute. The SameSite attribute is used even more infrequently—in less than 0.01% of the Alexa Top 1 million sites.

4. Implement a stop-gap CSP

You might want to start off with a looser CSP that includes unsafe attributes as a stop-gap, but in the long run, it's worth investing the time to remove it and utilize hashes and/or nonces.

5. Tighten up your CSP

Remove unsafe attributes (especially unsafe-inline script-src) and leverage a CSP reporting tool while you test and resolve any issues.

You can use the following tools to do this: Templarbit, Scott Helm's Report URI, and Sqreen (the former two also support Expect-CT reports).


Some frameworks support security headers out of the box, and all major frameworks have libraries with security header functionality. We’ve researched the most popular and made charts to help, but if we missed anything, please let us know!

Rails added support for CSP nonces in 5.2 (4 December 2018); Nuxt added hashes in 1.1.0 (12 January 2018); and Play added nonces and hashes in 2.7 (1 February 2019). Django depends on the Django CSP or Django Security libraries to add a CSP; the former supports nonces.


Essentially every language and framework has a secure headers library with varying degrees of capabilities, including dynamic headers for nonces, hashes, and cache-control.

Need help with implementation? Feel free to reach out anytime for input. :)

And if your company doesn't currently run a vulnerability disclosure or bug bounty program, or you are looking for an assessment/pentest, we'd love to help with that as well!

Other resources


CSP docs and cheatsheets