Implementing CSP with Nonce for Inline scripts in AEM: A Step-by-Step Guide

Saravana Prakash
3 min readJun 11, 2024

--

Photo by Collin on Unsplash

Is my AEM site CSP certified?

Prelude

One of Non-Functional Requirements (NFR) is to comply with CSP Version3

There are chrome extensions and tools to evaluate CSP compliance

And articles to understand this requirement https://support.securityscorecard.com/hc/en-us/articles/6223292850587-Content-security-policy-contains-broad-directives

Idea is simple, only 2 changes:

  1. The Response Header must include Content-Security-Policy with a value script-src 'nonce-<uniquevalue>
  2. ALL <script> tags loaded on page must include a nonce attribute with same value as header.

Response Header

Page Source

This way browser asserts if attribute value matches to header, and then accepts the clientlib. If any malicious code, attempts to run custom js, the nonce value mismatches and browser blocks.

How to Implement

3 changes:

  1. Update dispatcher Response header to include the `Content-Security-Policy`
  2. Create new Transformer Pipeline that ll include the same nonce into <script> tags
  3. Config to Register the pipeline into transformer factory

Dispatcher Change

From the site.vhost file, under root Directory tag, add below lines

<VirtualHost *:80>
TraceEnable off
<Directory />
# CSP Policy
Header set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'; script-src 'nonce-unique' 'strict-dynamic' 'self';"
</Directory>
</VirtualHost>

Transformer Pipeline

Create a new LinkTransformer that checks <script> tag and adds same nonce-unique into the script tags.

package com.app.corp.core.filter;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.rewriter.*;
import org.osgi.service.component.annotations.Component;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
* This filter will add a nonce=unique attribute to ALL <script> tags. At
* dispatcher, response header adds a Content-Security-Policy script-src
* 'nonce-unique'. Now browser will allow only <script> tags bearing the
* correct nonce. This will prevent XSS injections.
*/
@Component(immediate = true, service = TransformerFactory.class, property = { "pipeline.type=nonce-injestor" })
public class NonceTransformer implements TransformerFactory {

@Override
public final Transformer createTransformer() {
return new ClientlibsTransformer();
}

public class ClientlibsTransformer extends DefaultTransformer {
@Override
public void init(ProcessingContext context, ProcessingComponentConfiguration config) {
// do nothing. housekeeping function
}

@Override
public void startElement(final String nsUri, final String localname, final String qName, final Attributes attrs)
throws SAXException {
final AttributesImpl newAttributes = new AttributesImpl(attrs);
if (StringUtils.equalsIgnoreCase(localname, "script")) {
newAttributes.addAttribute("", "nonce", "nonce", "CDATA", "unique");
}
super.startElement(nsUri, localname, qName, newAttributes);
}
}
}

Register the pipeline

Under /apps/myapp/config/rewriter/myapp, include the new transformer

Test the changes

From local, hit ctrl+U to view source and validate if all script tags are adding the nonce=unique tag. If local dispatcher is running validate the page response headers include the same CSP with nonce tag.

After deploying to lower environment, test from 3rd party tools like cspvalidator.org and make sure the site is CSP compliant.

Congratulations! you made your security audit happy. Cheers!!

--

--

Saravana Prakash

AEM Fullstack Enthusiast. Working on AEMCaaS, Adobe EDS, Adobe IO and other Adobe Marketing Cloud tools