Vercel v0、ユーザーの OAuth トークンを公開せず Snowflake に認証する仕組みを解説
本文の状態
日本語全文を表示中
詳細モードで約12分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
Vercel Blog
Vercel は v0 の Snowflake 統合において、生成コードがユーザーの OAuth トークンにアクセスできないよう、サンドボックス外部のプロキシ経由で認証情報を動的に注入する仕組みを導入した。
AI深層分析を開く2026年8月21日 10:47
AI深層分析
キーポイント
認証情報の分離アプローチ
生成されたコードが実行されるサンドボックス環境内にユーザーの認証トークンを配置せず、外部プロキシで解決することで機密情報の漏洩リスクを排除する。
サンドボックスの限界と対策
従来の分離技術は内部からの情報読み出しを防げないため、TLS 終端と証明書検証を行うプロキシを介してすべての Snowflake リクエストを仲介する。
動的な認証トークン注入
プロキシがサンドボックスの OIDC トークンを検証し、対応するチャットセッションからユーザーのロール情報を取得して、リクエスト実行時にのみ一時的に認証情報を付与する。
トークンファイルの非機密化
本物のOAuthトークンをサンドボックスに書き込む代わりに、アクセス権を持たない固定のプレースホルダーを配置して既存のSDK互換性を維持する。
プロキシによる認証制御
リクエストの承認はプレースホルダーの内容ではなく、サンドボックスのサーバーサイドIDとユーザーチャットのバインディングに基づいて行われる。
重要な引用
generated code shouldn't have access to the user's credentials
Isolation protects the rest of the system from untrusted code, but it doesn't protect secrets inside the sandbox.
Every Snowflake request goes through the proxy
The real OAuth token, the reusable credential that represents the user, is never written into the sandbox.
編集コメントを表示
編集コメント
生成 AI の実用化において、セキュリティと利便性の両立は最大の課題の一つである。Vercel が提示したプロキシによる動的認証注入の手法は、開発者が既存のツールをそのまま使いながら高いセキュリティレベルを維持するための重要な指針となる。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
AI が生成したアプリケーションは、ユーザーに代わって外部サービスへの認証が必要なケースが少なくありません。しかしここで問題が生じます。生成されたコードが、ユーザーの認証情報を直接取得してはいけないからです。
v0 の Snowflake 連携機能を開発する際にも、この判断を迫られました。この連携により、ユーザーは Snowflake に接続し、スキーマを検索したりデータをクエリしたり、そのデータウェアハウス上で動作するアプリケーションを生成することが可能になります。生成されたコードが Snowflake に認証を行う必要がある一方で、それはモデルによって書かれ、人間のレビューを経ずに実行されるものです。プロンプトインジェクション攻撃によって悪意ある指示を与えられれば、コードは読み取れるあらゆるデータを流出させる方向に誘導されかねません。したがって、ユーザーの OAuth トークンは、コードが動作する環境に決して持ち込まれてはいけません。
この課題に対する解決策として、v0 のサンドボックス向けに Snowflake リクエストプロキシを導入しました。これは Vercel Sandbox ファイアウォールを基盤に構築されています。サンドボックス内では通常の Snowflake クライアントを実行できますが、実際の認証情報は、実行環境の外側にあるサーバー側のプロキシでリクエスト時に解決されます。これにより、既存の Snowflake クライアントはサンドボックス内でそのまま動作可能となりながら、ユーザーの認証情報が生成コードに漏洩するリスクを排除できます。
より困難だったのは、どこでならプロキシが認証情報を安全に注入できるかを決定することでした。単純な実装として、出現するプレースホルダートークンを置き換える方法は、別の形で認証情報の漏洩を招くことになります。
分離だけでは秘密は守れない
v0 は生成されたアプリケーションを隔離されたサンドボックス内で実行しています。この分離は、信頼できないコードからシステム全体を守る役割を果たしますが、サンドボックス内部に存在する秘密情報自体を保護するものではありません。
生成されたアプリがファイルシステムからトークンを取得できる場合、そのトークンはログにコピーされたり、API 応答で返却されたり、生成されたクライアントコードに埋め込まれたり、別のホストへ送信されたりする可能性があります。サンドボックスの分離機能はアプリケーションのアクセス範囲を制限しますが、認証情報がすでにサンドボックス内部にある状態では無力です。
Snowflake の場合、この認証情報はユーザーが接続した際に使用した Snowflake ロールを指します。v0 は、ユーザーが権限を持つデータを使って探索や構築を行えるよう支援すべきですが、必要な場合に生成されたコードにプロバイダーの生認証情報を渡すべきではありません。
すべての Snowflake リクエストはプロキシを経由します
サンドボックスからは Snowflake に直接接続できません。内部で実行されるコードがユーザーの Snowflake アカウントホストへリクエストを送信すると、サンドボックスのファイアウォールがそのリクエストを v0 の Snowflake プロキシへ転送します。
ファイアウォールは各サンドボックス固有の認証局(CA)が発行した証明書で TLS を終端し、通常であれば暗号化される通信内容をプロキシが読み込んで書き換えることを可能にします。サンドボックスはこの認証局を自動的に信頼するため、Snowflake SDK や CLI はデフォルトの証明書検証設定(OCSP 対応含む)で動作します。その後、プロキシはサンドボックスからの OIDC トークンを検証し、該当する v0 チャットを検索してそのチャットに紐づくユーザーセッションを復元し、そのユーザー用の新しい Snowflake 認証情報を取得します。
サンドボックスは、トークンを含むリクエストが送信される先を決定しません。プロキシはサーバーサイドの認証情報から Snowflake アカウントホストを導出し、無効なアカウント URL を拒否します。これにより、認証情報は接続された Snowflake アカウントにスコープされ、生成されたコードによって提供されるホスト情報を信頼する必要がなくなります。
認証情報の露出なしでの互換性確保
暫定的な対応として、統合の最初のバージョンでは、ユーザーの実トークンをサンドボックス内の Snowflake トークンファイルに書き込んでいました。しかし、プロキシはこのアプローチを置き換えています。理想的には、サンドボックスからトークンファイルを完全に削除したいところですが、認証フローによって Snowflake クライアントが期待する認証情報の場所が異なるためです。
一部のリクエスト、例えば Snowflake SQL API の呼び出しでは、Authorization: Bearer ヘッダーを使用して認証を行います。一方、他のクライアントフローはローカルのトークンファイルを読み取り、ログインリクエストの一部としてトークンを送信します。一度クライアントが認証を完了すると、その後のリクエストには Snowflake が発行したセッショントークンが使用され、プロキシはこれらのリクエストをそのまま通過させます。
互換性を維持するため、v0 は依然としてサンドボックス内にトークンの形状をしたプレースホルダーを書き込みます。このプレースホルダーはアクセス権を与えない固定された公開の 72 バイト文字列であり、その唯一の役割は、既存の Snowflake SDK や CLI フローがトークンが存在するかのように動作できるようにすることです。プロキシはこのプレースホルダー自体に基づいてリクエストを承認することはありません。代わりに、プロキシはサンドボックスのサーバーサイドIDとユーザーチャットとのバインディングを用いてリクエストを承認します。
実際の OAuth トークン(ユーザーを表す再利用可能な認証情報)は、サンドボックス内に書き込まれることはありません。ログイン後、Snowflake はセッショントークンを発行し、これはサンドボックス内で有効になりますが、各トークンは単一の認証済みセッションにのみ紐付いています。
実務上、これらのセッションは短命です。生成されたアプリに含まれる Snowflake ヘルパーは、クエリごとに接続を切断するため、セッションも即座に終了します。チャットを閉じただけではセッションが自動的に終了するわけではありませんが、サンドボックスを破棄するとトークンも一緒に消去されます。また、Snowflake はデフォルトで 4 時間の非活動状態になるとサーバー側でセッションを失効させます。
リクエストがプロキシに到達した際、プロキシは認証情報をどこに、あるいは付けるべきかを判断します。
なぜ盲信的な置換が失敗するのか
プロキシの最初のバージョンでは、各リクエストからプレースホルダーを検索し、出現するすべての箇所に実際のトークンを置き換えていました。
問題点は、生成されたコードがリクエストの一部を制御しており、プレースホルダーが現れる場所もその一部であることです。例えば SQL 文は呼び出し元が制御するデータです。クエリ内にプレースホルダーが文字列リテラルとして含まれている場合、盲信的な置換を行うと、そのクエリに実際の OAuth トークンが含まれることになります。その後、データベースがこの文字列を返却すると、実際のトークンがクエリの出力としてサンドボックス内に戻り、本来守るべきトークンをプロキシが漏洩してしまう結果になります。
厳密なマッチングだけでは脆弱性を塞ぐことはできません。プロキシは攻撃者が操作したテキストに基づいて認証情報を注入してはいけません。代わりに、各 Snowflake リクエストのどのフィールドに認証情報が含まれているかを正確に把握する必要があります。
認証情報の注入先を限定する
Snowflake からのリクエストには主に 3 つのパターンがあり、それぞれで認証情報が配置される場所が異なります。
Snowflake SQL API へのリクエストの場合、プロキシは OAuth トークンを Authorization: Bearer ヘッダーに設定します。ボディ側は呼び出し元の制御下にある SQL のまま変更せず、そのまま通します。もしプレースホルダーが SQL ペイロード内に現れた場合、プロキシは Snowflake へ到達する前にリクエストを拒否し、ログとしてプレースホルダーの誤用と記録します。
Snowflake ログインリクエストの場合、プロキシは JSON リクエストボディを解析し、構造的にログイントークンフィールドにトークンを設定した上で、再度シリアライズして送信します。もしリクエストボディ内の他の場所にプレースホルダーが残っている場合、プロキシは安全のためにリクエストを拒否します。
ログイン後のセッションリクエストでは、Snowflake が管理するセッショントークンで認証が行われるため、プロキシが注入を行う必要はありません。
安全のために失敗する(Failing Closed)
以下のいずれかの条件に該当する場合、プロキシはリクエストを拒否します。
- サンクボックスがチャットにバインドされていない
- ユーザースコープの認証情報を取得できない
- Snowflake アカウントホストを特定できない
- プレースホルダーが許可された認証フィールド以外に現れる
- 構造化されたログインボディを安全に解析できない
リクエストは検査前にバウンド(制限)されるため、圧縮されたまたは過大なボディがプロキシを無制限のパーサーに変えることがありません。すべてのプロキシ化されたリクエストには、アップストリームの結果、ステータス、所要時間、注入場所を含む観測イベントが発行されます。これにより、機密情報を露出させることなく、不正使用や統合障害を把握できます。
リフレッシュとデプロイの処理
トークンリフレッシュは、環境に依存するブラウザクッキーに頼ることはできません。なぜなら、プロキシからのリクエストはユーザーのブラウザセッションではなく、サンドボックスから発生するためです。v0 はユーザーセッションをサンドボックスにバインドし、そのバインドから OAuth 認証情報を発行および更新します。
公開パスでは、Snowflake CLI のフローが同じ認証境界で利用されます。デプロイ時にはプレースホルダーを認証ファイルに書き込むことはできますが、実際のトークンを記述することは決してありません。
デプロイ後、アプリケーションは Snowpark Container Services 内で実行され、自身のサービスユーザーとして認証します。このトークンは Snowflake が管理・自動回転するものであり、/snowflake/session/token にマウントされます。これにより、ユーザーの OAuth トークンと v0 プロキシは関与しなくなります。
結果としてのセキュリティ境界
ユーザーにとってこれらの仕組みは目に見えません。Snowflake を接続し、v0 にスキーマの検査やアプリ構築を依頼し、結果をプレビューして準備ができたらデプロイするだけです。
このセキュリティモデルは以下の 5 つのルールに基づいています:
- クレデンシャルの注入は、各エンドポイントの認証フィールドのみで行われます
- 認証フィールド以外でプレースホルダーを使用するリクエストは拒否され、ログに記録されます
トークン付与リクエストは、接続された Snowflake アカウントのホストのみへ送信されます。
認証情報はサーバー側で発行・更新され、生成されたコードはユーザーの OAuth トークンを一切読み込むことなく Snowflake を利用します。
本プロキシを本番環境に導入して最初の 15 日間で約 13,000 リクエストが処理されましたが、プレースホルダーの誤使用による拒否はゼロでした。
このプロキシは Snowflake のために構築されましたが、同様の課題は他の統合でも共通しています。生成されたコードが認証を行う際、ユーザーの長期有効な認証情報を取得できないケースが多いためです。重要なのは、任意のリクエストデータを改変するのではなく、プロトコルで定義された認証フィールドにのみ認証情報を注入することです。
v0 の Snowflake 統合は現在ベータ版として利用可能です。詳細は統合ドキュメントをご覧ください。
原文を表示
AI-generated applications often need to authenticate to external services on behalf of their users. That creates a problem: generated code shouldn't have access to the user's credentials.
We faced that decision when building the v0 Snowflake integration. It lets users connect Snowflake, inspect schemas, query data, and generate applications that run against their warehouses. That generated code has to authenticate to Snowflake, but it is written by a model and runs without human review, and prompt injection can steer it into exfiltrating whatever it can read, so the user's OAuth token should never enter the environment the code runs in.
We solved this with a Snowflake request proxy for v0 sandboxes, built on the Vercel Sandbox firewall. The sandbox can run normal Snowflake clients, but the real credential is resolved at request time in a server proxy outside the sandbox runtime. This lets existing Snowflake clients work inside the sandbox without exposing the user's credential to generated code.
The harder problem was deciding where the proxy could safely inject the credential. The obvious implementation, replacing a placeholder token wherever it appears, introduces another credential leak.
Isolation doesn't protect secrets
v0 runs generated applications in isolated sandboxes. Isolation protects the rest of the system from untrusted code, but it doesn't protect secrets inside the sandbox.
If a generated app can read a token from the filesystem, that token can be copied into logs, returned in an API response, embedded into generated client code, or sent to another host. Sandbox isolation limits what the application can access, but it does not help once the credential itself is available inside the sandbox.
For Snowflake, the credential represents the Snowflake role the user connected with. v0 should be able to help the user explore and build with data they are authorized to access, but generated code should not receive raw provider credentials just because it needs one.
Every Snowflake request goes through the proxy
The sandbox cannot talk to Snowflake directly. When code inside it sends a request to the user's Snowflake account host, the sandbox firewall forwards that request to the v0 Snowflake proxy:
The firewall terminates TLS with a certificate authority unique to each sandbox, which lets the proxy read and rewrite traffic that would otherwise be encrypted. The sandbox trusts that certificate authority automatically, so the Snowflake SDK and CLI run with their default certificate validation, OCSP included. The proxy then verifies the sandbox's OIDC token, looks up the v0 chat the sandbox belongs to, restores the user session bound to that chat, and retrieves a fresh Snowflake credential for that user.
The sandbox does not choose where a token-bearing request is sent. The proxy derives the Snowflake account host from the server-side credential and rejects invalid account URLs, keeping the credential scoped to the connected Snowflake account rather than trusting host information supplied by generated code.
Compatibility without credential exposure
As a stopgap, the first version of the integration wrote the user's real token into the sandbox's Snowflake token files. The proxy replaced that approach. Ideally, we would remove token files from the sandbox entirely, but Snowflake clients expect credentials in different locations depending on the authentication flow.
Some requests, such as Snowflake SQL API calls, authenticate with an Authorization: Bearer header. Other client flows read local token files and send the token as part of a login request. Once the client has authenticated, subsequent requests use Snowflake-issued session tokens, and the proxy passes those requests through untouched.
To preserve compatibility, v0 still writes a token-shaped placeholder into the sandbox. The placeholder is a fixed, public, 72-byte string that grants no access, and its only job is to let existing Snowflake SDK and CLI flows behave as if a token exists. The proxy never authorizes a request based on the placeholder itself. Instead, the proxy authorizes the request using the sandbox's server-side identity and its binding to the user's chat.
The real OAuth token, the reusable credential that represents the user, is never written into the sandbox. Snowflake does issue session tokens that live inside the sandbox after login, but each of those belongs to a single authenticated session.
Those sessions are short-lived in practice. The Snowflake helper in generated apps destroys its connection after each query, which ends the session right away. Closing the chat does not end a session on its own, but tearing down the sandbox takes the token with it, and Snowflake expires the session server-side after four hours of inactivity by default. When a request reaches the proxy, it decides whether and where to attach the credential.
Why blind replacement fails
The first version of the proxy searched each request for the placeholder and swapped in the real token wherever it appeared:
The problem is that generated code controls parts of the request, including where the placeholder can appear. A SQL statement, for example, is caller-controlled data. If a query contains the placeholder as a string literal, blind replacement turns that query into one containing the real OAuth token. If the database then returns that string, the real token comes back into the sandbox as query output, and the proxy would have leaked the token it was built to keep out.
Stricter matching does not close the hole. The proxy shouldn't inject a credential based on attacker-controlled text. Instead, it needs to know which field in each Snowflake request carries authentication.
Injecting credentials only into authentication fields
Snowflake requests reach the proxy in three shapes, and each shape puts authentication in a different place.
For Snowflake SQL API requests, the proxy sets the OAuth token on the Authorization: Bearer header. The body remains caller-controlled SQL and is not rewritten. If the placeholder appears in the SQL payload, the proxy rejects the request before it reaches Snowflake and logs it as placeholder misuse.
For Snowflake login requests, the proxy parses the JSON request body, sets the token structurally at the login token field, and serializes the body again. If the placeholder remains anywhere else in the request body, the proxy fails closed.
Post-login session requests authenticate with Snowflake-managed session tokens, so the proxy has nothing to inject.
Failing closed
The proxy rejects any request where:
The sandbox is not bound to a chat
No user-scoped credential can be obtained
The Snowflake account host cannot be derived
The placeholder appears outside an approved authentication field
A structured login body cannot be parsed safely
Requests are also bounded before inspection so that compressed or oversized bodies cannot turn the proxy into an unbounded parser. Every proxied request emits an observability event with the upstream outcome, status, duration, and injection location. This surfaces misuse and integration failures without exposing secrets.
Handling refresh and deploy
Token refresh cannot depend on an ambient browser cookie, because proxy requests originate from the sandbox, not from the user's browser session. v0 binds the user session to the sandbox instead, and the proxy mints and refreshes OAuth credentials from that binding.
The publish path uses Snowflake CLI flows with the same credential boundary. Deploy can write the placeholder into credential files, but never the real token.
After deployment, the application runs in Snowpark Container Services and authenticates as its own service user, with a token that Snowflake manages and rotates automatically, mounted at /snowflake/session/token. The user's OAuth token and the v0 proxy are no longer involved.
The resulting security boundary
For users, none of this is visible. Connect Snowflake, ask v0 to inspect schemas or build an app, preview the result, and deploy when it's ready.
The security model rests on five rules:
Credential injection happens only at each endpoint's authentication fields
Requests that use the placeholder outside an authentication field are rejected and logged
Token-bearing requests go only to the connected Snowflake account host
Credentials are minted and refreshed server-side
Generated code uses Snowflake without ever reading the user's OAuth token
In its first 15 days in production, the proxy attached credentials server-side for roughly 13,000 requests and recorded zero placeholder-misuse rejections.
Although we built this proxy for Snowflake, the same problem applies to other integrations: generated code often needs to authenticate without receiving the user's long-lived credential. The important part is to inject credentials only into protocol-defined authentication fields, rather than rewriting arbitrary request data.
The v0 Snowflake integration is now available in beta. Get started by reading the integration docs.
Read more
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み