【ITニュース解説】Hardening a Static App on Vercel: CSP, CORS, and Service Workers that Don’t Bite
2025年09月19日に「Dev.to」が公開したITニュース「Hardening a Static App on Vercel: CSP, CORS, and Service Workers that Don’t Bite」について初心者にもわかりやすく解説しています。
ITニュース概要
Vercel上の静的アプリを安全かつ高速に運用するため、CSPで不正なスクリプトをブロックし、CORSでAPIアクセスを制限する方法を解説する。Service WorkerでUI要素のみをキャッシュし、API通信はリアルタイムで行うことで、セキュリティとパフォーマンスを両立させる設定例を紹介する。
ITニュース解説
Webアプリケーションを開発する上で、ユーザーに安全で快適な体験を提供するためには、「堅牢化(Hardening)」というセキュリティとパフォーマンスを高める取り組みが不可欠である。これは、アプリケーションを外部からの不正な攻撃や予期せぬ不具合から保護し、安定して機能させるための重要なプロセスを指す。特に、システムエンジニアを目指す初心者にとって、これらの概念を理解し、実際にどのように実装されるかを知ることは、セキュアなシステム設計の基礎を築く上で非常に役立つ。この記事では、Vercel上でホストされる静的アプリケーション「Pocket Portfolio」の例を通じて、Content Security Policy(CSP)、Cross-Origin Resource Sharing(CORS)、そしてService Workerという主要な技術がいかにアプリケーションの安全と速度を両立させているかを具体的に解説する。
Content Security Policy(CSP)は、Webアプリケーションに対する最も一般的な攻撃の一つであるクロスサイトスクリプティング(XSS)を防ぐためのセキュリティ機能である。これは、ウェブブラウザに対し、Webページがどのようなリソース(スクリプト、スタイルシート、画像など)をどこから読み込むことを許可するかを厳密に指示するポリシーを定義する。Pocket Portfolioでは、vercel.jsonファイル内のHTTPヘッダーとしてCSPを設定している。例えば、default-src 'self'という設定は、すべてのリソースは自分のサイト(オリジン)からのみ読み込まれることをデフォルトで許可するという意味だ。その上で、Firebase認証サービスやTailwind CSSのCDNなど、意図的に利用する外部サービスのリソースについては、script-srcやconnect-srcといった具体的なディレクティブを使って、信頼できる特定のドメインのみを明示的に許可している。frame-ancestors 'none'は、アプリケーションが他のサイトの<iframe>内に埋め込まれることを完全に禁止し、クリックジャッキングのような攻撃から保護する。このような詳細かつ厳格なCSP設定により、悪意のあるスクリプトがWebページに注入されても、ブラウザがその実行をブロックし、ユーザーのデータが侵害されるのを防ぐことができる。また、Firebase認証に必要な特定のドメインを許可することで、本番環境で認証ポップアップが機能しないといったトラブルを回避し、アプリケーションが意図通りに動作することも保証している。
Cross-Origin Resource Sharing(CORS)は、Webブラウザのセキュリティ機能である「同一オリジンポリシー」を補完し、異なるオリジン(ドメイン、プロトコル、ポートの組み合わせ)にあるリソースへの安全なアクセスを可能にする仕組みである。同一オリジンポリシーは、セキュリティ上の理由から、Webページが自分と同じオリジンからのリソースにしかアクセスできないように制限する。しかし、現代のWebアプリケーションでは、フロントエンドがバックエンドAPIと異なるドメインで動作したり、複数の異なるサービスからデータを取得したりすることが一般的であるため、この制限だけでは開発が困難になる。CORSは、サーバー側が特定のヘッダーをレスポンスに含めることで、ブラウザに対して異なるオリジンからのアクセスを許可するかどうかを伝え、安全なクロスオリジン通信を可能にする。Pocket Portfolioでは、サーバーレス関数やエッジ関数として提供されるAPIに対してCORSを設定し、自分のサイト(https://pocketportfolio.app)からのリクエストのみを許可している。これにより、他の悪意のあるWebサイトがPocket PortfolioのAPIを直接呼び出して情報を不正に取得するのを防ぎ、APIが提供するデータを保護できる。実装例として示されている/api/_cors.jsファイルでは、Access-Control-Allow-Originヘッダーに許可するオリジンを正確に指定し、Access-Control-Allow-Methodsで許可するHTTPメソッド(例えばGET)を定義している。また、ブラウザが本命のリクエストを送る前に安全性確認のために行うOPTIONSメソッドのプリフライトリクエストにも適切に応答することで、通信の安全性を確保している。
Service Workerは、Webブラウザのバックグラウンドで動作するスクリプトであり、Webアプリケーションのオフライン対応やプッシュ通知、リソースのキャッシュによるパフォーマンス向上など、Web体験を大幅に向上させる機能を提供する。特に、Progressive Web App(PWA)の実現にはService Workerが不可欠である。しかし、強力なキャッシュ機能を持つService Workerは、不適切な設定をすると、アプリケーションの動作を阻害したり、特に金融アプリケーションのような機密性の高いデータを扱う場合は、古いデータを表示してしまうことによる信頼性の低下やセキュリティ上の問題を引き起こす可能性があるため、慎重な設計が求められる。Pocket Portfolioでは、「アプリケーションの動作を妨げないService Worker」を目標に、以下のキャッシュ戦略を採用している。まず、Service Workerが影響を及ぼす範囲(スコープ)を/app/パス以下に限定し、アプリケーションのUIシェル(CSS、JavaScript、アイコンなどの静的なファイル)やナビゲーションページのみを対象とする。これらの静的アセットに対しては「キャッシュファースト」戦略を適用し、一度ダウンロードされたら、ネットワーク接続がない状態でもすぐにアプリケーションが表示できるようにする。一方、ナビゲーションリクエスト(ページ遷移など)に対しては「ネットワークファースト」戦略を採用し、常に最新のコンテンツをネットワークから取得することを優先するが、ネットワークエラーが発生した場合にはキャッシュされたバージョンにフォールバックして表示する。最も重要なポイントは、/api/パスへのリクエストはService Workerによって決してインターセプトしないという厳格なルールである。金融アプリケーションにおいて、APIから取得されるデータ(例えば、株価や取引履歴)はリアルタイムで正確である必要があり、古いデータをキャッシュしてしまうとユーザーに誤解を与え、深刻な問題を引き起こす可能性があるため、常にネットワークから最新の情報を取得するようにしている。Service Workerはinstallイベントで初期キャッシュを構築し、activateイベントで古いキャッシュをクリーンアップする。そしてfetchイベントでネットワークリクエストをインターセプトし、設定されたキャッシュ戦略に基づいて適切な応答を返す。開発環境でのService Workerの登録は、誤動作を防ぐために行わないという配慮もなされている。このような設計により、Pocket Portfolioは高速な動作とオフラインアクセスを提供しつつ、金融データに関する正確性と安全性を確実に保っている。
これらの主要なセキュリティ対策に加えて、Pocket Portfolioはさらなる堅牢化のためのボーナス対策も実施している。例えば、X-Content-Type-Options: nosniffヘッダーを設定することで、ブラウザがMIMEタイプスニッフィング(ファイルの内容からMIMEタイプを推測する機能)を行わないように指示し、不正な形式のファイルがスクリプトとして実行されるのを防ぐ。Referrer-Policy: strict-origin-when-cross-originは、異なるオリジンへのリクエスト時にリファラー(参照元)情報を厳密に管理し、不要な情報漏洩を防ぐ。静的アセットにはCache-Control: public, max-age=31536000, immutableというヘッダーを設定し、ブラウザに一年間キャッシュを保持するよう指示することで、再訪問時のロード時間を劇的に短縮している。さらに、APIへの「レート制限」を導入することで、一定時間内のリクエスト数を制限し、DDoS攻撃やブルートフォース攻撃から保護している。これらの多層的なセキュリティ対策は、アプリケーションの全体的な防御力を高める。
アプリケーションの開発プロセスにおいても、堅牢化は継続的に行われる。Pocket Portfolioのようなオープンソースプロジェクトでは、Vercelのプレビュー機能やGitHubのワークフローが重要な役割を果たす。プルリクエスト(PR)が作成されるたびに、VercelのプレビューURLが自動的に生成され、開発者はコードがマージされる前に、その変更がセキュリティ、パフォーマンス、ユーザー体験にどのような影響を与えるかを詳細にレビューできる。lint、typecheck、test、buildといった自動化されたチェックが必須とされ、コードの品質と安全性が保証される。このような「トランクベース開発」とVercelのプレビュー機能を組み合わせたワークフローは、高速かつ安定したリリースを可能にし、開発段階からセキュリティとパフォーマンスを意識したアプリケーション開発を推進する上で極めて有効である。これらの取り組みは、堅牢なシステムを構築し、維持していくための基礎となり、システムエンジニアとして不可欠なスキルとなるだろう。