Can I use Content-Security-Policy (CSP) with FullStory?

Content-Security-Policy tells the browser what your page should interact with, and that lets the browser stop it if something on your page (maybe something maliciously injected via something like an XSS attack) tries to interact with anything else.

The content of this article is intended for developers with a technical background to get FullStory up and running in an environment where CSP is enforced. It's not intended to prescribe best practices for how your site should approach security as it relates to CSP.

In addition to whatever permissions your site needs without FullStory, there will be two CSP directives that you need to include with FullStory.

  • connect-src:

  • script-src:

We need connect-src to make connections from your visitors browsers to our servers, to report on the pages the visitors use, and report the events that happen on those pages. There are three pieces of code that will be used by FullStory:

  • The recording snippet that downloads the recording script (fs.js);

  • The recording script, fs.js;

  • Code for any integrations that you may be using with FullStory.

Each of these will require some work to include with your site.

CSPv2 and Script-Src hashes

In its first version, CSP either doesn’t allow inline scripts (that is, script loaded any way except <script src=””/>), or it allows any inline script, which is a problem because of XSS and similar injection attacks.  In the second version of CSP, however, you can specify which scripts you’re willing to allow to be inline.  Of course, because the web is what it is, not all browsers support CSPv2 yet.

We much prefer for the recording snippet to be inline.  We prefer it because it starts recording sooner (no waiting for the snippet to load separately), and because not doing it inline means you either have to wait for a synchronous load (which slows your site down) or you need to do extra work to know when you can use our API calls like FS.identify().  An inline snippet solves both problems—but you need CSPv2 to make it safe.

To use CSPv2, just need to add a hash to the script-src CSP directive above, something like this:

script-src 'sha256-iw6bXKZ26G/k=invalid=PNcDIhxUCeSQ/sm1n+bSJs='

That hash is an invalid example; don’t cut-and-paste it.  The correct one for you is going to depend on your snippet, because that contains your orgId, so it’s going to be different for each of our customers.  (The hash also changes if you have a trailing blank line or extra spaces, so be careful.)  There are websites to compute it for you if you paste the snippet into the site, and it can also be computed using the OpenSSL command 

echo -n "snippet value" | openssl dgst -sha256 -binary | openssl enc -base64

The easiest method may be to (in development) use a CSP that doesn’t have the hash, and the browser console will tell you the right value.  For example, you will see a message such as:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-ZznlC9tUx8IcYnPzR8RnQYigA4/Vzq86uY90/LM0qcI='), or a nonce ('nonce-...') is required to enable inline execution.

In this case, the hash (sha256-ZznlC9tUx8IcYnPzR8RnQYigA4/Vzq86uY90/LM0qcI=) can be included as part of the script-src directive.

Integrations and inline scripts

If you use any of our integrations, keep in mind that by default, those integrations are injected also, which means they are also flagged as “inline” scripts. You have one of two options in this scenario:

  • You need a separate hash for each of the inline scripts that will be inlined. As with the snippet, the browser’s console will tell what the value for each integration’s hash should be, too.


  • You can set the value of window["_fs_csp"] = true in the FullStory snippet. This will cause integration scripts to be loaded in a separate script request (rather than being inlined). This may be a bit slower from a performance perspective (accounting for the extra time to make the separate requests for the script), but it removes some of the complexity of having to account for the hash of the integrations.

Again, not all browsers support CSPv2 yet.  Relying on it means that old versions of Safari, any version of Internet Explorer, and some mobile or rarely-used browsers will be unprotected.  Still, this is a good balance of protection and ease of use, and we expect that “trailing edge” to shrink, so it may be a good solution.

CSPv1 and ‘unsafe-inline’

If you don’t rely on CSPv2, you can always use a CSPv1 script-src with unsafe-inline, although it’s not what we recommend.  Still, inline scripts can be hard to get rid of if you started your site with them, so if you already need unsafe-inline, you can use an inline FullStory snippet without adding any new vulnerability.  And if your other CSP directives are tight, even if you assume that some sort of injection has put “bad” inline script onto your page, the other directives can prevent any exfiltration as a result, or injection of big or changeable scripts via a <script src=.../> tag.

So this is at least an easy way to get many of the benefits of CSP and to limit your exposure.  And it is super easy; just include that script-src and the connect-src directive, and you don’t need to worry about anything else.  Also, you can exploit a quirk of Internet Explorer’s being very behind the times, and have a CSPv2 Content-Security-Policy header, and also a CSPv1 X-Content-Security-Header with unsafe-inline—Internet Explorer is so behind that it still looks only for the second header, so it will get some protection while other browsers get v2 protection.

In this scenario, the script-src tag looks like:

script-src 'unsafe-inline'

CSPv1 without ‘unsafe-inline’

Alright, if you can’t use CSPv2, and you’re unwilling to use that unsafe-inline keyword—you do still have an option, although of course it’s the most complicated one.

Your CSP header is going to have, in addition to whatever else your site needs:

  • connect-src:

  • script-src: 'self'

The complication isn’t what you put into your CSP header itself, it’s what you do with your recording snippet.  That is either going to be embedded into your own JavaScript code, which you must be loading from some source URL already, or it’s going to be replaced with something like:

<script src=””/ async>

The async keyword is important; that’s what keeps FullStory from slowing your page load at all.  And that fs-snippet.js URL is going to return the FullStory snippet from your settings page in our UI (or, we hope, from your web pages already using FullStory).  That alone will load FullStory, without an inline script, only slightly slower than if it were inline.

If your site has code of your own, you might put the snippet into your script directly; we wrote it to be safe even if you do both and get two copies on a page somewhere.  If you don’t put the snippet into your own script, you might need to add just a wee little bit to allow you to call e.g. FS.identify() whenever you need to:

…normal recording snippet here…
if (window[“_fs_loaded”]) {

Those three extra lines are useful so that, elsewhere in your code, you could define window[_fs_loaded] as a function to call FS.identify() only after the snippet had defined the FS namespace object and the various functions in it, something like this:

if (window.FS) {
} else {
  window[“_fs_loaded”] = function() {

You need this because, due to the async load of the snippet, you can’t know whether your code will run before or after the snippet does.  If the snippet loads first, and your code after that, the direct call to FS.identify() runs.  If your code loads first, and FS with its functions isn’t yet available, a call to FS.identify() is saved in window[_fs_loaded] and will be run later.  (If you need to, you can make a fancier version that chains multiple calls, or makes _fs_loaded be an array and loop so all the functions in it are called).

If you have questions, feel free to reach out, and we’re happy to help.

Closing Thoughts

Remember, it’s always good to first test your proposed CSP with a Content-Security-Policy-Report-Only header.  If you already do something with CSP but want to tighten it (for example, to chase away unsafe-inline), you can deploy a “new” policy in the CSPRO header, while still enforcing an “old” policy with Content-Security-Policy.  But you definitely want to be sure the report-only header doesn’t generate any reports before you start enforcing it and accidentally break your site!

If you do use CSPv2, the hashes will occasionally change, and you’ll need to update your policies.  But the snippet is under your control, so you’ll know to do the CSP update whenever you update your snippet, and it will be stable apart from that.  Similarly, if you add or remove any integrations, that will change the hash of the integrations—I would leave both the old and the new ones in your CSP for a bit, to account for possible caching as you migrate.

References has a pretty useful description of the various CSP versions, including which browsers support what.  It also includes a browser test tool to see what your browser will actually allow (assuming you can get a copy of the browser you’re interested in). is the official W3C candidate recommendation for CSPv2. is, in effect, the specification for CSPv1.  ( is an aborted W3C draft to codify CSPv1, but it discontinued as they skipped up to v2 instead.)

There are dozens of SHA256 generators; one is at  Please remember to be careful about keeping the whitespace exact in your input, because it does change the output!

Can’t find what you’re looking for?

The FullStory team awaits your every question.

Contact Us