Content-Security-Policy Compatibility

Introduction

Content-Security-Policy is the name of a HTTP response header that modern browsers use to enhance the security of the document (or web page). The Content-Security-Policy header allows you to restrict which resources (such as JavaScript, CSS, Images, etc.) can be loaded, and the URLs that they can be loaded from.

Using Content-Security-Policy (CSP), injection attacks like cross-site scripting (XSS) can be prevented.

As of 2025, the Jenkins (core) UI is compatible with the CSP directives that would allow preventing such injection attacks, but many plugins are not. This guide documents how to identify components that will be incompatible with CSP rules and how to write and adapt UI code in a manner that is compatible with Jenkins enforcing CSP protections on its UI.

Statically Identifying Affected Code and Plugins

To check plugins for CSP compatibility, look for the following code patterns:

Inline <script> blocks

Referencing a file to load JavaScript from is fine, but inline block contents are not. All <script> tags without src are a problem. They will break without 'unsafe-inline' in the script-src CSP directive.

Standalone st:bind tags with disallowed var attribute

Only simple JavaScript identifiers are allowed for CSP-compliant <st:bind> tags.

Inline event handler definitions

Any occurrence of onclick, onload, etc. in HTML files (e.g. Jelly and Groovy views) is a problem.

Legacy checkUrl definitions

Check the checkUrl attribute for values that are not simple URLs, but JavaScript expressions. While the underlying support is implemented in Jenkins core rather than in plugins, this will break without 'unsafe-eval' in the script-src CSP directive. You can easily identify them through their use of single quotes inside the double-quoted attribute value (or, rarely, vice versa).

Use of eval

Check for the use of eval inside any JS resource files (potentially after applying fixes for the previously listed issues). This will break without 'unsafe-eval' in the script-src CSP directive.

Use of FormApply#applyResponse

This method expects as the argument a JavaScript snippet to be executed.

Resources loaded from other domains

Any images, scripts, styles, etc. loaded from other domains cause problems. Resources should be hosted by Jenkins.

Dynamically Identifying Plugins

When running Jenkins, you can use the following techniques to identify broken features and the component that defines them:

Built-in CSP protection in Jenkins 2.TODO+

Running Jenkins 2.TODO or newer in development mode (e.g., mvn hpi:run) will have Content Security Policy protections enabled by default. Learn more.

Content Security Policy Plugin 1.x

In Jenkins before version 2.TODO, Content Security Policy Plugin (1.x) lets you define a Content-Security-Policy that gets applied to the Jenkins web UI. It can operate both as enforcing and to only gather reports. Both modes can be useful with identifying broken functionality.

Report Only

When set to only report issues but not enforce, the UI can be used as usual while letting the browser report violations, allowing to quickly gather a number of findings while not being blocked by broken functionality. Afterwards, the reported violations can be reviewed and affected functionality identified this way. Navigate to Manage Jenkins » Content-Security-Policy Report and review the violations there.

Make sure to review your browser’s console for errors related to reporting CSP violations if you experience problems on the UI, but the report is empty. For example, some browser extensions will disable CSP reporting.

Enforcing

When set to enforce the rules, the UI will more readily break if functionality being accessed is impacted by the rules.

Enabling Jelly tracing

Run the following script in the script console:

org.kohsuke.stapler.jelly.JellyFacet.TRACE = true

This property is documented on Views and emits comments in the rendered HTML of Jelly views that allow you to better understand how the views are composed, and potentially more easily identify which component is responsible for contributing code that violates the CSP rules.

Development Guidelines

Inline JavaScript blocks

General advice

Do not use inline JavaScript (JS) in the Jenkins GUI, i.e., JS embedded in HTML output.

This is typically done with <script> tags, like so:

<script type="text/javascript">
alert("Hello, world!");
</script>

The guidelines in the documentation on XSS prevention can be useful to pass arguments to JavaScript, or otherwise control its behavior dynamically.

You can generally use Stapler adjuncts to load files related to UI views and ensure they are loaded only once.

An example of this is jenkinsci/jenkins#6849.

Inline st:bind tags

If you’re using <st:bind> tags inside inline JavaScript, add the var attribute to set the variable with the specified name, and reference that variable in your own scripts. Use a simple JavaScript identifier (complying with this regular expression) as var value, rather than a more complex expression.

Make sure to not reuse the same var value when extracting multiple or repeatedly executed <st:bind/> tags from inline JS. You can use ${h.generateId()} to generate a unique value.

An example of this is warnings-ng-plugin#1862.

Standalone st:bind tags with disallowed var attribute

<st:bind> generates different output depending on how it is invoked. See the previous section for advice on adding the var attribute for standalone use, rather than inside inline JS.

In addition to adding the var attribute, its value must comply with this regular expression to generate a CSP-compatible <script> tag.

See build-monitor-plugin#830 for an example.

Inline event handlers

Event handlers like onclick or onblur should be defined in separate files.

For this to work, the element that would have had the inline event handler attribute(s) needs a class or ID by which it can be looked up from JS.

Depending on how that element is added to the UI, you’d use one of the following methods to add event handlers:

You can use document.addEventListener('DOMContentLoaded', …) for one or more elements that are present on the page from the moment it is loaded. Look up the elements by their ID or class or similar characteristics, then call #addEventListener on them. Be mindful of Jenkins’s extensibility, so consider including plugin names in element class names or IDs to prevent unintentional conflicts with other plugins.

Use Behaviour#specify to add event handlers to elements that may be dynamically added to the page, for example as part of AJAX responses. One common instance of this is in configuration forms: renderOnDemand is used by common form elements like hetero-list to load parts of the page only as the form is being changed. The code that adds content from AJAX responses dynamically to the page needs to call Behaviour#applySubtree on the newly added content.

For event handlers like onclick that used to call return false to prevent the usual action (e.g. link navigation) from happening, add a call to Event.preventDefault() in a separate event handler on the provided Event argument.

Examples of this are: jenkinsci/jenkins#5514

Legacy JavaScript checkUrl validation

Do not use "legacy" mode form validation, which supports inline JS with manually specified checkUrl parameters. It looks like the following:

<f:textbox checkUrl="'${rootURL}/${h.jsStringEscape(it.url)}checkText?value='+encodeURIComponent(this.value)+'"  />

This combines inline JS and building parts of the string using JEXL expressions in Jelly, with different ways to escape different parts of the content to prevent injection vulnerabilities.

Instead, use the modern checkUrl mode, which as of Jenkins 2.360 requires the checkDependsOn attribute to be set (but it can be an empty string). This mode will automatically add the current form element’s value as the query parameter called value, so the above example can be simplified to the following:

<f:textbox checkUrl="${rootURL}/${it.url}checkText" checkDependsOn=""  />

To pass additional values, specify the respective form field names as part of the checkDependsOn string.

If you need to pass parameters that are not represented as form fields, the following options exist as of Jenkins 2.360:

  • Define a new form validation endpoint. This can be a viable option when it’s a boolean value (2 endpoints instead of one).

  • Define a hidden form field (wrap it in f:invisibleEntry) with the expected name and value and specify it in checkDependsOn. Make sure to ignore it otherwise. See jenkinsci/jenkins#6859 for an example.

eval calls

eval should not be used to interpret a string as JS code.

Depending on your use case, different solutions are possible.

To parse JSON, use JSON.parse instead. See jenkinsci/jenkins#6868 for an example.

To invoke a callback, have the caller define a global function and pass its name as an argument. Then your code can invoke the callback like this:

/* someone else provides this */
let callbackName = 'foo';
/* invoke it with arguments */
window[callbackName](args);

FormApply#applyResponse calls

Use FormApply#showNotification, which was added in Jenkins 2.482.

Resources loaded from other domains

Move images, scripts, and styles into the resources of your plugin. This will also make Jenkins work better in environments without, or only limited, internet connectivity.

Any dynamically determined images (e.g., "avatar" images based on user configuration, like GitHub orgs or user avatars based on the configured security realm) can be handled in the following ways:

  • Have Jenkins request (and possibly cache) these images, serving them through a local URL. Be careful to not allow parameterization of the URL serving this image such that it accepts arbitrary parameter values, resulting in arbitrary URLs being proxied.

  • For compatibility with Content Security Policy in Jenkins 2.TODO and newer, implement jenkins.security.csp.Contributor (or jenkins.security.csp.SimpleContributor in simple cases). This will allow Jenkins users' browsers to load images from a known safe domain. In this case, make sure that only administrators can ultimately configure the domains that images can be loaded from. For example, regular Jenkins users should not be able to, e.g., edit their user profile or configure a job in a certain way to allow a domain of their choice.

Testing

In Jenkins 2.TODO and newer

Jenkins 2.TODO and newer supports Content Security Policy out of the box. See the documentation for information how to set it up.

Running Jenkins in development mode will by default enforce Content Security Policy, so plugin maintainers will likely encounter incompatibilities in their own testing.

In older releases of Jenkins

Content Security Policy Plugin (1.x) lets you define a Content-Security-Policy that gets applied to the Jenkins web UI. It can operate both as enforcing and to only gather reports. Both modes can be useful with identifying broken functionality.