Search code examples
reactjsspringspring-bootcontent-security-policyprimereact

Problems CSP with Spring Boot and PrimeReact


we have an single-page-application, that uses Spring Boot as backend and React as frontend. We use PrimeReact as our component library for React. We are packaging the React Application as a static part in our Spring Boot Application so that we have only one deployment file and the React Application is served by Spring Boot to the users..

After updating our Node.js-packages PrimeReact injects nonced inline-style at the body element of the resulting pages. The CSP, that is statically created in Spring Boot configuration and added to the HTTP Header bean does not like the nonce and the site looks really unstyled..

Chrome shows this error multiple times:

"Refused to execute inline script because it violates the folowwing Content Security Policy directive: "script src 'self'". Either the 'unsafe-inline' keyword, a has ('sha256-*EXPLICIT-HASH-EVERY-TIME*'), or a nonce ('nonce-...') is required to enable inline execution"

I know, why the browser refuses to use these hashes. But i don't know how to come by with this problem.

Original Packages:

  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.46",
    "@types/react": "^18.2.21",
    "@types/react-dom": "^18.2.7",
    "chart.js": "^4.4.0",
    "chartjs-plugin-datalabels": "^2.2.0",
    "http-proxy-middleware": "^2.0.6",
    "primeicons": "^6.0.1",
    "primereact": "^9.6.2",
    "quill": "^1.3.7",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.15.0",
    "react-scripts": "5.0.1",
    "react-transition-group": "^4.4.5",
    "typescript": "^4.9.5",
    "web-vitals": "^2.1.4"
  }, 

Updated Packages:

  "dependencies": {
    "@testing-library/jest-dom": "^6.5.0",
    "@testing-library/react": "^16.0.1",
    "@testing-library/user-event": "^14.5.2",
    "@types/jest": "^29.5.12",
    "@types/node": "^22.5.4",
    "@types/react": "^18.3.5",
    "@types/react-dom": "^18.3.0",
    "chart.js": "^4.4.4",
    "chartjs-plugin-datalabels": "^2.2.0",
    "http-proxy-middleware": "^3.0.2",
    "primeicons": "^7.0.0",
    "primereact": "^10.8.2",
    "quill": "^2.0.2",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.26.1",
    "react-scripts": "^5.0.1",
    "react-transition-group": "^4.4.5",
    "typescript": "^4.9.5",
    "web-vitals": "^4.2.3"
  },

PrimeReact throws exception at this place (hooks.esm.js) while injecting styles at the HTML-body element:

  var load = function load() {
    if (!document || isLoaded) {
      return;
    }
    var styleContainer = (context === null || context === void 0 ? void 0 : context.styleContainer) || document.head;
    styleRef.current = getCurrentStyleRef(styleContainer);
    if (!styleRef.current.isConnected) {
      styleRef.current.type = 'text/css';
      if (id) {
        styleRef.current.id = id;
      }
      if (media) {
        styleRef.current.media = media;
      }
      DomHandler.addNonce(styleRef.current, context && context.nonce || PrimeReact.nonce);
      styleContainer.appendChild(styleRef.current);
      if (name) {
        styleRef.current.setAttribute('data-primereact-style-id', name);
      }
    }
    styleRef.current.textContent = css;
    setIsLoaded(true);
  };

The above code was introduced with the new version of PrimeReact, in PrimeReact 9.6.2 this code was not there. I have not found a clue why this may happen now and why.

CSP Generation in Spring Boot:

contentSecurityPolicyConfig -> contentSecurityPolicyConfig.policyDirectives("default-src 'self'; img-src 'self' data:;")

Current unsafe workaround we are using:

contentSecurityPolicyConfig -> contentSecurityPolicyConfig.policyDirectives("default-src 'self' 'unsafe-inline'; img-src 'self' data:;")

I have some questions about this:

  • Why does PrimeReact uses inline-styles now? Aren't they a bad thing?
  • Is it possible to prevent PrimeReact on using such inline-styles (and not going back to the old version)? To me it might be related to some configuration mistake that i am not aware of.
  • I have seen, that there is an option in Spring Boot to generate nonces and use them in a dynamically created CSP. Additionally PrimeReact holds it's nonces centrally. But i can't imagine a way to make these 2 interoperable if the javascript files that create the nonce are statically served by the spring boot tomcat. There is no way to make this work that way, true?
  • Might it be necessary to use a CSP that is included in the HTML? Is it necessary to replace the HTTP Header CSP with an HTML CSP all together to make it work?

Any answer hinting me in the right (and may be best) direction is appreciated. Thanks in advance.


Solution

  • We found a solution:

    We are now using a in-HTML CSP. In React we generate a nonce for the current HTML page and pass it to PrimeReact. At a first glance this looked a little bit unsafe: Transfer the content to the client and let the client build the csp but in fact it is only one request that is made for the page and it seals the content so that no other content can be loaded that is not covered by the csp.

    const array = new Uint32Array(1);
    const nonce = crypto.getRandomValues(array)[0].toString();
    
    function initCSP() {
    
    let csp = "default-src 'self'; img-src 'self' data:; style-src 'self' 'nonce-{$nonce$}'".replace("{$nonce$}", nonce);
    var meta = document.createElement('meta');
    meta.httpEquiv = "Content-Security-Policy";
    meta.content = csp;
    
    let updated = false;
    
    for (const element of document.getElementsByTagName('head')[0].children) {
        if (element.getAttribute("http-equiv") === "Content-Security-Policy") {
            element.setAttribute("content", csp);
            updated = true;
        }
    }
    
    if (!updated) {
        document.getElementsByTagName('head')[0].insertBefore(meta, document.getElementsByTagName('head')[0].firstChild)
    }
    
    return nonce;
    }
    

    --

    const config = {
        nonce: nonce
    }
    

    --

    <PrimeReactProvider value={config}>
    ...
    </PrimeReactProvider>
    

    Some of the code is not yet optimal, but i think you might get the idea.