Scriptlock 2.1

The code execution prevention system for JavaScript


For the website developer Scriptlock can be implemented in a five easy steps:

  1. Firstly, the Scriptlock service needs to be installed on your webserver OR you can subscribe to our hosted service.
  2. Your website's CSP policy may need some adjustment to allow the Scriptlock protection to work.
  3. Websites with CSP2.0 already deployed require a minor modification to the way the "nonce" is generated.
  4. Four lines of code need to be added to the top of your HTML header to invoke the protection mechanism.
  5. Finally, inline script and inline events must have a "nonce" attribute added, or use one of the alternate semantics provided by Scriptlock. If you already have CSP2.0 implemented then this step may already have been completed.

How it works

Scriptlock comprises two essential components: The scriptlock.min.js rewriter script and the Scriptlock script server. Together these components work to provide the protection mechanism.

The rewriter script is loaded as an external script at the top of the HTML body. Before the rewriter script loads two carefully crafted inline scripts diagnose and detect the level of CSP available in the client browser. When the rewriter script is executed it uses the outcome of these diagnostic scripts to apply the most approriate form of protection:

  • On pre-CSP browsers the rewriter targets and wraps dead-locked security around the vulnerable parts of the JavaScript language and DOM. It does this by replacing native code (setters, getters, functions, etc) with protective wrappers and then uses Object.defineProperty to render the amended properties non-configurable or non-writable as appropriate. A stack-trace based alogorithm allows the protected wrappers to determine URL and nonce origins during code execution and prevent execution from untrusted origins. This means that unlike CSP1.0 and CSP2.0 script is allowed to execute, but vulnerable parts of the language or DOM are not.
  • On CSP1.0 browsers the system relies on inline script being naturally blocked by the CSP policy. It also relies on unsafe-eval being permitted by your CSP policy. In this mode the rewriter parses blocked "unsafe-inline" SCRIPT elements and executes them if they have a valid nonce attribute. CSP1.0 browsers don't know about the "nonce" attribute so it is natually left in place. The rewriter sanitises the script as per CSP2.0 guidelines. This effectively emulates the CSP2.0 nonce, with one crucial difference: By default execution of inline script is deferred to when the DOM is loaded. To get around the potential problems that deferred execution could cause, a synchronisation script (scriptlock.synchronize.js) can be used to synchronise script execution so that that inline script executes in the correct sequence when mixed with external script. For instance when external script depends on the actions performed by preceeding inline script.
  • On CSP2.0 browsers the system downgrades gracefully to allow the CSP2.0 nonce to work as normal.

Scriptlock also provides the following additional features regardless of the form of protection applied:

  • An extension of the CSP2.0 standard to provide nonce support for inline event handlers. This allows you the freedom to code how you want and makes it quicker and easier to apply CSP protection to existing websites. Since the "nonce" attribute gets deleted by CSP2.0 browsers, an alternate "event-nonce" attribute is used. Scriptlock also allows you to use a comment based nonce in the form /*!nonce:nonce-value*/.
  • Emulation of unsafe-eval. Since Scriptlock relies on eval, Function and setTimeout for various parts of the protection, it offers an emulated 'unsafe-eval' policy, which is as effective and reliable as that provided by CSP.
  • An alternate SCRIPTLOCK tag and alternate comment based nonces in the form /*!nonce:nonce-value*/ that are less vulnerable to certain dangling mark-up vulnerabilities.
  • Scriptlock sanitises residual 'nonce' attributes for older CSP2.0 browsers that have not implemented sanitisation. It also does a sweep for 'data-csp-nonce' attributes that are mistakenly left in place by the PayPal™ API.

Nonce and Policy synchronisation

Since there is no way for JavaScript to access the details of the HTTP headers, the policy and nonce information for the pre-CSP and CSP1.0 forms of protection must be delivered through the scriptlock.min.js rewriter script via the Scriptlock script server.

As the script server downloads the rewriter script to your web page it embeds the nonce and other policy information into the script. The system therefore relies on the nonce embedded in your web page being synchronisable with the nonce embedded by the script server. To achieve this synchronisation it is desirable that the nonce generation code in your web service should be changed to use a HMAC based algorithm.

nonce = Base64URLSafeString ( HMAC ( pid , private-key )  ) 

This algorithm generates the nonce by taking a page identifier (pid) and hashing it with a private key. The pid is itself a kind of nonce as it should be a random number that is regenerated for every page refresh.

The pid is passed to the script server in the query string of the rewriter script request in the HTML HEAD of your web page. The pid is extracted from the query string and combined with the private key using the same HMAC algorith to generate the same nonce.

<script src="/Scriptlock/js/scriptlock.min.js?pid={$pageID}" type="text/javascript">;</script>

Basic integration


The Scriptlock Service must be used to serve up the scriptlock.min.js and scriptlock.synchronize.js scripts to your website. You can also use the Scriptlock Service to serve up other scripts if you need to embed the nonce in those scripts. See the section on advanced integration for why you may want to do this. It is therefore usual to map anything under /Scriptlock/js/* to the service.

If you do not have access to a Tomcat instance, then a hosted option is also available.

Service Installation

Tomcat – war file installation

  1. Use the Tomcat Manager to deploy the Apache Tomcat/webapps /Scriptlock.war file in the Scriptlock installation.
  2. Locate the Scriptlock/WEB_INF/SLSettings.xml file and make the following amendments:

    Set the privatePath value to a local path on your server where you will store your JavaScript files. Note, this should not be accessible to your web service.

    Set the privateKey to a random value with at least 20 characters.
  3. Use the Tomcat Manager to restart the “Scriptlock” servlet.

Tomcat – manual installation

  1. Copy the “Scriptlock” directory from the “Apache Tomcat/webapps” directory in the installation to the Tomcat “webapps” directory on your server. 
  2. Locate the Scriptlock/WEB_INF/CCSettings.xml file and make the following amendments:

    Set the privatePath value to a local path on your server where you will store your JavaScript files. Note, this should not be accessible to your web service.

    Set the privateKey to a random value with at least 20 characters.
  3. On Tomcat 5 and earlier you will need to create a Scriptlock.xml file in Tomcat/conf/Catalina/localhost for the new servlet to be picked up.
  4. Restart Tomcat.


If you are using the IIS isapi_redirect.dll or the Jakarta connector, then you should add a mapping in the file:


Adjust your CSP policy

You CSP policy needs to be amended to do three things under the script-src directive.

  1. Turn on 'unsafe-eval'
  2. Turn off 'unsafe-inline'
  3. Add the 'nonce-' source

Adjust your nonce generation code

For optimal security the nonce generation code in your web service should be changed to use a HMAC based algorithm, as follows:

nonce = Base64URLSafeString ( HMAC ( pid , private-key )  )

The default HMAC algorithm is HmacSHA256 with the output transformed to a Base64 URL Safe string. If you have installed self-hosted Scriptlock service then alternative algorithms can be used. Please see the Advanced Integration Topics for more details.

Example servlet code

If you are using Java/JSP to generate your web pages then typical servlet code code that generates the nonce in the desired way will be as follows:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class MyServlet extends HttpServlet {

  public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
         throws ServletException, IOException { 
         String pid = random256();      
         String nonce = getNonce(pid);
         String CSPPolicy = "default-src 'none'; script-src 'self' 'unsafe-eval' 'nonce';"; 
         CSP header
         response.setHeader("Content-Security-Policy", CSPPolicy.replaceAll("'nonce'","'nonce-" + nonce + "'"));
         Generate your HTML and merge in pid and nonce accordingly


  public static String HMAC(String text, String keyString, String algorithm)
	  throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
	  String digest = null;
	  SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algorithm);
	  Mac mac = Mac.getInstance(algorithm);

	  byte[] bytes = mac.doFinal(text.getBytes("iso-8859-1"));

	  return new String(Base64.encodeBase64URLSafeString(bytes));

  protected String getNonce(String pageID){
      return HMAC(pageID, privateKey, algorithm);
    catch (Exception e)
      return "";
  public static String random256()
	  throws NoSuchAlgorithmException, NoSuchProviderException { 
	  // Get 64 random bytes (256 bits)
	  SecureRandom secureRandomGenerator = SecureRandom.getInstance("SHA1PRNG", "SUN");
      byte[] randomBytes = new byte[64];
	  return new String(Base64.encodeBase64(randomBytes));

Modify your HTML

Document HEAD

Your HTML pages will require the addition of the Scriptlock protection in the <head> section of the html. In pure XSLT this would look as follows:

<script>if(!window.scriptlock) scriptlock = {nonce:'<xsl:value-of select="$nonce"/>', type:'legacy'};</script>
<script nonce="{$nonce}">if(!window.scriptlock) scriptlock = {nonce:'<xsl:value-of select="$nonce"/>', type:'nonce'};</script>
<script src="/Scriptlock/js/scriptlock.min.js?pid={$pageID}" type="text/javascript">;</script>
<script nonce="{$nonce}">scriptlock.protect();</script>

Where $nonce is a variable containing the value of the nonce and $pageID a variable containing the page identifier.

This block of code should always go at the top of the <head> section to make sure it is called before any other scripts. This minimises the risk of a Persistent-XSS attack succeeding.

Each line in the block is very important. The first two lines detect whether you browsers is pre-CSP and whether CSP2.0 is available in that order, the third line loads the rewriter script and the fouth line applies the Pre-CSP protection, if it is required.

The first of the inline scripts is deliberately not protected by a "nonce". This is very important as it will only execute on pre-CSP browsers. Do note, that if you have CSP reporting turned on, you may wish to detect this line at your reporting end point and eliminate it from your reports.

Inline scripts

In order for the pre-CSP stack-trace based algorithm to work properly a little helper function scriptlock.permit(); needs to be added to the top of each inline script. As follows:

<script nonce="{$nonce}">
  /* Your inline script */

In the pre-CSP protection mechanism the helper function identifies the current script in the DOM, checks the nonce attribute and executes the innerHTML of script in an eval called from within the scope of the rewriter script. This has the effect of placing the entire stack trace for execution within the list of valid script sources. The helper function then deletes the inline script and raises an exception to stop the script executing again. In the console you will see exceptions reported as follows:

Script protection applied successfully to inline script

In the CSP1.0 and CSP2.0 protection mechanisms the function does nothing and simply allows the rest of the script to execute normally.

Inline events

The inline events feature provided by Scriptlock provides a long awaited extension to CSP that allows inline events when they are presented in conjunction with a nonce. Make sure the 'inline-event' policy is defined in Scriptlock Service configuration file for this to work.

After the DOM has load, this feature works by detecting and reading inline event attributes from elements. When a valid nonce is detected the event source is shifted from the attribute to a wrapper function that is then bound to the respective event property on the element.

The nonce value for inline events can be provided two ways:

  1. By adding an "event-nonce" attribute to the element with the nonce as its value.
  2. By adding a comment of the form /*!nonce:...nonce value...*/ somewhere in the event source code

The following shows these two methods in action:

<button event-nonce="{$nonce}" onclick="alert('Good inline event');">Click me</button>

<button onclick="/*!nonce:{$nonce}*/alert('Good inline event');">Click me</button> 

In both instances the protection mechanism will remove the event-nonce and event attributes from the DOM.

There are two important points to note about this feature:

  1. Once an event has been processed it can't be access again from the elements event attribute, but it can be accessed from the elements property. Therefore some modification to your website source code may be required to accomodate this.
  2. Any event that fires before the DOMContentLoaded event will not be processed in time.


When the CSP1.0 protection mechanism is applied, inline script is execuded asynchronously in a DOMContentLoaded event listener. In effect this is the same as setting defer on all inline script. In some instances this is not desirable and inline script needs to be synchronised with the execution of external script. For example:

<script nonce="{$nonce}">
  var my_setting = 'some value';
<script src="js/my_script_that_depends_on_my_setting.js" type="text/javascript">;</script>

Scriptlock provides the scriptlock.synchronize.js script to keep things synchronised. Each time this script is loaded and executed it forces any inline script preceeding it to execute. So to fix the code above, just do the following:

<script nonce="{$nonce}">
  var my_setting = 'some value';
<script src="Scriptlock/js/scriptlock.synchronize.js?pid={$pageID}" type="text/javascript">;</script>
<script src="js/my_script_that_depends_on_my_setting.js" type="text/javascript">;</script>

Advanced Integration Topics

Configuring the Scriptlock Service


The scriptlock service is configured by the WEB-INF/SLSettings.xml file, normally located in Tomcat/webapps/Scriptlock directory. This file contains a number of settings that can be used to adjust the Scriptlock policy, nonce algorithm, private key and path to the script repository.

<?xml version="1.0" encoding="ISO-8859-1"?>
<setting name="algorithm">HmacSHA256</setting>
<setting name="policy">'self' 'nonce' 'unsafe-eval' 'inline-events' 'remove-script'</setting>
<setting name="privateKey">your private key</setting>
<setting name="privatePath">path to your repository</setting>


This is a string containing the name of the HMAC algorithm to use. The options supported depend on the Java platform running on your server. Every Java platform is required to support:

  • HmacMD5
  • HmacSHA1
  • HmacSHA256

Other options available to you may include:

  • HmacSHA224
  • HmacSHA384
  • HmacSHA512

algorithm also supports 'pass-through', which tells the nonce generation code to simply output the value of sessionID (sid) + pageID (pid) as the nonce. This is useful in cases where you are not able to change the way your website generates its nonce. The nonce can be passed in as the pid or sid query parameter to the rewriter script request. This is not recommended practice as the nonce could be intercepted in a man-in-the-middle attack. In order to protect the nonce the rewriter script will attempt to remove any occurrence of it in the src attribute of the external script element that loaded the rewriter.


Policy defines a space-delimited list of origins that governs the protection applied by Scriptlock. This should resemble the script-src directive of your website's CSP policy. The policy largely exists for the benefit of the pre-CSP and CSP1.0 protection mechanisms. However there are also general keywords relevant to all mechanisms. The following is supported:

DirectiveDescriptionProtection mechanism
'unsafe-eval' Emulates the CSP 'unsafe-eval' keyword for all protection mechanisms. All
'inline-events' Enables nonce-protected inline events. So long as the element has an "event-nonce" attribute or /*!nonce:...*/ comment containing the correct nonce, the event's source code will be shifted to an event listener. All
'remove-script' By default the "nonce" and "event-nonce" attributes are removed by Scriptlock, as per CSP guidelines. The 'remove-script' keyword forces Scriptlock to remove the entire inline script element from the DOM. All
'nonce' The 'nonce' keyword enables the nonce origin in the pre-CSP and CSP1.0 protection mechanisms. pre-CSP
'self' The 'self' keyword enables the same-domain origin in externally loaded script. pre-CSP
* As per CSP the wildcard (*) allows any domain source. pre-CSP As per CSP adds the specified fully qualified domain source ( to the allowed list. pre-CSP
* As per CSP adds all subdomains for the specified domain source ( to the allowed list. Wildcard (*) processing in Scriptlock is a little more flexible than in CSP as it can be mixed with text, e.g. server* pre-CSP
'unsafe-optimization' Enabled optimisation for events, which allows all execution to proceed if it is originating from an event that was registered from an allowed source. This is considered unsafe, because it by passes the full stack trace algorithm and therefore opens your code to the possibility of variable hijacking. However, if you are using very intensive applications, such as TinyMCE, then this will boost performance back up to normal levels. pre-CSP
'unsafe-legacy' Use this to completely disable pre-CSP protection. pre-CSP


privateKey is a string containing the private-key supplied to the HMAC algorithm


privatePath gives the path to your script repository less the "js". So if your script repository is /Somepath/Scriptlock/js, you should enter /Somepath/Scriptlock here.

Customising the Scriptlock Service

The Scriptlock service provides a means of using your own algorithm to generate the nonce.

  protected String getHash(String sessionID, String pageID);

The standard version of getHash() simply creates a Base64URLSafeString encoded HMAC of the text value comprising sessionID + pageID and the privateKey, where the sessionID and pageID are passed in as "sid" and "pid" values in the query string submitted to the servlet.

You may find it is necessary to override this behaviour in the following instances:

  • When is not possible to modify your website's nonce generation code and you don't want to use the pass-though option. But you are able to reproduce the nonce reliably using your own code.
  • When you want to tie the privateKey to an individual session (a "session secret") or user account.

public class MyScriptService extends SLScriptServlet {

  protected String getHash(String sessionID, String pageID){
    /* Your nonce generation code */

Optimisiation and Peformance

When the pre-CSP protection is applied, a stack-trace based algorithm is used to determine to determine URL and nonce origins during code execution. There is an overhead to this that become apparent in repetative processes. For instance if you need to itterate large lists of elements during load it could add a number of seconds to render time.

Fortunately there is a way of improving performance in these circumstances:

Using the password parameter

The original Scriptlock mechanism rewrote native functions to allow the nonce to be passed as an additional password parameter. This scheme still exists in the current pre-CSP Scriptlock mechanisms and can be utilised to improve performance. Providing the additional password parameter removes the need to execute the stack-trace based algorithm, thereby restoring performance back the speed of native functionality. More over, each of the functions that supports this extra parameter does so in a way that retains compatibilty with the native functions. That is to say that the additional password parameter is naturally ignored by the native functions left in place by the CSP1.0 and CSP2.0 protection mechanisms.

Any script that is managed by the Scriptlock Service can take advantage of this additional parameter. The Scriptlock Service replaces any occurence of /*!param:hash*/ in your script file with the nonce.

IMPORTANT: When passing the nonce as a parameter you need to make sure that there is no way that the calling function could be hijacked to inject XSS into your webpage, noting that the pre-CSP version of the protection mechanism still allows all script to execute.

The rewritten functions

The following functions are presently rewritten by Scriptlock to provide a password parameter:

Original callScriptlock optimised call window.safeOpen(document,"/*!param:hash*/")
document.close() window.safeClose(document,"/*!param:hash*/")
document.write(params) window.safeWrite(document,"/*!param:hash*/",params)
document.writeln(params) window.safeWriteln(document,"/*!param:hash*/",params)
document.createElement(value,options) document.createElement(value,options,"/*!param:hash*/")
document.importNode(node,deep) document.importNode(node,deep,"/*!param:hash*/")
document.cookie window.getCookie(document,"/*!param:hash*/")
document.cookie = value window.setCookie(document,value,"/*!param:hash*/")
implementation.createHTMLDocument(title) implementation.createHTMLDocument(title,"/*!param:hash*/")
window.eval(source) window.eval(source,"/*!param:hash*/")td>
new window.XMLHttpRequest() new window.XMLHttpRequest("/*!param:hash*/")
new window.ActiveXObject(name) new window.ActiveXObject(name,"/*!param:hash*/"),name,features),name,features,"/*!param:hash*/")td>
element.src = value window.setSrc(element,value,"/*!param:hash*/")
element.href = value window.setHref(element,value,"/*!param:hash*/")
element.action = value window.setAction(element,value,"/*!param:hash*/")
element.innerHTML = value window.setInnerHTML(element,value,"/*!param:hash*/")
element.outerHTML = value window.setOuterHTML(element,value,"/*!param:hash*/")
element.appendChild(node) element.appendChild(node,"/*!param:hash*/")
element.addEventListener(eventType, listener, useCapture) element.appendChild(eventType, listener, useCapture,"/*!param:hash*/")
element.attachEvent(eventType, listener) element.appendChild(eventType, listener,"/*!param:hash*/")
element.cloneNode(node) element.cloneNode(node,"/*!param:hash*/")
element.insertAjacentHTML(where,value) element.insertAjacentHTML(where,value,"/*!param:hash*/")
element.insertAjacentElement(where,node) element.insertAjacentElement(where,node,"/*!param:hash*/")
element.insertBefore(new_node,existing_node) element.insertBefore(new_node,existing_node,"/*!param:hash*/")
element.removeChild(node) element.removeChild(node,"/*!param:hash*/")
element.removeNode(remove_children) element.removeNode(remove_children,"/*!param:hash*/")
element.replaceChild(new_node,old_node) element.replaceChild(new_node,old_node,"/*!param:hash*/")
element.setAttribute(attrib_name,attrib_value,case_sensitivity) element.setAttribute(attrib_name,attrib_value,case_sensitivity,"/*!param:hash*/")
element.setAttributeNode(node_reference) element.setAttributeNode(node_reference,"/*!param:hash*/")
element.setAttributeNodeNS(node_reference) element.setAttributeNodeNS(node_reference,"/*!param:hash*/")
element.setAttributeNS(attrib_ns,attrib_qn,attrib_value) element.setAttributeNS(attrib_ns,attrib_qn,attrib_value,"/*!param:hash*/")
element.removeAttribute(attrib_name,case_sensitivity) element.removeAttribute(attrib_name,case_sensitivity,"/*!param:hash*/")
element.removeAttributeNode(node_reference) element.removeAttributeNode(node_reference,"/*!param:hash*/")
element.removeAttributeNS(attrib_ns,attrib_ln) element.removeAttributeNS(attrib_ns,attrib_ln,"/*!param:hash*/")

Use the trusted() function

Scriptlock provides the scriptlock.trusted() function to define a block of code within which all execution should be trusted. Within such a block the stack-trace algorithm will not be used and the performance of your code should be nearly normal. The trusted() function takes two parameters, a function to execute and an optional password parameter, which should contain the nonce. If the password parameter is omitted then the regular stack-trace based algorithm is used to determine if the calling function is in scope of a trusted origin or a nonce. Calls to trusted() can be nested.


Be extra careful when using this function. It is considered unsafe because it is vulnerable to variable hijacking. That is if your code accepts functions from variables in the global name space, then a malicious function could be injected into those variables and make their way into the trusted line of execution.

Use the 'unsafe-optimization' policy

The 'unsafe-optimization' policy will treat as trusted any event or event listener that was added from the scope of a trusted origin or a nonce. This means that any code within such events will execute a near normal speed. However, this mechanism is considered unsafe because it is vulnerable to variable hijacking. That is if you pass functions about in variables and those variables are exposed to the global name space, then a malicious function could be injected into those variables and make their way into the trusted line of execution.

Notes about pre-CSP protection

Scriptlock's pre-CSP protection is usually very straight forward. However, there are some gotcha’s to be aware of in particular circumstances or when you are optimising your code.

Window object functions

Scriptlock creates a number of functions on the window object, including safeOpen, safeWrite, safeWriteln, safeSetSrc. It is important that when these functions are used from the correct window object that owns or contains the document or element that they are acting on. Instances where this might catch you out include: using sub-documents and IFRAMES. With the stack-trace based algorithm, for the most part you will not need to use these functions.

XML Documents

Protection is not needed for XML Documents, as they reside in a separate security scope to the parent window and cannot therefore be exploited to reverse or circumvent the Scriptlock protection.  However, a quirk in Internet Explorer means that you must treat them as being protected.

In Internet Explorer XML documents created with createDocument inherit the prototype function protection of the parent window automatically. The same does not happen in Firefox. XML documents created with ActiveXObject('MSXML2.DOMDocument') or ActiveXObject('Microsoft.XmlDom') do not inherit any prototype function protection either.

If you find that you are optimising code around a document created in this way, then this quirk should not cause problems. You can pass the password parameter to functions in the unprotected XML document as though they were protected and the parameter is just ignored.

function getXML() {
  var i = document.implementation;
    if (!i || !i.createDocument) {
      // Try IE objects
      try {return new ActiveXObject('MSXML2.DOMDocument',"/*!param:hash*/");} catch (ex) {}
      try {return new ActiveXObject('Microsoft.XmlDom',"/*!param:hash*/");} catch (ex) {}
    else return i.createDocument('', '', null);

var d = getXML();
var e = d.createElement("html","/*!param:hash*/"); // Always works!

In the above example: If getXML returns a document created with createDocument on Internet Explorer, then the password parameter is required for createElement to work. Otherwise if it is create with ActiveXObject or created with createDocument on Firefox, the native version of createElement just ignores the password parameter.

HTML Documents

HTMLDocuments created with createHTMLDocument are protected automatically by Scriptlock. Optimised calls to protected functions within the scope of these documents can be made with the usual password parameter.


Same domain Iframes and frames are automatically protected by Scriptlock at the point of creation: as they are created by setInnerHTML, setOuterHTML or as they are attached to a document using appendChild, etc. If the IFRAME/FRAME loads an html document the protection is deleted, so it is up to the loaded html document to reapply the Scriptlock protection in its <head> element.

Child Windows

Unlike IFRAMES, child windows are not automatically protected by This behaviour may change in future versions of Scriptlock. As with IFRAMES it is up to the loaded html document to apply the protection in its <head> element.