Strictest Content-Security-Policy for Unity games

Introducing a Content-Security-Policy on any pre-existing application host is generally a process of trial and error. As Unity projects lay out and load assets in a specific way, we were hoping for an official guide outlining a good setup for strict Content-Security-Policy directives. We found neither an official nor unofficial summary of the situation. The following is our best guess, synthesized from Unity support answers and our own experiments.

The core requirements are to evaluate WebAssembly and scripts from blob: URLs. To avoid allowing inline scripts, the WebGL templates need to be adjusted. This requires a small workaround in Unity versions older than 2020.1.

Theoretical baseline: WebAssembly and blob: URLs

At the time of writing, the strictest theoretical script source directive that will allow Unity games to run is

script-src blob: 'wasm-unsafe-eval' 'self';

which allows blob: URLs and WebAssembly (and assuming the scripts are hosted on the same server as the HTML document, hence 'self').

Practical baseline: Eval and blob: URLs

The wasm-unsafe-eval keyword is part of CSP Level 3 and has only recently landed in some major browsers. An estimated 79 % of users are currently using compatible browsers. Previous versions lump evaluation of WebAssembly in with evaluation of other JavaScript. To support slightly older browser installations, it may therefore be necessary to relax the directive to

script-src blob: 'unsafe-eval' 'self';

Avoiding inline scripts in Unity 2019.4 and older

Unity in version 2019.4 is only able to fill in template variables in an index.html file (see the 2019.4 manual). The manual recommends an inline script to capture and use the BUILD_URL variable. When following this recommendation, 'unsafe-inline' must be allowed as script-src.

To avoid this, we can instead write the build variable’s contents to a data attribute on the game container and read it from an external main.js.

<div id="container" data-build-url="%BUILD_URL"></div>
<!-- ... -->
<script src="%UNITY_WEBGL_LOADER_URL%"></script>
<script src="main.js"></script>
Excerpt from index.html
var container = document.getElementById("container");
var unityInstance = UnityLoader.instantiate(container, container.dataset.buildUrl);
Minimal main.js

Avoiding inline scripts in Unity 2020.1 and newer

According to the manual for version 2020.1 and later, Unity now processes JavaScript files in WebGL templates. It should thus be possible to use the template variables directly in external JavaScript files.


We hope that this little summary will save somebody some research work. We thank Malachi from Unity’s support team for checking some of our hypotheses with their technical team.

Patching Nixpkgs Pins

A while ago we noticed a small misconfiguration in a Nixpkgs file that was causing misleading warnings during system boot. A fix had already made it to Nixpkg’s master, but had not been backported to the release we were using.

As we had three related but distinct build configurations that all needed the fix, we were looking for a way to apply it to all of them uniformly. As they all made use of a shared pin of Nixpkgs already, a simple method proved to be applying a patch to the nixpkgs sources.

The core ingredient is a generic function that takes a source tree and a list of patches to apply to it, producing a patched source tree:

{ name, src, patches }:
    
(import <nixpkgs> {}).runCommand name
  { inherit src patches; }
  ''
    cp -r $src $out
    chmod -R +w $out
    for p in $patches; do
      echo "Applying patch $p";
      patch -d $out -p1 < "$p";
    done
  ''
patch-sources.nix

We need a bootstrap version of Nixpkgs (via the <nixpkgs> search path) purely to be able to use pkgs.runCommand.

Now we simply call this function with our Nixpkgs pin and import the result:

let
  nixpkgs = import ./patch-sources.nix rec {
    name = "nixos-21.11";
    src = builtins.fetchTarball {
      url = "https://github.com/nixos/nixpkgs/archive/${name}.tar.gz";
      sha256 = "04ffwp2gzq0hhz7siskw6qh9ys8ragp7285vi1zh8xjksxn1msc5";
    };
    patches = [ ./CVE-XYZ.patch ];
  };
in
import nixpkgs {};
Applying patch-sources.nix to pinned sources of Nixpkgs

We found this recipe tucked away in the NixOS wiki page for pinning Nixpkgs with pre-2.0 Nix, but think it deserves independent mention.