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 CSP directives that you need to include with Fullstory.

  • connect-src: https://edge.fullstory.com https://rs.fullstory.com

  • script-src: https://edge.fullstory.com https://rs.fullstory.com
  • img-src: https://rs.fullstory.com

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 data capture snippet that downloads the data capture script (fs.js);

  • The data capture 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=”www.somewhere.com/path/to/some.js”/>), 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 data capture snippet to be inline.  We prefer it because it starts data capture 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: https://edge.fullstory.com https://rs.fullstory.com '[YOUR HASH HERE]'


**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

As a note, the "snippet value" is everything between the <script> tags in your org's Settings page. You should not include the tags in the value you hash.

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.

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.

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

script-src: https://edge.fullstory.com https://rs.fullstory.com 'unsafe-inline'

(Note that support for CSPv1, while more prevalent than CSPv2, is still not universal. For example, Internet Explorer 11 and below only support the sandbox directive.)

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: https://edge.fullstory.com https://rs.fullstory.com

  • script-src: https://edge.fullstory.com https://rs.fullstory.com 'self'

The complication isn’t what you put into your CSP header itself, it’s what you do with your data capture 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=”https://my.site.com/fs-snippet.js”/ 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 data capture snippet here…
if (window[“_fs_loaded”]) {
   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) {
  FS.identify(strUserID);
} else {
  window[“_fs_loaded”] = function() {
     FS.identify(strUserID);
  };
}

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.  

References

https://content-security-policy.com/ 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).

https://www.w3.org/TR/CSP2/ is the official W3C candidate recommendation for CSPv2.

https://developer.mozilla.org/en-US/docs/Web/Security/CSP is, in effect, the specification for CSPv1.  (https://www.w3.org/TR/CSP1/ 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 http://www.xorbin.com/tools/sha256-hash-calculator.  Please remember to be careful about keeping the whitespace exact in your input, because it does change the output!

FAQ

I have an EU org. How should my URLs be formatted?

Customers with an EU org need to include eu1 in the listed URLs like so:

  • connect-src: https://edge.eu1.fullstory.com https://rs.eu1.fullstory.com

  • script-src: https://edge.eu1.fullstory.com https://rs.eu1.fullstory.com
  • img-src: https://rs.eu1.fullstory.com

Was this article helpful?

Got Questions?

Get in touch with a Fullstory rep, ask the community or check out our developer documentation.