# 'safe-eval' with Worker Before we get started… *Prefer `script-src-elem` over `script-src` for better cross-browser support of this solution.* ## Update, May 13, 2022 I have a variation on this solution using *embedded* workers with blob URLs at https://gist.github.com/dfkaye/14e5cc5dbe5bb38a9d80f25f54061c7f. ## The problem You want to do this: ``` Function(`return ${expr}`) ``` But your `Content-Security-Policy` header prevents the main thread from running `eval()` or `Function()` by default; for example: ``` <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'strict-dynamic' 'nonce-4AEemGb0xJptoIGFP3Nd';" > ``` Notably, the `script-src` policy does *not* contain 'unsafe-eval' — in effect, your site does not trust *any* code, whether yours or a third party's, to evaluate code safely. ## The solution 1. Create a web worker (we'll define its contents in a moment) and subscribe to its `message` event: ``` var worker = new Worker("./worker.js"); worker.addEventListener('message', function(e) { console.info('worker completed'); // event.data contains the stringified JSON response var data = JSON.parse(e.data); // do things in the main thread using data... console.log(JSON.stringify(data, null, 2)); }); ``` 2. Define the functionality you want inside the 'worker.js' file. The following pretends to assign a new value in `data`, specifically, `data[cellid] = value`, and then return the modified `data`, `id`, and `value`. ``` function evaluate(e) { console.info('worker evaluating'); console.log(e.data); var data = JSON.parse(e.data.data || '{}'); var item = JSON.parse(e.data.item || '{}'); var id = item.id; var value = item.value; // Replace cellId (A1, PP12, etc.) with data[cellid] value. var expr = value.substring(1).trim().replace(/[A-Z]+[\d]+/g, function(cellid) { var item = data[cellid] || { value: '' }; var test = Number(item.value); // Quote the value if it can't be coerced to a Number. return test !== test ? '"' + item.value + '"' : (item.value || 0); }); var computedValue = value; try { computedValue = Function(`return ${expr};`).call(null); } catch (e) { console.error({ error: "Error evaluating " + value, expr, computedValue }); } self.postMessage(JSON.stringify({ id: id, value: computedValue })); } ``` 3. Subscribe to the message event inside the worker ``` self.addEventListener('message', evaluate); ``` 4. Back in the main thread, create a function that calls `worker.postMesage()` — we'll assume the function uses fields from the `data` and `item` arguments, then makes that call: ``` function useWorker(data, item) { worker.postMessage({ data: JSON.stringify(data), item: JSON.stringify(item) }); } ``` 5. Call that function ``` var data = {}; var item = { id: "test", value: "something" }; useWorker(data, item); ``` You should see the "worker completed" message in the console along with formatted <abbr title="JavaScript Object Notation">JSON</abbr> data: ``` { "id": "test", "value": "something" } ``` ## Restrictions 1. The worker must reside in its own file - it cannot be created from a `Blob` or data schema <abbr title="Uniform Resource Identifier">URI</abbr> - more on that here → https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Content_security_policy. 2. Content Security Policy's `script-src` must include 'self' or 'strict-dynamic' (recommend including both for browsers that do not support 'strict-dynamic') - more about the script-src directive here → https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src # Update <time datetime="2020-01-03">3 January 2020</time> 1. *Do __not__ use `script-src` — Use __`script-src-elem`__ for best cross-browser support.* The `script-src` directive is not carefully supported across all browsers to enable workers launched from trusted scripts to run `eval()`. This past week (2020 new year's) I found that only Firefox <= 71 runs `eval()` in a worker launched from code in a `script-src` directive when defined in the `<meta>` tag in the page itself. Edge ignored it completely and ran the worker. Chrome complained that the worker's CSP did *not* allow 'unsafe-eval'. Moving that directive to the server and adding the response header there resulted in __ALL__ browsers failing to execute the worker. Changing `script-src` to `script-src-elem` in both server and `<meta>` tag versions resulted in successful execution across all browsers. 2. There is also a `worker-src` directive, but… According to this, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src, you can specify a CSP header for worker scripts. I had no success using this directive on server response headers or in the `<meta>` tag.