Plotly.js: Security warning: avoid using function constructor.

Created on 2 Sep 2016  Β·  29Comments  Β·  Source: plotly/plotly.js

I recently discovered that one of plotly.js's dependency is using Function constructor.
Unfortunately, I can't tell exactly which one, but the code doesn't seems to belong to plotly itself.

Here is Chrome's error message:

plotly.js:74222Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is
 not an allowed source of script in the following Content Security Policy directive: "script-src 'self'
 www.google-analytics.com ajax.googleapis.com 'nonce-67df8dace6ea7feb57cab73e41ebe0c7'".

makeOp  @   plotly.js:74222
(anonymous function)    @   plotly.js:74244
455.cwise-compiler  @   plotly.js:74271
s   @   plotly.js:7
(anonymous function)    @   plotly.js:7
129.ndarray @   plotly.js:25687
s   @   plotly.js:7
(anonymous function)    @   plotly.js:7
138../lib/shaders   @   plotly.js:27330
s   @   plotly.js:7
(anonymous function)    @   plotly.js:7
798.../../constants/gl3d_dashes @   plotly.js:127609
s   @   plotly.js:7
(anonymous function)    @   plotly.js:7
800.../../constants/gl_markers  @   plotly.js:128154
s   @   plotly.js:7
(anonymous function)    @   plotly.js:7
15.../src/traces/scatter3d  @   plotly.js:355
s   @   plotly.js:7
(anonymous function)    @   plotly.js:7
12../bar    @   plotly.js:312
s   @   plotly.js:7
e   @   plotly.js:7
(anonymous function)    @   plotly.js:7
a   @   plotly.js:7
(anonymous function)    @   plotly.js:7
__webpack_require__ @   bootstrap 08e5224…:585
fn  @   bootstrap 08e5224…:109
(anonymous function)    @   svg-to-png.js:2
__webpack_require__ @   bootstrap 08e5224…:585
fn  @   bootstrap 08e5224…:109
(anonymous function)    @   commons.js:36658
__webpack_require__ @   bootstrap 08e5224…:585
fn  @   bootstrap 08e5224…:109
(anonymous function)    @   index.js:1
__webpack_require__ @   bootstrap 08e5224…:585
fn  @   bootstrap 08e5224…:109
(anonymous function)    @   bootstrap.js:9
__webpack_require__ @   bootstrap 08e5224…:585
fn  @   bootstrap 08e5224…:109
(anonymous function)    @   index.js:3
__webpack_require__ @   bootstrap 08e5224…:585
fn  @   bootstrap 08e5224…:109
(anonymous function)    @   index.js:8
__webpack_require__ @   bootstrap 08e5224…:585
webpackJsonpCallback    @   bootstrap 08e5224…:21
(anonymous function)    @   index.js:1

Function constructor is considered by browser's CSP as an equivalent of eval.
Therefore, we can't setup a good Content Security Policy, unless we allow 'unsafe-eval' which is a whole in the policy itself.

It is possible to identify the faulty dependency, and maybe provide a workaround ?

community β™₯ NEEDS SPON$OR

Most helpful comment

Great news: Equinor is generously willing to sponsor $15k of this work if we can find sponsors for the rest! Please let us know if you're able to contribute part of the remaining $30k-$35k and we can get started on this project :)

All 29 comments

I think this is the line: https://github.com/scijs/cwise-compiler/blob/master/lib/compile.js#L351

cwise is a library that performs operations on ndarrays. It doesn't look like plotly uses the cwise browserify transform when bundling plotly.js, but I believe even the transformed version that does the parsing ahead of time still compiles an optimized version (i.e. new Function) at runtime.

Long story short, this is pretty central to what cwise does, so I'm not totally sure of a good workaround, unless there's a way to short-circuit the optimization and just perform naive operations on data.

Edit: more precisely, this looks like it might be coming from ndarray-ops, which is already transformed, but the problem remains the same. Looks like it's coming from here. That wouldn't be too hard to rewrite naively, but I'd be surprised if cwise didn't creep in somewhere else in the dependencies.

It doesn't look like plotly uses the cwise browserify transform when bundling plotly.js,

The plotly build step doesn't explicitly list the cwise transform, but several of our dependencies have cwise listed in their respective package.json namely ndarray-fill, ndarray-wrap and gl-select-static (ref this script).

I'm also running into this problem. Can't run my (Meteor) app because of this.
Is there a work-around maybe?

When I tried to bring plotly.js into my Electron application (via ember-plotly-shim) it triggers a security warning. In fact, since I have a set a Content-Security-Policy per the recommendations, it does not run at all.

Would someone be willing to write some / point to some documentation that describes how to incorporate plotly w/o using a pipeline that evaluates strings as code?

unsafe-string-eval

Still seeing this as well. Hasn't been addressed at all.

Nothing has changed since https://github.com/plotly/plotly.js/issues/897#issuecomment-244402532, so yes this hasn't been addressed _at all_ :scream:

The problematic cwise transform is only used in gl3d and gl2d (excluding scattergl), so using one of the partial bundles that don't include these trace types would _solve_ this issue. Better yet, bundling plotly.js yourself may do the trick.

Thanks for the advice! I'll take a look at that.

Does anyone have the CSP header they've used to keep security as tight as possible but still use plotly?

@jtal

Does anyone have the CSP header they've used to keep security as tight as possible but still use plotly?

I recommend starting with the strictest options and gradually relaxing restrictions until your application functions properly. There may be other parts of your work that won't work when fully restricted. As an example only, here is one I have used (inside of an electron app):
default-src 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; img-src 'self' 'data:'; style-src 'self' 'unsafe-inline'

See MDN

Looks like the direct plotly.js dependencies today that make plotly.js dependent on cwise are:

Does anyone know if it is feasible for plotly.js to go away from these dependencies (any alternatives?) and/or modify the dependencies themselves to not use cwise?

The unsafe-eval requirement is unfortunately currently stopping us from being allowed to use plotly.js's variant of parallell coordinates, as it appears to be part of the gl2d bundle, which then looks to be indirectly requiring cwise, ref. https://github.com/plotly/plotly.js/issues/897#issuecomment-371968332.

@anders-kiaer you may be interested in https://www.npmjs.com/package/plotly.js-gl2d-dist, you can install it using

npm install plotly.js-gl2d-dist

which has no dependencies.

Thanks @etpinard πŸ™‚ Hmm, not sure if I understand, isn't the cwise dependency bundled into the gl2d dist? I tried using the dist from npm install plotly.js-gl2d-dist, and also what I think is the CDN equivalent, and got the same CSP block without 'unsafe-eval'. Tested using this Dash snippet:

import dash
import dash_html_components as html
from flask_talisman import Talisman

app = dash.Dash(
    __name__, external_scripts=["https://cdn.plot.ly/plotly-gl2d-latest.min.js"]
)


CSP = {
    "default-src": "'self'",
    "script-src": [
        "'self'",
        "https://cdn.plot.ly",
        # "'unsafe-eval'",
        "'sha256-jZlsGVOhUAIcH+4PVs7QuGZkthRMgvT2n0ilH6/zTM0='",
    ],
    "style-src": ["'self'", "'unsafe-inline'"],
}

Talisman(app.server, content_security_policy=CSP, force_https=False)

app.layout = html.Span("Hello Dash!")

if __name__ == "__main__":
    app.run_server()

which when looking at the browser console gives a

EvalError: call to Function() blocked by CSP

unless 'unsafe-eval' is added. Searching through plotly-gl2d.js gives me 18 hits on Function(. The first hit looks to come from this line.

My mistake. I thought you were worried about the "unsafe-eval" behavior of cwise when bundling your own custom bundle, not in the browser runtime.

Unfortunately, there's no easy way for us to simply replace cwise. This package can improve performance by a large amount.

But, the basic, cartesian, geo, mapbox and finance partial bundles should be fine.

That is, bundles that include trace types other than gl3d, gl2d, scattergl and splom should pass the "unsafe-eval" checks.

FWIW I don't think it would be unreasonable to explore making an eval-free version of cwise - it's not the eval that gives it its speed, it's just using that to do codegen and reduce its own size. But at the end of the day I'd expect gzip means this codegen isn't even saving that many bytes over the wire... not a guarantee, but if someone wants to explore that we would certainly entertain it.

The current version 1.52.2 (https://raw.githubusercontent.com/plotly/plotly.js/v1.52.2/dist/plotly.js) uses Fonction in eval mode in those places:

Looks like the direct plotly.js dependencies today that make plotly.js dependent on cwise are:

Does anyone know if it is feasible for plotly.js to go away from these dependencies (any alternatives?) and/or modify the dependencies themselves to not use cwise?

@anders-kiaer this is done in #4929 and #4930 and available in plotly.js >= v1.54.4

We're still meeting the use of eval as a problem trying to integrate plotly.js within CryptPad.
Using smaller ones as anders suggested (in https://github.com/plotly/plotly.js/issues/897#issuecomment-585315283 did not solve it for us).

A pity...

@polx can you confirm this is happening with version 1.54.7 of plotly.js, and can you provide some context around what tool you're using that is complaining about this please? This issue has wandered all over the place since 2016 and we recently addressed parts of it but it's not clear what's outstanding. It might be helpful to create a new issue narrowly-scope to the problem you're seeing.

Hello @nicolaskruchten ,
Yes, it was on 1.54.7. We've been trying to embed plotly renderings as one of the rendering "macros" for cryptpad code (https://cryptpad.fr/). Cryptpad aimst at very high security standard and thus requires CSP.
If we deactivate CSP, then things load.
If we activate CSP then I get the following having replaced the plotly with the non-minimized version.

    Uncaught EvalError: call to Function() blocked by CSP
    makeOp http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:60786
    [443]</< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:60808
    [443]< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:60835
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    [253]< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:41713
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    [266]< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:44060
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    [1164]< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:190058
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    [1166]< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:190689
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    [36]< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:614
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    [26]< http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:420
    o http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    r http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    <anonymous> http://localhost:3000/lib/plotly-latest.min.js?ver=3.20.1-1596722563915:7
    execCb http://localhost:3001/bower_components/requirejs/require.js?ver=2.3.5:1696

Does it help?

Paul

@anders-kiaer this is done in #4929 and #4930 and available in plotly.js >= v1.54.4

I'm also still experiencing this eval error with v1.54.7 @nicolaskruchten
both pages are HTML and one is using the CDN and the other one has v1.54.7 in a