-ao- ramune blog

©2020 unio / GO2直営からふるラムネ

Content Security Policyを適用してVueを動かす

2019年02月20日
  • Vue
  • CSP

厳し目のCSPを適用したい

投稿系のアプリを作る際に、なるべくXSSの脅威を減らすため CSP を厳し目に設定したいケースがあります。

CSPでunsafe-evalを設定しない場合、Vueの 完全パッケージ ではエラーが出てしまい動かすことができません。

VueのCSPエラー
                
                    [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.

                    ## ざっくり日本語訳
                    [Vue warn]: スタンドアロンビルドのVue.jsを、evalが禁止された環境上で利用しています。
                    この環境ではテンプレートコンパイラは動きません。
                    evalの利用を許可するか、テンプレートをrender関数でプリコンパイルすることを検討してください。
                
            

今回はCSPのscript-srcで一番厳しいscript-src 'self';を目標に、どうやってVueを動かすかの解説を行います。 なおビルド環境はwebpackを利用しますが、基本的な考えは他のビルドツールやCDN配布のVueスクリプト利用でも変わりません。

CSP vs. Vue

下記はテスト用にCSPを設定したページとVueが書かれたjsファイルです。

CSPを設定したHTML
                
                    <!DOCTYPE html>
                    <html lang="ja">
                        <head>
                            <title>CSPテスト</title>
                            <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
                        </head>
                        <body>
                            <main>
                                <span v-text="message"></span>
                            </main>
                            <script src="/build/test.js"></script>
                        </body>
                    </html>
                
            
test.js
                
                    import Vue from "vue";
                    new Vue({
                        el: "main",
                        data: {
                            message: "CSPテスト"
                        }
                    });
                
            

このままでは先程のevalエラーが出てしまうので、 テンプレートコンパイラの入っていないランタイムパッケージを利用します。

webpack.config.js
                
                    ...
                    resolve: {
                        ...
                        alias: {
                            ...
                            // ランタイムパッケージを利用
                            "vue$": "vue/dist/vue.runtime.esm.js"
                        }
                    },
                
            

Symfony4のWebpackEncoreBundleを利用している場合は、下記のように設定します。

Webpack Encore版のwebpack.config.js
                
                    const config = Encore.getWebpackConfig();
                    config.resolve.alias["vue$"] = "vue/dist/vue.runtime.esm.js";
                
            

ランタイムパッケージに切り替えるとevalエラーは消えますが、今度は別のエラーが出ます。

ブラウザのデベロッパーコンソールに表示されるエラー
                
                    vue.runtime.esm.js:619 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available.
                    Either pre-compile the templates into render functions, or use the compiler-included build.

                    ## ざっくり日本語訳
                    vue.runtime.esm.js:619 [Vue warn]: テンプレートコンパイラの入っていないランタイムビルドのVueを利用しています。
                    テンプレートをrender関数でプリコンパイルするか、コンパイラの入ったパッケージを利用してください。
                
            

もちろんテンプレートコンパイラは使えないので、render関数でプリコンパイルする方法で対応します。

render関数を使う

render関数を使うためVueをコンポーネント化します。 Vueコンポーネントはいくつかありますが、今回はシングルファイルコンポーネント(SFC)で記述します。

Test.vue
                
                    <template>
                        <main>
                            <span>{{ message }}</span>
                        </main>
                    </template>

                    <script>
                        export default {
                            props: {
                                message: String,
                            },
                        }
                    </script>
                
            

SFCを書いたら、Vueのエントリーjsもrender関数に書き直します。

test.js
                
                    import Vue from "vue";
                    // 作成したSFCをインポートする
                    import Test from "./Test";

                    new Vue({
                        el: "main",
                        render(createElement) {
                            // SFCをレンダリングする
                            return createElement(Test, {
                                props: {
                                    message: "CSPテスト",
                                }
                            });
                        },
                    });
                
            

最後にHTMLからv-textを外して完成です。

HTMLからバインド構文を除去
                
                    <!DOCTYPE html>
                    <html lang="ja">
                        <head>
                            <title>CSPテスト</title>
                            <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
                        </head>
                        <body>
                            <main></main>
                            <script src="/build/test.js"></script>
                        </body>
                    </html>
                
            

これでCSPに違反せず、Vueが動くことを確認できると思います。

まとめ

今回はCSPのスクリプト設定で一番厳しいscript-src 'self';上でVueを動かしました。 ちなみにこの設定はインラインスクリプトも許可しないので、XSSの危険度をかなり下げることができます。 安全性が増す反面、外部サイトスクリプト(広告タグや計測タグ)を利用する場合は設定が面倒になるので、 リスク判断に応じて制限を緩めるのがオススメです。

プロフィール画像
なかのひと:unio

数十年前の牧歌的なインターネッツが好きだった、永遠のモラトリアム人。 ただ、モラトリアムしててもお金は増えないので、しゃかいの厳しさを斜め後ろから眺めつつほそぼそと生活しています。

Twitter GitHub
[広告]