Vue.jsとCSP(Content Security Policy)

# このページについて
[Vue.js Advent Calendar 2018 - Qiita](https://qiita.com/advent-calendar/2018/vuejs) の 7日目 の記事です。

VueはどうやらCSPを気にしないといけないっぽいので、勉強がてらアウトプットしてみます。

# このページを書くモチベーション的なの（なぜCSPなのか）

LaravelのスカフォールドでVue向けにセットアップして、
単一ファイルコンポーネントが出力されることまでを見届けて、
機能を色々入れたところ、

しばらくしたら単一ファイルコンポーネントがHTMLに展開されなくなっていた。

コンソールログを確認したら、

```text
app.js?id=d5d0f8c0c937d3505d0a:32856 [Vue warn]: It seems you are using the standalone build of Vue.js in an environment with Content Security Policy that prohibits unsafe-eval. The template compiler cannot work in this environment. Consider relaxing the policy to allow unsafe-eval or pre-compiling your templates into render functions.
warn @ app.js?id=d5d0f8c0c937d3505d0a:32856
12:44:27.872 app.js?id=d5d0f8c0c937d3505d0a:32856 [Vue warn]: Failed to generate render function:

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: "default-src 'self' 'unsafe-inline' data:".
 in

with(this){return _c('div',{attrs:{"id":"app"}},[_c('vue-component')],1)}


(found in <Root>)
```

というエラーが出ていて、CSPにunsafe-evalが設定されてないぞってことで、
VueとCSPがチョット気になりました。

# このエラー自体はどう直すのか
ログに書いてある通りで、unsafe-evalを設定してあげればOK。

CSPの知見的には[Googleのガイド](https://developers.google.com/web/fundamentals/security/csp/?hl=ja)にまとまっているので、
ここを参考にCSPへ以下を追加。

> script-src 'self' 'unsafe-eval';

# なぜ起きたのか
デフォルトの設定からCSPの設定を自分で変えているからですね。
CSPが絡んでくるという認識が全く無かったので、特に動作確認とかしてなかったです。

# CSPの設定がどうチェックされているか探ってみる

## ログを吐いている入口を探す
IntelliJというのは本当に便利で、jsのデバッグが出来るのでどうやって出力しているか簡単に判りますね。
ほんと素晴らしい。

余談ですが、IntelliJでjsのデバッグをしたい場合はLiveEditを有効にする必要があるので、
興味あれば[公式](https://pleiades.io/help/idea/live-edit.html)を参照してやってみてください。

以下のとおり
> new Function('return 1');

の処理で例外が発生したときにログ出力されています。

```js
function createCompileToFunctionFn (compile) {
  var cache = Object.create(null);

  return function compileToFunctions (
    template,
    options,
    vm
  ) {
    options = extend({}, options);
    var warn$$1 = options.warn || warn;
    delete options.warn;

    /* istanbul ignore if */
    if (true) {
      // detect possible CSP restriction
      try {
        new Function('return 1');
      } catch (e) {
        if (e.toString().match(/unsafe-eval|CSP/)) {
          warn$$1(
            'It seems you are using the standalone build of Vue.js in an ' +
            'environment with Content Security Policy that prohibits unsafe-eval. ' +
            'The template compiler cannot work in this environment. Consider ' +
            'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
            'templates into render functions.'
          );
        }
      }
    }

    // check cache
    var key = options.delimiters
      ? String(options.delimiters) + template
      : template;
    if (cache[key]) {
      return cache[key]
    }

    // compile
    var compiled = compile(template, options);

    // check compilation errors/tips
    if (true) {
      if (compiled.errors && compiled.errors.length) {
        warn$$1(
          "Error compiling template:\n\n" + template + "\n\n" +
          compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
          vm
        );
      }
      if (compiled.tips && compiled.tips.length) {
        compiled.tips.forEach(function (msg) { return tip(msg, vm); });
      }
    }

    // turn code into functions
    var res = {};
    var fnGenErrors = [];
    res.render = createFunction(compiled.render, fnGenErrors);
    res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
      return createFunction(code, fnGenErrors)
    });

    // check function generation errors.
    // this should only happen if there is a bug in the compiler itself.
    // mostly for codegen development use
    /* istanbul ignore if */
    if (true) {
      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
        warn$$1(
          "Failed to generate render function:\n\n" +
          fnGenErrors.map(function (ref) {
            var err = ref.err;
            var code = ref.code;

            return ((err.toString()) + " in\n\n" + code + "\n");
        }).join('\n'),
          vm
        );
      }
    }

    return (cache[key] = res)
  }
}
```

この処理、CSPに必要な設定があるかどうかだけをチェックするためだけにやっているみたいです。

こんなので調べられるの？？ってことで調べてみたら、[Chromeのガイド](https://developer.chrome.com/extensions/contentSecurityPolicy)に

> The policy against eval() and its relatives like setTimeout(String), setInterval(String), and new Function(String) can be relaxed by adding 'unsafe-eval' to your policy:

とか書かれていますね。

てことはこの辺はブラウザ依存か？
ってことで非モダンブラウザの代表であるIE11で試してみたらエラーにならなかったです。

これだけでかなり面倒くさいな・・・って印象をかなり持った。

ちなみにガイドのすぐ下には、

> However, we strongly recommend against doing this. These functions are notorious XSS attack vectors.

と書かれていて、動的にメソッド作ってるので間違いなくまぁそうなんですけど、
JSのフレームワークって潜在的にセキュリティあんま良くない？とか思ったのでした。
フロントは詳しくないから強い人に教えて欲しいです・・・。

Angularとかもチョロっとコード覗いてきたけど、new Functionとかやってるので、
この問題はVueに限らないみたいですね。

# まとめ
Vueに絞って簡単にまとめておくと、

* フレームワーク自身がCSPにunsafe-evalを求めてきている（ガイドに書いといて欲しいな、書いてあったらすみません）
* モダンブラウザじゃないと、CSP意識してないからそのまま動く
* 上記より、IE -> Chromeとかに乗り換えると何で？ってことが起きそう（Vue使うようなシステムがIEベースでやってるとは思わないけど）