🌴

LINEミニアプリの検証環境にVercelのDeployment Protectionを適用する

に公開

React2ShellとDeployment Protection

2025年末、おなじみReact2Shellの脆弱性が報告されました。

この脆弱性自体は最新バージョンへのアップデートで対処できますが、Vercelを利用している場合は別の問題が残ります。過去のPreview URLは脆弱性を含んだバージョンのままという点です。

https://vercel.com/kb/bulletin/react2shell

これに対しVercelは、Deployment Protectionで過去のデプロイメントへのアクセスを制限することを推奨しています。

Even if your production app has been patched, older versions could still be vulnerable. We strongly recommend turning on Standard Protection for all deployments besides your production domain.

この機能を使えば、過去のデプロイメントを含むすべてのPreview環境へのアクセスにVercel Authenticationを必須にできます。

そうはいっても適用できない場合もある

しかし、Deployment Protectionを有効にすると、開発環境や検証環境のURLを外部に共有している場合にページアクセスができなくなり、APIも叩けなくなります。

また、影響はPreview環境だけでなくProduction環境にも及ぶ可能性があります。

たとえば、Upstash Queueを用いてAPIコールを非同期タスク化している場合、コール先のURLをVERCEL_URLなどに指定していると、Deployment Protectionは本番環境のカスタムドメインに合致しないとして、本番ブランチであってもUpstash側からのリクエストにVercel認証を要求するため、正常に動作しなくなります。

Protect all except production Custom Domains for your project.

そのような状況では安易にDeployment Protectionを適用できず、代わりに過去のPreviewデプロイメントをすべて削除するしかありません(実際、脆弱性発覚当日、私はいくつかのプロジェクトでそうしました)。

過去のPreviewデプロイメントのURLはランダムサフィックスを含むため、直接漏洩しない限り予測困難なURLではありますが、もはや攻撃者は総当りの勢いのため、油断はできません。さらにブランチURLは比較的推測されやすいため、より危険です。

なるべくダウンタイムを出さずにDeployment Protectionに移行したい

この記事では、上記のような外部連携を維持しながらDeployment Protectionを適用するのに試行錯誤した際の知見をまとめておきます。
また、私はLINEミニアプリ(LIFF)を構築することが多いため、組み合わせて使う場合のポイントも記します。

Deployment Protectionの有効化方法

https://vercel.com/docs/deployment-protection

Deployment ProtectionはVercelのプロジェクト設定から有効化できます。

もし、あらゆるページアクセスとAPIコールがVERCEL_PROJECT_PRODUCTION_URLにのみ依存しているのであれば、有効にするだけで問題ありません。

そうでない場合は、従来通りパブリックにアクセス可能なURLを維持するために、以下のバイパス方法を利用する必要があります。

バイパス方法

Deployment Protectionには2つのバイパス方法があります。

1. Protection Bypass for Automation

x-vercel-protection-bypassヘッダーにバイパスシークレットを設定することで、認証をバイパスできます。
また、設定画面でバイパスシークレットを追加するとVERCEL_AUTOMATION_BYPASS_SECRET環境変数がビルド時に自動で割当てされるようになります。

https://vercel.com/docs/security/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation

ブラウザ以外からのAPIコールでは、後述のSharable LinksによるCookieの保持ができないため、基本的にこの方法でバイパスすることになります。

Sharable Linksは、特定のURLからアクセスした際にブラウザにCookieをセットし、以降のセッションでDeployment Protectionを通過できるようにする仕組みです。

https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/sharable-links

一度Cookieがセットされれば、同一セッション内のAPIリクエストもCookieが保持されるため問題なく動作します。

ダウンタイムを最小化する導入手順

未適用の環境にDeployment Protectionを導入する場合、何も考えずに有効化すると以下の作業が完了するまでサービスが停止します:

  • Sharable Linksの発行と設定
  • 外部サービスへのバイパスシークレット設定

特に、VERCEL_AUTOMATION_BYPASS_SECRETの設定を何も考えずにProtection Bypass for Automation経由で行う場合、反映まで再ビルドが必要になるため、数分間のダウンタイムが発生します。

そこで、先んじて手動設定するという手法を用います。

推奨手順

  1. VERCEL_AUTOMATION_BYPASS_SECRETを事前に手動設定

    • Vercelの環境変数に任意の32文字で先に設定しておく(openssl rand -base64 24など)
    • VERCEL_接頭辞の環境変数も問題なく利用できる(できないかと思っていた)
  2. コード変更をデプロイ

    • 外部サービスからのリクエストにx-vercel-protection-bypassヘッダーを付与するコードを反映
  3. Deployment Protectionを有効化

    • 有効化直後にProtection Bypass for Automationからバイパスシークレットを追加(このとき、自動生成せずに1の手順で用いた値を指定すること)
    • 検証環境用ブランチに対してDeploymentsタブからSharable Linksを発行
    • ページアクセスは以降、Sharable Links経由で行う
  4. 手動設定した環境変数を削除(任意)

    • 1で指定したものと同じ値のため、わざわざ消さなくてもよい
    • ちなみにProtection Bypass for Automationで環境変数が自動設定される場合、1で手動設定した値よりも自動設定の値が優先(オーバーライドされる)

ただし、Sharable Linksの発行は手動で行う必要があるため、こちらは事前に準備しておくことはできません。
とはいえ、こちらはあくまで検証環境へのページアクセス用途であることを考えれば、そもそもアクセスしうるユーザーが限られるため影響は少ないでしょう。

LINEミニアプリ(LIFF)との組み合わせ

ここまでDeployment Protectionの一般的な話をしてきましたが、実はLIFFにはこの仕組みと非常に相性の良い特性があります。

LIFFのリダイレクト挙動

LIFFでは、ユーザーがLIFF URLにアクセスすると以下のような流れでページ遷移が行われます:

  1. 1次リダイレクト:LIFF URLからエンドポイントURL(ルートページ)へ
  2. 2次リダイレクト:認証処理などを経て目的のページへ

つまり、LIFFは必ず最初にエンドポイントURL(ルートページ)を経由するという性質があります。

エンドポイントURLにSharable Linksを設定するだけでOK

この特性を利用すると、LINE DevelopersコンソールでLIFFアプリのエンドポイントURLにSharable LinksのURLを設定するだけで、URLの再配布なくDeployment Protectionに対応できます。

ユーザーがLIFF URL経由でアクセスすれば:

  1. 最初のリダイレクトでSharable Links経由でルートページにアクセス
  2. この時点でCookieがセットされる
  3. 以降のページ遷移やAPIリクエストはCookieにより認証を通過

個別のパスごとにSharable Linksを発行する必要はなく、エンドポイントURLの設定だけで全体をカバーできるため非常に都合が良いです。

Messaging APIのWebhook設定

LINEのMessaging API Webhookは、カスタムヘッダーを設定できません。

そのため、ヘッダーによるバイパスが使えず、クエリパラメータでバイパスシークレットを渡すという非推奨の方法を取らざるを得ません。

https://example.vercel.app/api/webhook?x-vercel-protection-bypass=<secret>

クエリパラメータはURLに露出するためセキュリティ上好ましくありませんが、Messaging APIの仕様上これ以外に手段がありません。

リスクを最小限にするため、Messaging API専用のバイパスシークレットを分離して発行するのがよいと思います。
万が一漏洩した場合でも、他のバイパス経路には影響せずにローテーションできるようにしておきたいところです。

小ネタ

  • Deployment ProtectionをON/OFFしても、再生成するまでSharable LinksのURLは変わらない
  • 一度生成したバイパスシークレットは、Protectionを無効化した後も値が保持される
  • 一度もProtectionを有効化していない状態ではバイパスシークレットの追加はできないが、それ以降はOFFでも追加できる
キリフダ株式会社

Discussion