Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】eval() in a straitjacket: running user JavaScript under a strict CSP

2026年10月17日に「Dev.to」が公開したITニュース「eval() in a straitjacket: running user JavaScript under a strict CSP」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

ユーザーのJavaScriptコードを安全に実行するサンドボックスの構築方法を解説。厳格なCSPとiframe、Web Workerによる多層的な隔離で、悪意あるコードからの情報漏洩を防ぐ。CSP継承の課題やデータとコード分離、自己検証、徹底したテスト体制で堅牢性を確保した。

ITニュース解説

ウェブサイトの運営において、ユーザーが自分で書いたJavaScriptコードを実行できる機能は非常に便利である一方で、深刻なセキュリティ上のリスクをはらんでいる。例えば、テキスト変換ツールのように、ユーザーが機密性の高い情報(認証トークンやAPIキーなど)を貼り付けて処理を行う可能性がある場合、もし悪意のあるJavaScriptコードが実行されてしまうと、それらの機密情報が外部に盗み出されてしまう恐れがある。この問題に対処するため、厳重なセキュリティ対策が施された「サンドボックス」という隔離された実行環境が不可欠となる。

このサンドボックスは、何重もの防御壁でユーザーのコードを閉じ込める仕組みである。まず、一番外側にはサイト全体のコンテンツセキュリティポリシー(CSP)が適用される。CSPは、ウェブサイトが読み込むスクリプトや画像などのリソースの場所や種類を厳しく制限するルールであり、許可されていないリソースはブラウザによってブロックされる。ここでは、インラインスクリプト(HTML内に直接記述されたJavaScriptコード)は、事前に計算されたハッシュ値が一致するものだけが実行を許可されるという非常に厳しい設定がされている。

次に、ユーザーのコードは<iframe sandbox>という特別なHTML要素の中で実行される。iframeはウェブページの中に別のウェブページを埋め込むための仕組みだが、ここではURLから読み込むのではなく、実行ごとにHTML全体を文字列として生成し、srcdoc属性に直接渡される。sandbox属性にはallow-scriptsのみが許可され、JavaScriptの実行だけが許される。重要なのはallow-same-originという権限が意図的に除外されている点だ。これにより、iframeは親ページとは異なる「不透明なオリジン」を持つことになり、親ページのクッキーやローカルストレージ、DOM(ウェブページの構造)に一切アクセスできなくなる。これは、iframeが親ページにとって見知らぬよそ者として扱われることを意味する。唯一許されるのは、postMessageという安全なメッセージ送信機能を使って、親ページと限定的なやり取りをすることだけである。

このiframeの中にも独自のCSPが設定されており、default-src 'none'から始まり、インラインスクリプトの実行と次の段階であるWeb Workerの起動に必要な最低限の許可しか与えられない。これにより、iframeの中から外部ネットワークへのアクセス(fetch、XHRなど)は完全に遮断される。つまり、ユーザーのコードは計算はできるが、その結果を外部に送信する手段がない状態となる。

iframeの中では、小さなブートストラップスクリプトが起動し、ユーザーのコード、入力データ、セキュリティトークン、実行時間制限といった情報を受け取る。そして、実際にユーザーのJavaScriptコードはWeb Workerという仕組みの中で実行される。Web Workerはウェブページとは別の処理スレッドで動作するため、もしユーザーのコードが無限ループに陥っても、ウェブページ全体がフリーズすることなく、Web Workerのスレッドだけが消費される。また、Web WorkerもウェブページのDOMに直接アクセスできないため、ページの改ざんを防ぐことができる。時間制限を超過した場合は、iframeがWorkerを強制終了させる。Web Workerのスクリプトは、外部からダウンロードされるのではなく、メモリ上で生成されたblob: URLとしてロードされるため、外部からの不正なスクリプトの挿入も防がれる。

このように多層のサンドボックスを構築する際、サイト全体のCSPがiframeやWeb Workerに「継承される」という重要な特性がある。最初は、ユーザーのコードをブートストラップスクリプトの中に直接埋め込んでいたため、実行ごとにスクリプトの内容が変わってしまい、サイトの厳しいCSPでハッシュ値が一致せずブロックされてしまうという問題が発生した。この解決策として、ユーザーのコードをデータ(JSON形式)として<script type="application/json">タグ内に格納し、ブートストラップスクリプトは常に同じ内容で提供されるように変更された。これにより、ブートストラップスクリプトのハッシュ値は固定され、CSPに登録できるようになる。データはJSON形式で埋め込まれるが、HTMLパーサーが誤って</script>タグを認識してしまい、その後の内容がHTMLとして解釈されるリスクを防ぐため、JSON文字列中の<文字は\u003cにエスケープされる工夫が施されている。

また、親ページからiframeへpostMessageを使って直接データを渡すことは危険である。もし悪意のあるサイトがiframeを乗っ取った場合、機密情報が攻撃者のページに送信されてしまう可能性があるためである。そこで、ユーザーのコードや入力データはiframeのHTMLに最初から埋め込まれ、メッセージはiframeから親ページへの一方向のみに限定される。送信されるメッセージには、その実行に固有のセキュリティトークンを含め、親ページはこのトークンを検証して、正しいiframeからのメッセージだけを処理するようにしている。

さらに、Web Workerが本当に安全に隔離されているかを確認するため、Worker自身がセキュリティチェックを行う仕組みが導入されている。CSPが正しく適用されないという最悪のケースを想定し、まずdata:スキームのスクリプトを読み込もうとする。これは通常CSPでブロックされるはずである。しかし、サイト全体のCSPもdata:をブロックするため、これだけでは不十分だった。そこで、次に自身のサイトの/robots.txtというファイルにネットワークアクセスを試みる。サイト全体のCSPは自身のサイトへのアクセスを許可するが、iframeのCSPはネットワークアクセスを一切許可しないため、この試みはブロックされるはずである。この二重のチェックによって、Workerがネットワークにアクセスできないことが確認できた場合にのみ、ユーザーのコードを実行する。これにより、万が一ブラウザの挙動によってCSPの継承が正しく行われない場合でも、安全性が保たれる。

ユーザーのコードをnew Functionなどの機能で実行するためには、サイトのCSPで'unsafe-eval'というキーワードを許可せざるを得ない。これは、もしアプリケーションの他の部分で悪意のある文字列をevalなどに渡した場合のセキュリティリスクを高めるトレードオフではあるが、厳重なサンドボックス環境とその他の対策によって、そのリスクは限定的に抑えられている。

最後に、共有リンクなど外部から提供されたカスタムJavaScriptステップは、ユーザーが内容を理解し、信頼するまで自動的に実行されないよう、デフォルトで無効化される。複雑なコードの入れ子構造があっても、すべてのコードステップが確実にチェックされるように、TypeScriptの型システムを活用した堅牢な実装がなされている。

これらのセキュリティ対策は、単に実装するだけでなく、その有効性を継続的にテストし、検証することが重要である。インラインスクリプトのハッシュ値を自動的にチェックするユニットテストや、ビルド時にすべてのページをスキャンして未許可のスクリプトがないかを確認するテスト、そして実際のCSP環境下でエンドツーエンドの動作を確認し、外部へのネットワークリクエストがゼロであることを検証するテストなど、多角的なアプローチでセキュリティが保たれている。さらに、静的解析ツールによるコードの監査や、HTML解析の不備による潜在的な脆弱性の発見と修正など、様々な方法でセキュリティホールの発見と対処が行われている。

このように、ユーザーが提供したJavaScriptコードを安全に実行できる環境は、幾重ものセキュリティレイヤーと厳格なポリシー、そしてそれらを検証する徹底したテストによって初めて実現される。安易なユーザーコード実行機能は大きなリスクを伴うため、極力避けるべきだが、もし必要であれば、ここで紹介されたような徹底した対策を講じる必要がある。

関連コンテンツ

関連IT用語