はじめに

アプリケーション開発部の田村です。自社で運営するサービス開発を担当しています。
今、担当しているサービスはReactで開発し運用しています。
Reactで開発したアプリケーションを公開する際に、注意しないと誰でも開発時のコード(後述しますがMinify前のコード)を閲覧出来てしまう恐れがあります。
今回はその事象と対策について紹介します。

本記事を実施する上での要件

  • Node.js
  • Google Chrome

事象の説明

開発構成

私のプロジェクトではcreate-react-appを利用しています。
create-react-appを使ってアプリケーションの雛形を作成し、開発を行い、ビルド・本番環境へデプロイをしています。

webpackについて

create-react-appを用いてサンプルのアプリケーションを作成すると、ビルドはwebpackを用いて行われます。
webpackとはモジュールバンドラのことで、複数のjsファイルやcssファイルを1つのjsファイルに束ねてくれます。

webpackイメージ

1つのjsファイルにすることで、ブラウザが多くのjsファイルやcssファイルを読み込む必要がなくなり、リクエストのオーバーヘッドが軽減されます。
またプラグインを用いることで、コメントや改行の削除、変数名のmangle(変数名をaやbなどに置き換える)などを行うMinify処理も行われます。
このMinifyによりファイルサイズが小さくなると同時に簡易的な難読化の効果も期待できます。

Minifyされたコード

SourceMapについて

SourceMapとはMinifyされたコードをMinify前のコードに復元できる情報を持ったファイルです。
このファイルを使えばMinifyされたコードを開発時のコードに復元することが出来ます。

SourceMapを読み込んでMinify前のコードに復元出来るツールとして一番身近なものがGoogle Chromeブラウザになります。 Minify後のコードとSourceMapがサーバに配置されていれば、誰でもMinify前のコードを見ることが出来ます。

create-react-appで作成されたアプリケーションをnpm run buildでビルドすると、このSourceMapがデプロイ対象のファイルと同じディレクトリに生成されてしまいます。
デプロイ時にこのSourceMapを「多分必要なものなんだろう」と思ってサーバに配置してしまうと「Minify前の開発コードが丸見え」という状況になります。

SourceMapを配置した時のデメリット

このSourceMapを配置することで開発コードが閲覧されてしまうことのデメリットについて考えていきます。
デメリットはいくつかありますが重要なのはセキュリティの問題です。

通常のアプリケーション開発者のレベルであればMinifyされたコードだけでアプリケーションの挙動をトレースするのは困難です。
しかし、Minifyは正確には難読化とは違うので、スキルの高い技術者ならトレースは出来てしまいます。 それでも、容易にトレースをさせない・トレースを出来る人を制限、出来るのでそれだけでも十分効果はあると考えます。

容易にトレース出来る状態でサービスを公開していると、不特定多数の人にコードをトレースされ、アプリケーションの脆弱性をつかれる可能性が高まります。

またコードコメントが見えることも問題です。コメントは第三者に向けてコードの理解を促す目的で書かれるものですから、解析作業を助長してしまいます。
「TODO」コメントなども放置されがちで、これも攻撃者にとって美味しい情報となります。

事象の再現

今回の事象を手元で実際に試してみましょう。Node.jsがインストールされてる環境であれば簡単なコマンドで試すことが出来ます。

検証アプリケーションの作成

任意のディレクトリで下記コマンドを実行して、検証用のReactアプリケーションを作成します。

npx create-react-app my-app

検証アプリケーションのビルド

次にビルドを行います。

cd my-app
npm run build

ビルドが終わると ./build ディレクトリにファイルが生成されます。
この中の ./static/js/*.js がMinifyされたコードで、 ./static/js/*.js.map がSourceMapとなります。

ブラウザで確認

ビルドされたファイルをGoogle Chromeで確認します。まず下記コマンドで簡易のHTTPサーバツールを導入し、起動します。

npm install serve
npx serve -s  build

Google Chromeを起動し、http://localhost:5000にアクセスしてください。
Reactのサンプルアプリケーションの画面が表示されます。

コードの確認

Google Chrome上でF12キーを押下し、開発者ツールを表示します。
そして「ソース(Sources)」タブを選択します。すると下記の画像のように開発時のjsファイルを見ることが出来ます。コメントもしっかり見えています。

buildディレクトリ配下には、App.jsindex.jsというファイルは存在しません。
しかしGoogle Chromeの開発者ツールにはSourceMapを読み込み、擬似的に開発コードを復元・表示する機能が備わっています。
この機能がデフォルトで有効になっているため開発コードが見えているわけです。

このように簡単な操作で誰でもMinify前の開発コードを見ることが出来ました。

対策方法

この「開発コード丸見え」状態を回避するにはどうすればよいかというと、SourceMapを物理的に削除すれば解決します。
ただ、削除するなら元から生成しなければよい話ですのでビルド時に生成しないようにしましょう。

create-react-appではのビルド時の挙動を環境変数により変更することが出来ます。下記が設定一覧となります。
https://create-react-app.dev/docs/advanced-configuration/

上記のページにGENERATE_SOURCEMAPという項目が存在します。こちらを無効にすることでSourceMapの生成を止めることが出来ます。

アプリケーション直下のpackage.jsonファイルを下記のようにします。
package.json

   "scripts": {
     "start": "react-scripts start",
-    "build": "react-scripts build",
+   "build": "GENERATE_SOURCEMAP=false react-scripts build",
     "test": "react-scripts test",
     "eject": "react-scripts eject"
   },

.envに書いてもよいです。
ただ今回は、後ほどSourceMapも生成するビルドコマンドを追加するため、設定のスコープをコマンドにするためshell変数としています。

下記のコマンドで再度ビルドをしてHTTPサーバを起動してください。

npm run build
npx serve -s  build

Google Chromeの開発者ツールで確認すると、先程まで見えていたApp.jsindex.jsといった開発コードが表示されなくなりました。

SourceMapがないと困ること

これで「開発コードが丸見え」という状況は回避出来ましたが1つ問題があります。
それは、Reactのエラーが起きた時にGoogle Chromeの開発者ツールでエラー箇所を確認しようとしても、Minifyされたコードしか見ることが出来ないため、調査に支障が出てしまうという点です。

具体的にどういうことか、App.jsを下記のように修正して確認してみましょう。

 './App.css';
 
 function App() {
+
+  const hoge = () => {
+    const hoge = 'hoge';
+    hoge = 'fuga';
+  };
+
   return (
     <div className="App">
       <header className="App-header">
         <img src={logo} className="App-logo" alt="logo" />
+        <button  onClick={hoge}>
+          TEST
+        </button>
         <p>
           Edit <code>src/App.js</code> and save to reload.
         </p>

簡単なエラーを起こす「TESTボタン」を追加しました。

npm run build
npx serve  -s build

で起動し、Google Chromeの開発者ツールを表示してから「TESTボタン」を押してみましょう。

「コンソール(Console)」タブにエラーが表示され、エラーの発生箇所がリンクで表示されます。

しかしこのリンクを押しても、見えるコードはMinifyされたコードなので調査は困難です。

一時的にSourceMapを適用する方法

これでは困るのでSourceMapをサーバに配置せずに、自分の手元でのみ適用させる方法をご紹介します。

まず、SourceMapを出力するためのコマンドを追加するためにpackage.jsonを下記のように変更します。
SourceMapを生成し、ビルドの出力場所を./devBuildに変更するコマンドdevBuildを追加しました。

   "scripts": {
     "start": "react-scripts start",
    "build": "GENERATE_SOURCEMAP=false react-scripts build",
+  "devBuild": "BUILD_PATH='./devBuild' react-scripts build",
     "test": "react-scripts test",
     "eject": "react-scripts eject"
   },

実行してみましょう。

npm run devBuild

./devBuild配下に、SourceMapを含んだファイルが生成されます。

次にGoogle Chromeの開発者ツール上ででSourceMapを適用したいファイルを開きます。今回はmain.2c843d7b.chunk.jsとなります。
開いたソースファイル上で右クリックをして「Add source map…」を選択してください。

SourceMapのURLを要求されるので、先程作成した./devBuild配下にあるSourceMapを指定します。指定する際はfileプロトコル形式でフルパスを指定します。
私の環境(Linux)では下記のように指定しました。

file:///tmp/my-app/devBuild/static/js/main.2c843d7b.chunk.js.map

再度、「TESTボタン」を押下して「コンソール(Console)」のエラーを見てみましょう。
at onClickのエラー行の表示が変わっています。これをクリックすると無事、App.jsのエラー発生箇所を表示することが出来ます。

まとめ

package.jsonを少し変更するだけで、SourceMapの配置を回避することが出来ました。
手間もかからず、またビルドも少し速くなるので検討してみてください。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。