Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Markus Wollny
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Markus Wollny

Getting FusionReactor User Experience Monitoring (UEM) To Play Nicely With Content Security Policy (CSP) In ColdFusion

By
Published in

For the past few days, I've been digging into some network latency issues on my blog. And, in response to some of my public messaging on the topic, David Tattersall suggested that I look into FusionReactor's User Experience Monitoring (UEM). Whereas FusionReactor's Java agent provides server-side insights and confidence, the UEM module is designed to shed light on the end-user experience (UX). After all, the server-side leg is only part of the journey. Getting UEM up-and-running is easy; but, out of the box, it doesn't play very nicely with my Content Security Policy. As such, I wanted to share how I got it working on my ColdFusion blog.

For starters, FusionReactor's UEM works by hooking into — and overriding — the response for two different URLs:

  • /fusionreactor/UEM.cfm
  • /fusionreactor/UEMJS.cfm

Apparently you can get this to work with virtual directories; but, it was easy enough for me to just create two empty CFM files on my server - path of least resistance.

Once I had these two empty files in place, loading the UEM client-side library is done through a snippet of dynamically generated JavaScript code. By calling .getUemTrackingScript() on my FusionReactor API (FRAPI) instance, a few lines of JavaScript code are output. This is meant to be called inside and existing Script tag:

<cfscript>

	frapi = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" )
		.getInstance()
	;

	csp = application.contentSecurityPolicy.getCspConfig();

	// Set the strict Content-Security-Policy.
	cfheader( attributeCollection = csp.header );
	cfheader( attributeCollection = csp.reportToHeader );

</cfscript>
<cfoutput>

	<!doctype html>
	<html lang="en">
	<body>

		<h1>
			FusionReactor - User Experience Monitoring (UEM)
		</h1>

		<script type="text/javascript">
			#frapi.getUemTrackingScript()#
		</script>

	</body>
	</html>

</cfoutput>

As you can see, this ColdFusion page is defining Content Security Policy (CSP) HTTP headers. It then executes #frapi.getUemTrackingScript()# inside a <script> tag. Unfortunately, when we run this, we see that the browser is blocking it from running.

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'nonce-UiUdwPODb55fVAT4wI7L6Q==' 'strict-dynamic' https: 'unsafe-inline'". Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list.

Now, this isn't FusionReactor's fault - this is my fault. I set the CSP HTTP Headers but I didn't provide a nonce attribute in my Script tag. No problem, let's add the nonce:

<cfscript>

	frapi = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" )
		.getInstance()
	;

	csp = application.contentSecurityPolicy.getCspConfig();

	// Set the strict Content-Security-Policy.
	cfheader( attributeCollection = csp.header );
	cfheader( attributeCollection = csp.reportToHeader );

</cfscript>
<cfoutput>

	<!doctype html>
	<html lang="en">
	<body>

		<h1>
			FusionReactor - User Experience Monitoring (UEM)
		</h1>

		<script type="text/javascript" nonce="#encodeForHtmlAttribute( csp.nonce )#">
			#frapi.getUemTrackingScript()#
		</script>

	</body>
	</html>

</cfoutput>

This is the same exact code, only this time my Script tag has the necessary nonce attribute. However, this time, when we run the ColdFusion code, we get the following error:

Refused to load the script 'https://www.bennadel.com/fusionreactor/UEMJS.cfm' because it violates the following Content Security Policy directive: "script-src 'nonce-pWMaZtnik4WXXU8hc++C/Q==' 'strict-dynamic' https: 'unsafe-inline'". Note that 'strict-dynamic' is present, so host-based allowlisting is disabled. Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

At first, this error confused me. But then, I looked at what the FusionReactor API was outputting. When I call #frapi.getUemTrackingScript()#, this is what is being generated:

<script type="text/javascript" nonce>
	var anUrl = "/fusionreactor/UEM.cfm?db=0&wr=11&s=8B92BB6DBB4B1FFF4ED56B4CF4A2F0B7&t=927808";
	document.write(unescape("%3Cscript src='/fusionreactor/UEMJS.cfm' type='text/javascript'%3E%3C/script%3E"));
</script>
<script src="/fusionreactor/UEMJS.cfm" type="text/javascript"></script>

As you can see, part of what the FusionReactor-generated code is doing is writing another <script> to the current page. And, this new Script tag doesn't have the necessary nonce attribute required by my CSP.

What we can see from the output is that both of the URLs in the output are the /fusionreactor/ URLs that I mentioned above. So, to make this play nicely with my site's CSP, I'm going to capture the FRAPI output, extract the two URLs, and then manually write-out the two script tags:

<cfscript>

	frapi = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" )
		.getInstance()
	;
	// Generate the UEM output; but, instead of writing the output to the page, I'm going
	// to capture the content and extract the two `/fusionreactor/` URLs. These will be
	// explicitly written the page down below.
	uemScriptUrls = frapi.getUemTrackingScript()
		.reMatchNoCase( "/fusionreactor/[^'""]+" )
	;

	csp = application.contentSecurityPolicy.getCspConfig();

	// Set the strict Content-Security-Policy.
	cfheader( attributeCollection = csp.header );
	cfheader( attributeCollection = csp.reportToHeader );

</cfscript>
<cfoutput>

	<!doctype html>
	<html lang="en">
	<body>

		<h1>
			FusionReactor - User Experience Monitoring (UEM)
		</h1>

		<script type="text/javascript" nonce="#encodeForHtmlAttribute( csp.nonce )#">
			var anUrl = "#encodeForJavaScript( uemScriptUrls[ 1 ] )#";
		</script>
		<script
			type="text/javascript"
			src="#uemScriptUrls[ 2 ]#"
			nonce="#encodeForHtmlAttribute( csp.nonce )#">
		</script>

	</body>
	</html>

</cfoutput>

Now, instead of transparently letting FusionReactor write to the output, I'm stepping in as an intermediary. Essentially, I'm generating the output on behalf of FusionReactor so that FusionReactor doesn't have to perform the dynamic script-injection. Obviously, this more tightly-couples my ColdFusion code to the implementation details of the UEM module. But, the only time I ever have to check on this compatibility is when I upgrade my FusionReactor version.

And, when we run the ColdFusion code this time, we can see from the network activity that everything went smoothly!

As you can see, no CSP violation errors show up and the UEMJS.cfm network request is made successfully!

I think that the .getUemTrackingScript() function could easily be updated to encapsulate all of this. First, it could take an optional nonce argument. But then, it could also generate the actual <script> tags (both of them) instead of trying to execute as a line of JavaScript. However, in the meantime, this is how I got it working on my ColdFusion site.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel