Docs

Documentation versions (currently viewingVaadin 24)

Content Security Policy

How to enable strict Content Security Policy.

Content Security Policy (CSP) is a browser standard that helps to mitigate certain types of attacks. Strict CSP allows the application to define rules for how JavaScript can be loaded and run in the browser. This provides an additional layer of security against Cross-Site Scripting (XSS), data injection, data theft, site defacement and malware distribution.

Vaadin & Strict CSP

Vaadin Flow isn’t generally compatible with strict CSP rules. This is due to how the client-server communication has been built. For example, dynamic JavaScript functions and eval calls are used in Vaadin. These aren’t compatible with strict CSP rules.

However, with some effort, nonce-based strict CSP can be used with Vaadin Flow applications. Nonce is a random identifier which is generated by the server for each HTTP request. It needs to be included in the response headers. Once this is done, only script tags containing this nonce value are loaded by the browser — while dynamic functions and calling eval are not allowed.

Strict CSP implemented this way is only available when the Vaadin application is running in production mode.

Creating Random Nonce

To create the random nonce value, the application needs to have an IndexHtmlRequestListener, which must generate and add the nonce to the index file response and all script tags, like this for example:

String nonce = UUID.randomUUID().toString();

// Add a header to make the browser require the nonce in all script tags
response.getVaadinResponse().setHeader("Content-Security-Policy",
        "script-src 'nonce-" + nonce + "'");

// Add the nonce to all script tags in the host page
response.getDocument().getElementsByTag("script").attr("nonce", nonce);

Function Execution & Eval Calls

The example in the above section only adds the nonce to the application. It doesn’t help with handling all of the dynamic functions, eval calls, and chunk loading. An application that needs to use strict CSP must provide a JavaScript file for handling these issues in a CSP-compliant way.

The Function constructor and eval must be overridden. Also, the application developer needs to provide equivalent pre-defined JavaScript functions for any calls made by the application. A snippet of this is shown below as an example:

// Override the Function constructor with a version that uses inlined code if available
const originalFunction = window.Function;
window.Function = function(...args) {
  const key = args.join(",");

  if (functions.has(key)) {
    return functions.get(key);
  }

  if (key.startsWith("return window.Vaadin.Flow.loadOnDemand(")) {
    const chunk = key.split("return window.Vaadin.Flow.loadOnDemand('").pop().split("');").pop();
    return function (chunk) {return window.Vaadin.Flow.loadOnDemand(chunk);};
  }

  // Expression was not found in functions map, and was not chunk-loading call, so log it to console
  const code = args[args.length - 1];
  // Ignore the stats gatherer which isn't used in production mode
  if (code.indexOf("var StatisticsGatherer") == -1) {
    const snippet = `functions.set("${key}",\n  function(${args.slice(0, -1).join(',')}) {${code}});`
    console.warn(snippet);
  }

  // Fall back to the original constructor.
  // This will fail in strict CSP mode, but this way the browser will report an error.
  return originalFunction.apply(null, args)
}

// Override eval(..) with a version that uses inlined code if available
const originalEval = window.eval;
window.eval = function(script) {
  const originalArg = script;
  // Removes parenthesis used by Vaadin Charts
  if (script.length > 1 && script.substring(0, 1) === "(" && script.substring(script.length - 1) === ")") {
    script = script.substring(1, script.length - 1);
  }
  if (evalCalls.has(script)) {
    return evalCalls.get(script);
  } else {
    let snippet = "";
    if (script.startsWith("function")) {
      snippet = `evalCalls.set("${script}",\n  ${script});`
    } else {
      snippet = `evalCalls.set("${script}",\n  function() {return ${script}});`
    }
    console.warn(snippet);

    // Fall back to the original eval.
    // This will fail in strict CSP mode, but this way the browser will report an error.
    originalEval.apply(null, originalArg);
  }
}

In the example here, functions and evalCalls provide mapping from string to a predefined function. The next snippet shows a couple of examples only where a real application would have many more of these mappings:

const evalCalls = new Map();
evalCalls.set("'The value for <b>' + this.x + '</b> is <b>' + this.y + '</b>'",
    function() {return 'The value for <b>' + this.x + '</b> is <b>' + this.y + '</b>'});

const functions = new Map();
functions.set("$0,$1,return $0.$connector.confirm($1)",
    function($0,$1) {return $0.$connector.confirm($1)});

An example application using strict CSP is available on GitHub. Files containing the required changes are Application.java and csp.js.

B5E8EEC6-0F7E-49C5-8121-0906827BE4A5