Fixing Blue Ocean CSP Error After Plugin Upgrade

After upgrading the Blue Ocean plugin, Jenkins started throwing CSP errors in the browser. I walked through the investigation, tried a few fixes that didn't work, and landed on a simple solution.

Fixing Blue Ocean CSP Error After Plugin Upgrade
Photo by Viktor Forgacs / Unsplash

The problem

I upgraded the Blue Ocean plugin on our Jenkins instance from version 1.27.25 to 1.27.26. The old version worked fine, but Blue Ocean is deprecated. I wanted to grab the latest version while it's still available. After the restart, the Blue Ocean UI was broken. The browser console was full of CSP errors.

This is the error I saw:

EvalError: Evaluating a string as JavaScript violates the following
Content Security Policy directive because 'unsafe-eval' is not an
allowed source of script: script-src 'report-sample' 'self'".

    at new Function (<anonymous>)
    at optimizeLookup (blueocean-core-js.js:48503:13)

The old version (1.27.25) never had this problem. Something changed between 1.27.25 and 1.27.26 in the bundled JavaScript. The Blue Ocean JavaScript bundle was failing to initialize. The entire plugin was dead.


The environment

Our Jenkins runs inside a Docker container on a Tencent Cloud CVM, behind an nginx reverse proxy. The stack looks like this:

  • Jenkins 2.568.2 LTS (official Docker image)
  • Blue Ocean plugin (latest version, just upgraded)
  • CSP enabled via the Jenkins web UI checkbox
  • No custom CSP headers in nginx

The Jenkins instance is internal, only accessible by the team. It's not public-facing.

What I tried first

The error message was pretty clear. The CSP script-src directive was blocking unsafe-eval. The Blue Ocean JavaScript bundle uses new Function() in its optimizeLookup function, and the browser treats that as eval.

My first thought was to override the CSP by adding the hudson.model.DirectoryBrowserSupport.CSP system property. I added this to the JAVA_OPTS in the Docker Compose file:

JAVA_OPTS: "-Xms512m -Xmx4096m -Dhudson.model.DirectoryBrowserSupport.CSP=\"sandbox allow-scripts; default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';\""

I redeployed the container. Checked that the environment variable was set correctly with docker inspect:

JAVA_OPTS=-Xms512m -Xmx4096m -Dhudson.model.DirectoryBrowserSupport.CSP="sandbox allow-scripts; default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"

It was there. But the error still showed the default CSP: script-src 'report-sample' 'self'. My override was being ignored.

What confused me

At this point I was confused. The system property was set. The container had it. But Jenkins was still using the default CSP.

Then I remembered: I had enabled CSP via the Jenkins web UI. In Manage Jenkins > Configure Global Security, there's a checkbox for "Content Security Policy". I had it checked.

Here's the thing I didn't know: when you enable CSP via the web UI checkbox, Jenkins uses its own default CSP value. It does not give you a text field to customize the value. The checkbox just toggles between "default CSP on" and "CSP off". There's no way to add unsafe-eval through the UI.

And the web UI setting takes precedence over the system property. So even though JAVA_OPTS had the correct value, Jenkins ignored it.

The resource root URL dead end

The Jenkins system settings page showed a warning:

The default Content-Security-Policy is currently overridden using the
hudson.model.DirectoryBrowserSupport.CSP system property, which is a
potential security issue when browsing untrusted files. As an alternative,
you can set up a resource root URL that Jenkins will use to serve some
static files without adding Content-Security-Policy headers.

I tried setting up a Resource Root URL. The idea is that Jenkins serves Blue Ocean's static files from a separate URL without CSP headers, while the main Jenkins UI keeps its CSP protection.

But Jenkins rejected it. The resource root URL must be on a different hostname than the Jenkins URL. I tried <jenkins-hostname>/resourceRoot and it said:

Cannot use the same host name for both Jenkins URL and resource root URL.

Setting up a separate subdomain would mean creating a new DNS record, updating the SSL certificate, and adding nginx config. Too much work for a plugin upgrade.


The actual fix

I went back to the CSP checkbox in the web UI and just disabled it. That's it. Blue Ocean started working immediately.

The fix is simple: go to Manage Jenkins > Configure Global Security, find the Content Security Policy checkbox, and uncheck it.

Is this ideal? No. Disabling CSP removes a security layer. CSP helps prevent XSS attacks by restricting what scripts the browser can execute. For now, the trade-off is acceptable. We're planning to add Cloudflare Zone Lockdown to restrict access to the Jenkins instance, which will reduce the risk of XSS attacks. But until that's in place, there's a small gap.