IBM Support

Content Security Policy (CSP) Enhancements for Safe & Resilient Plugin Development

How To


Summary

The IBM Content Navigator 3.0.15 release introduces a new mechanism to enhance the Content Security Policy (CSP). We created a servlet filter, CSPFilter, which removes the `unsafe-inline` and `unsafe-eval` sources from the `script-src` directive. The CSPFilter then adds the `nonce` source, which allows only JavaScript tags with the valid nonce to execute on the browser. Learn more about the CSPFilter and how to work around any breaking changes introduced by removing `unsafe-inline` and `unsafe-eval` sources.

Enabling this feature is for testing purposes at this time. Due to the changes at the browser level, several apps and plugins that rely on Content Navigator may break.

Objective

The CSPFilter abstracts the requirement for users to manually edit or modify their CSP. It's a modular approach that can remove one or both of the unsafe sources. When unsafe-inline is removed, the nonce is applied. This technote covers the following topics:
  • Understand the CSPFilter and how to enable it.
  • Understand the side effects of removing unsafe-inline and unsafe-eval.
  • Understand the nonce.
  • Understand the new configuration properties of the Dojo Framework.
  • Learn about breaking changes introduced by removing eval, inline scripts, and even the asynchronous loading of Dojo modules.

Environment

Enabling the CSPFilter

Testing the future. Before you enable the filter on a production server, test its effects on a non-production event.

There are two ways to test your application with the new filter. The two new settings are removeUnsafeEval and removeUnsafeInline. When you set these to true, one or both of the directives are removed from the CSP header.

Setting the Properties

For most developers, setting the JVM options enables the feature. However, this change might not be sufficient depending on the environment type.

Enable the following JVM options by setting them to true:

-Dcom.ibm.ecm.icn.system.security.csp.removeUnsafeEval=true
-Dcom.ibm.ecm.icn.system.security.csp.removeUnsafeInline=true

Once your application restarts, you might not notice any change on your browser. Open the browser Developer Tools and inspect the Network tab. Locate a request to your server. The Content-Security-Policy header should contain nonce- and no longer contain unsafe-inline or unsafe-eval.

Known Issues

When the CSPFilter is enabled and the unsafe sources are removed, some applications and plug-ins that rely on IBM Content Navigator might break, including your own. Use this setting to debug your code and identify which areas are at fault. Follow the guide to learn about the issues and ways to address them.

Steps

Working with Script Nonce

Adding the nonce elevates the security by going above the default source like 'self' or domain-level restrictions.

For example, a user uploads an HTML page with the following code:

<script>alert(1)</script>

When other users open the page using an HTML viewer, they see the alert on their browser.

The script runs because the page is on the same domain as the application. However, adding the nonce to the page restricts such scripts from executing.

CSP Response Header

In the following instance, the CSPFilter is enabled and adds a nonce to the CSP header. In this example, the nonce is abc123.

script-src: 'nonce-abc123'

All scripts that are appended to the page must contain the nonce to execute their code. A valid script tag has the nonce attribute match the nonce header. The HTML would look like the following:

<script nonce="abc123">alert('This has a valid nonce!')</script>

Users are not able to guess the nonce, as it is randomly generated. Scripts added using plugins, are appended to the application using the nonce and execute their code.

With this change, the previous example in which a user uploads an HTML file with a script, no longer works. When a nonce is set, the script on the HTML file no longer executes; that is because the script does not have a valid nonce attribute, so the browser blocks it.

Users who view the HTML file do not see the script's alert dialog, because the script was rejected at the browser level. The potential for XSS on the application is drastically reduced.

Working with the removal of unsafe-inline and unsafe-eval

Restricting scripts from executing on a page with a nonce is a huge benefit. To enhance the security of the application, the sources unsafe-eval and unsafe-inline are also removed. These two items are, in a way, revolutionary because they go against the grain of JavaScript web development of the past several years.

Working without unsafe-eval

Removing the unsafe-eval source blocks all uses of eval and hard-coded new Function() statements on the JavaScript runtime. If your code uses eval to parse statements, the browser no longer executes that code. Given that validating statements passed to eval is a daunting task, this directive simply  removes the core functionality of evaluating any string as JavaScript. This reduces the risk of executing code that a bad actor is able to inject into an application.

Depending on the statements processed by eval, it might be complicated to convert eval strings with standard code.

Here is a simplified example of an eval statement that sums two numbers.

var someNum = myInput.value;
var sum = eval('1 + ' + someNum);

Rewriting this code is fairly straightforward. Additionally, adding validation to sumNum helps reduce unwanted errors.

function addOne(num) {
    var i = parseInt(num, 10);

    if (!isNaN(i))
        return 1 + i;

    return 1;
}

var someNum = myInput.value;
var sum = addOne(someNum);

For more complex uses of eval, break down the statement and simplify the code into functions or code blocks.

Working without unsafe-inline

Removing unsafe-inline from the CSP has its own set of new restrictions. This blocks all inline scripts and script tags that do not contain a valid security token, known as a nonce.

Removing unsafe-inline also disables inline event handlers like onclick, onblur, and so on.

For example, after the change the following code no longer works:

<a id="link"
  class="commonAlertHandler"
  href="#"
  onclick="alert(1)">Alert</a>

The browser will not execute the onclick statement.

Instead, the event must be registered within a script or code block that has been allowed by the nonce attribute.

<script nonce="abc123"> // some valid nonce

// set onclick on the element

document.getElementById('link').onclick = function () { alert(1); };



// or use Dojo to set the event

require(['dojo/on'], function (on) {
    on(someDomNode, 'click', function () { alert(1); });
});

</script>

Dojo Framework Changes & Limitations

Dojo is the core framework that Navigator uses to build its application components.

Starting with Dojo 1.17, the framework is compatible with CSP unsafe-eval and unsafe-inline. Prior to this version, the two sources were required.

Dojo Application Configuration

In addition to the CSP header changes, Dojo’s implementation requires the following settings at the browser level.

Dojo Config:

  • csp-restrictions is the Dojo flag that disables eval in its code. However, there are several classes in which Dojo continues to use eval and breaks the application.
  • async is set to true. This was previously false. Although async is not related to the CSP settings at a quick glance, it is required by the new csp-restrictions attribute. This flag has several breaking changes. See the next section for more details.
  • dojo-v1x-i18n-Api is now false . This was previously true.
  • aliases have been removed. We no longer use ecm.background.frame. Remove any references to ecm.background.frame in your code.

Custom Attribute:

  • csp-nonce is a new property specifically introduced by Content Navigator; it is not part of Dojo. However, you can use this nonce to insert scripts.

Breaking Changes in require Due to Async

Because async: true is now a requirement of csp-restrictions, Dojo loads its modules in an asynchronous fashion.

Prior to this version, async was previously set to a “legacy” mode, which paused code execution while a required module loaded.

With async set to true, a race condition can occur in which the required modules do not load at the right time.

For example

// expects to set this.viewer if it does not exist
if (!this.viewer)
  require([
    viewerPath
  ], lang.hitch(this, function(viewerClass) {
    this.viewer = new viewerClass(viewerParams);
  }));

// an error occurs, because viewer is undefined!
this.viewer.setItem(1);

What’s happening under the hood?

In the past, async was set to legacy mode. This caused each statement to process in sequence.

If there is no viewer, then 1) wait to load the viewer class, 2) instantiate the viewer, 3) then set the viewer's item.

This seems straightforward. However, when async is set to true, the module is loaded asynchronously. Loading the class happens in the background at a different time. This means that the code executes the require and continues to set the item, but the viewer is not defined.

One option is to remove the code from the require and use a callback when the  viewer is available.

// new method for when the viewer is ready
viewerReady: function () {
  this.viewer.setItem(1);
},

// new code flow

if (!this.viewer)
  require([
    viewerPath
  ], lang.hitch(this, function(viewerClass) {
    this.viewer = new viewerClass(viewerParams);
    this.viewerReady();
  }));
else
  this.viewerReady();

Alternatively, you can use Dojo Deferred.

var d = new Deferred();

if (!this.viewer)
  require([
    viewerPath
  ], lang.hitch(this, function(viewerClass) {
    this.viewer = new viewerClass(viewerParams);
    d.resolve(this.viewer);
  }));
else
  d.resolve(this.viewer)

d.then(function (viewer) {
    viewer.setItem(1);
});

Eval breaks Dojo HTML Templates

Removing eval is a breaking change to anyone who relies on it. However, the few areas in Content Navigator that used eval have been removed.

Other breaking changes

Check your code for the use of any of the following items.

In templates, data-dojo-props must contain valid JSON only. For example, the following will not work.

<div data-dojo-props="configId: this.configId"
  data-dojo-attach-point='_container'></div>

The value of this.configId is no longer evaluated. You must replace it with Dojo’s substring replacement. Fortunately, this change resolves automatically.

The parser replaces values within the ${} token replacer.

<div data-dojo-props="configId: ${configId}"
  data-dojo-attach-point='_container'></div>

For more complicated replacements, you might have to remove the values from your template and write the code you want in your constructor or postCreate method.

this._container.configId = this.configId;

Escaping Quotes

As mentioned, templates that use data-dojo-props can no longer use eval to evaluate the string value. These values must now be JSON5 compatible, and the values must be properly escaped.

Example:

<div data-dojo-type="ecm.widget.FilterTextBox"
  data-dojo-attach-point="_filterTextBox"
  data-dojo-props="placeholder: ${messages.name_contains_label}"
  id="${id}_filter"></div>

See the value ${messages.name_contains_label} . In the previous code, no quotes were needed around the value. Now quotes are required.

Note: If you have translations, the translations must be properly escaped, otherwise, the template breaks.

Imagine that the string is "It's an important value!". There is a high likelihood that the template will break. This has always been a potential issue.

It’s best to add the value to the attach point in something like postCreate because the value can be set there safely.

Option A: Somewhere in postCreate, set the value. This is the preferred option.

this._filterTextBox.placeholder = this.messages.name_contains_label;

Option B: Use quotes around the token. This option does not work if there are unescaped quotes in the string.

<div data-dojo-type="ecm.widget.FilterTextBox"
  data-dojo-attach-point="_filterTextBox"
  data-dojo-props="placeholder: '${messages.name_contains_label}'"
  id="${id}_filter"></div>

Remove hard-coded statements

Other instances that must be replaced are inline array strings.

array.some(list, "return item.isValid");

The string is no longer be evaluated. You must use a function instead.

var validator = function (i) { i.isValid };
array.some(list, validator);

Document Location

Worldwide

[{"Type":"MASTER","Line of Business":{"code":"LOB18","label":"Miscellaneous LOB"},"Business Unit":{"code":"BU056","label":"Miscellaneous"},"Product":{"code":"SSEUEX","label":"IBM Content Navigator"},"ARM Category":[{"code":"a8m3p000000LRxwAAG","label":"ICN-\u003ECore-\u003EPlugin"}],"ARM Case Number":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"3.0.15"}]

Document Information

Modified date:
01 March 2024

UID

ibm17030898