Securing Web Services

Configuring Content Security Policy Headers

Modern browsers use Content Security Policy HTTP response headers to enhance web pages’ security to mitigate certain types of attacks (like Cross-Site Scripting or data injection). You can enable CSP on Liferay to send the configured headers on each HTTP request.

CSP policies have many options. It is best to understand your users’ needs and design a policy to suit their requirements. After you enable CSP headers, browsers enforce them. Visit the links below to learn more about CSP:

Warning

Configuring an unsupported content security policy can cause your instance to malfunction.

Content Security Policy Directives and Values

Content Security Policies take the form of key-value pairs. The keys are directives, and they can have values. Using directives and values, you can form a robust policy to protect web applications against a variety of threats.

Directives: Directives specify the resources (scripts, stylesheets, images, etc.) a browser can load or execute on a web page. Below are examples of some commonly used directives, and the most restrictive values supported by Liferay.

DirectiveMinimum Supported ValueDescription
default-srcN/ASpecifies the default source for content types not explicitly defined by other directives.
base-urinone or domain.comSpecifies the base URL for resolving relative URLs. Set the value to your domain’s base URL (e.g., domain.com).
connect-srcselfSpecifies the sources to which you can make network requests.
frame-ancestorsnoneSpecifies which parent pages can embed resources in frames, IFrames, or other embed elements.
frame-srcselfSpecifies the domains from where you can embed pages or applications into a frame or IFrame.
img-srcselfSpecifies the sources from where you can load images.
object-srcnoneSpecifies the valid sources for object and embed HTML elements (which are legacy elements).
script-srcself unsafe-inlineSpecifies the sources from where you can load and execute JavaScript.
script-src-attrnoneSpecifies the sources from where you can load inline script event handlers like onClick.
style-srcself unsafe-inlineSpecifies the sources from where you can load stylesheets. Acts as the default for style-src-attr and style-src-elem if they’re not defined.
style-src-attrunsafe-inlineSpecifies the valid sources for inline styles applied to DOM elements in your stylesheets.
style-src-elemself unsafe-inlineSpecifies the valid sources for style elements.

Values: Values specify the approved origins to load resources from. Below are examples of some commonly used values. In addition to these, you can also specify URLs.

ValueDescription
selfAllows loading and execution of resources from the same origin.
unsafe-evalAllows execution of code generated by eval() and other unsafe methods.
unsafe-inlineAllows the inclusion of inline stylesheets, scripts, inline event handlers, etc.
[$NONCE$]Allows execution of scripts or loading of stylesheets with a specific nonce value generated by the server.
strict-dynamicAllows execution of scripts from trusted sources. Scripts included by loaders from a trusted script are not blocked.
noneBlocks execution of the specified directive from all sources.

It’s important to note that while unsafe-eval and unsafe-inline provide flexibility in terms of code execution and styling, they come with increased security risks.

In general, you should set your CSP headers to be as restrictive as your required sources allow to minimize the risk of possible security threats. However, Liferay currently cannot support a fully unsafe- free CSP due to existing limitations. See Current Limitations of CSP for more information.

Warning

While strict-dynamic simplifies CSP configuration and reduces the need for nonce propagation, it may enlarge the attack surface by allowing any script loaded by trusted scripts to execute.

Alternatively, you can send nonces from the server to the client, ensuring that dynamically loaded scripts have the necessary nonce attribute for compliance with CSP. This approach involves proprietary mechanisms, managed by Liferay, to propagate nonces from the server to the client.

Nonce propagation provides more granular control over script loading and may be preferable in scenarios where strict control over script execution is essential. However, its proprietary mechanisms may be more complex.

The choice between strict-dynamic and nonce propagation depends on factors such as the specific threat model, risk tolerance, and operational requirements. Consider the pros and cons before implementing it in your CSP.

Configuring a Content Security Policy

  1. Open the Global Menu (Global Menu) and navigate to Control Panel → Instance Settings → Security → Content Security Policy.

    Configuring a content security policy.

  2. To enable CSP headers, check the Enabled checkbox. You can enter your policy in the Content Security Policy input field and specify many paths to exclude (see below).

  3. Enter the following policy in the Content Security Policy input field.

    default-src 'self'; script-src 'self' https://trusted-cdn.example.com '[$NONCE$]'; style-src 'self' https://trusted-cdn.example.com '[$NONCE$]' base-uri 'self';
    

This sample content security policy allows resources to load only from the same origin self and from the trusted content delivery network https://trusted-cdn.example.com. It also includes the nonce '[$NONCE$]' for scripts and stylesheets to secure their integrity and prevent unauthorized script execution. The base-url 'self' value instructs the browser to allow loading resources only from the same origin as the document.

  1. Click Update.

Content Security Policy: Specify the content security policy to enforce. The value entered here appears as the value for the Content-Security-Policy HTTP header, or for the Content-Security-Policy-Report-Only header while Report Only is enabled. You can add a nonce by using the placeholder '[$NONCE$]' in your content security policy. The placeholder is replaced with a generated nonce for the tags you specify.

Report Only: Enabled by default. While enabled, Liferay sends the Content-Security-Policy-Report-Only header, so browsers report violations in the developer console without blocking anything. Clear it to enforce the policy.

Excluded Paths: Paths that start with the value added here are excluded from the content security policy. This setting controls only whether the Content-Security-Policy header is set on the requested path itself; it does not exempt sub-resources (such as scripts, styles, or images) that other pages load from an excluded path. You can add multiple paths to exclude by clicking the Add Setting icon button or remove them using the Remove Setting icon button.

Note

CSP configuration is available at all scopes. For the system scope, go to Control Panel → System Settings → Security → Content Security Policy. For the site scope, open the site menu and go to Configuration → Site Settings → Security → Content Security Policy.

Current CSP Limitations and Recommendations

CSP headers have these limitations:

  • If you use CKEditor 4 by enabling the LPD-11235 deprecation feature flag, your script-src directive must include the unsafe-inline value.

  • CKEditor 5 is the default editor and does not require the unsafe-inline directive for script-src.

  • Your style-src directive requires the unsafe-inline value because Liferay uses inline style attributes and dynamically injects stylesheets.

  • Even with CKEditor 5, some plugins (for example, table styling features) can generate inline styles. In these cases, style-src 'unsafe-inline' may still be required if editors use those features.

Due to platform limitations, you cannot restrict script-src and style-src to only self.

You can split your style-src directive into style-src-attr and style-src-elem to apply more restrictive policies to style attributes and style elements. Configure style-src-elem with a nonce source for required inline <style> elements.

Tip

In JSPs, use aui: tags instead of HTML tags so Liferay can add the nonce that the script-src and style-src directives require to your markup. A nonce does not satisfy script-src-attr or style-src-attr, which govern inline event handlers and style attributes. The aui: tags come from a JSP taglib, so FreeMarker templates use a different mechanism. See Adding a Nonce in FreeMarker Templates.

Using specific nonce values for your styles, you can configure more restrictive directives:

style-src-attr 'unsafe-inline';
style-src-elem '[$NONCE$]';

For the domain my-relative-domain.com, the most restrictive supported policy is:

base-uri 'my-relative-domain.com';
script-src '[$NONCE$]';
script-src-attr 'none';
style-src-elem '[$NONCE$]';
style-src-attr 'unsafe-inline';
connect-src 'self';
img-src 'self';
object-src 'none';
frame-ancestors 'none';
frame-src 'self';

Use this configuration where possible. If a page does not work (for example, because it uses CKEditor 4), add the page under Excluded Paths.

Use the Excluded Paths repeatable field to exclude any pages that don't work with your policy.

Extra CSP Limitations Before Liferay 2025.Q1

These extra limitations are in place for Liferay versions before 2025.Q1:

  • Your script-src directive must have the unsafe-eval value defined for Liferay to properly bundle code.

  • Your script-src-attr directive must have the unsafe-inline value for inline event listeners within HTML elements within Liferay.

Adding a Nonce in FreeMarker Templates

Page fragments, web content templates, widget templates, and information templates use FreeMarker rather than JSP, so aui: tags are unavailable in them. Instead, Liferay adds the ${nonceAttribute} variable to every template it renders for a request. Add the nonce to an inline <script> or <style> element with that variable:

<script ${nonceAttribute}>...</script>

<style ${nonceAttribute}>...</style>

The variable resolves to a complete attribute, including its name: nonce="rAnd0mVa1u3==". Do not surround it with quotation marks or assign it to a nonce attribute yourself.

Adding the nonce matters only when CSP is enabled with a policy that uses '[$NONCE$]'. CSP is disabled by default, and Report Only is enabled by default. While Report Only is enabled, browsers report violations without blocking anything, so the blocking described in the next paragraph applies once you clear Report Only. While CSP is disabled, the variable resolves to an empty string and the same markup is valid on an instance with or without CSP.

When the policy sets a nonce on script-src or style-src, an inline <script> or <style> element written without the variable receives no nonce, and the browser blocks it. Writing nonce='[$NONCE$]' in your markup does not help: Liferay substitutes that placeholder only in the CSP policy, not in the markup you author.

For the variable to work in a page fragment:

  • Keep FreeMarker enabled for fragments. It is enabled by default; if ${nonceAttribute} reaches the browser as literal text, check whether it’s disabled in your instance. Go to Control Panel → Instance Settings → Page Fragments → FreeMarker Fragment Entry Processor → Enable FreeMarker.

  • Do not mark a fragment cacheable when its HTML contains ${nonceAttribute}. Liferay stores the rendered HTML, nonce included, in a cache shared by every visitor, so the fragment keeps the nonce belonging to whoever loaded the page first until the cache entry is invalidated. That visitor sees the element run normally, and the browser blocks it for every session that follows. Test this with a second browser session rather than by reloading the page.

Caching does not carry this risk in web content templates. Liferay replaces the resolved nonce with a placeholder before it caches an article’s rendered content and substitutes the current request’s nonce each time it serves the article from that cache.

A fragment’s CSS and JavaScript panes need no variable because Liferay generates the <style> and <script> elements that wrap their contents and adds a fresh nonce to each one on every request. The JavaScript pane gets its nonce only when the JavaScript module is enabled by Control Panel → Instance Settings → Page Fragments → Javascript → Javascript Module Enabled, which is the default. When disabled, the generated wrapper does not include a nonce.

Feature Availability

Liferay VersionFeature StatusFeature Flag Label
Liferay DXP 2024.Q1/Portal GA112BetaContent Security Policy (CSP) (LPS-134060)
Liferay DXP 2025.Q2ReleaseContent Security Policy (CSP) (LPS-134060)
Liferay DXP 2025.Q4GA-