Authenticating generated code without handing over credentials
Applications built by AI models often need to reach external services on the user's behalf. That requirement collides with a hard security constraint: code generated by a model, running without human review, should never hold the user's actual credentials. Prompt injection alone is enough reason to keep long-lived tokens out of that environment.
The v0 Snowflake integration faced exactly this problem. Users connect Snowflake, inspect schemas, query data, and generate applications that run against their warehouses. The generated code needs to authenticate to Snowflake, but the token that authorized the connection cannot enter the runtime where that code executes.
The solution is a request proxy for v0 sandboxes, built on the Vercel Sandbox firewall. Sandboxed code runs standard Snowflake clients without modification, but the real credential is resolved at request time in a server-side proxy outside the sandbox. No OAuth token ever appears inside the generated code's environment.
Why sandbox isolation alone is not enough
Sandboxes protect the rest of the system from untrusted code. They do not protect secrets that live inside the sandbox. If generated code can read a token from the filesystem, it can also embed that token in an API response, write it into logs, send it to another host, or bake it into client code it produces. Once a credential is present in the sandbox, isolation no longer helps.
For Snowflake, the credential maps to the role the user connected with. The generated application should be able to explore and build with data the user is authorized to access, but it should never receive the provider's raw credential just because it needs one.
The proxied request path
Code inside the sandbox cannot reach Snowflake directly. When it sends a request to the user's Snowflake account host, the sandbox firewall forwards that traffic to the v0 Snowflake proxy:

The firewall terminates TLS with a certificate authority unique to each sandbox, allowing the proxy to read and rewrite otherwise encrypted traffic. The sandbox automatically trusts that certificate authority, so Snowflake SDKs and the CLI work with their default certificate validation intact, including OCSP. 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 fetches a fresh Snowflake credential for that user.
network-policy/index.ts
return {
[host]: [
// absolute URL, e.g. https://v0.app/chat/api/sandbox-proxy/snowflake
{ forwardURL: getSandboxRequestProxyForwardURL('snowflake') },
],
}
The sandbox has no say in where a token-bearing request is sent. The proxy derives the Snowflake account host from the credential it holds server-side and rejects invalid account URLs. The credential stays scoped to the connected Snowflake account, rather than trusting host information supplied by generated code.
Keeping client compatibility without a real token
An earlier stopgap version wrote the user's actual token into the sandbox's Snowflake token files. The proxy replaces that approach. Ideally, token files would not exist in the sandbox at all, but Snowflake clients expect credentials in different locations depending on the authentication flow.
Snowflake SQL API calls, for instance, authenticate with an Authorization: Bearer header. Other client flows read local token files and send the token as part of a login request. After authentication, subsequent requests rely on Snowflake-issued session tokens, which the proxy passes 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. Its purpose is simply 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. It authorizes using the sandbox's server-side identity and its binding to the user's chat.
The real OAuth token — the reusable credential representing 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 every query, ending the session immediately. Tearing down the sandbox removes any session token with it, and Snowflake expires sessions server-side after four hours of inactivity by default.
Why blind replacement leaks
The first proxy version searched each request for the placeholder and swapped in the real token wherever it appeared:
network-policy/snowflake-proxy.ts
const text = await request.text();
const patched = text.replaceAll(placeholder, realToken);
request = new Request(request, { body: patched });
The flaw is that generated code controls parts of the request, including where the placeholder may appear. A SQL statement is caller-controlled data. If a query contains the placeholder as a string literal, blind replacement rewrites that query to contain the real OAuth token. Should the database return that string, the actual token flows back into the sandbox as query output — the proxy leaks the very credential it was designed to protect.
Stricter matching does not close that hole. Credential injection cannot be based on attacker-controlled text. The proxy has to know which field in each Snowflake request carries authentication.
Injecting only into authentication fields
Snowflake requests reach the proxy in three distinct shapes, each with authentication in a different place.
For Snowflake SQL API requests, the proxy sets the OAuth token on the Authorization: Bearer header. The request body stays caller-controlled SQL and is never rewritten. If the placeholder shows up in the SQL payload, the proxy rejects the request before it reaches Snowflake and logs it as placeholder misuse.
For login requests, the proxy parses the JSON body, sets the token structurally at the login token field, and serializes the body again. If the placeholder survives anywhere else in the body, the proxy fails closed.
For post-login session requests, Snowflake-managed session tokens are in play, so the proxy has nothing to inject.
Failing closed
The proxy rejects any request when:
- 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; or
- a structured login body cannot be parsed safely.
Requests are bounded before inspection so compressed or oversized bodies cannot turn the proxy into an unbounded parser. Every proxied request emits an observability event containing the upstream outcome, status, duration, and injection location — enough to surface misuse and integration failures without exposing secrets.
Refresh, deploy, and the resulting boundary
Token refresh cannot depend on an ambient browser cookie, since proxy requests originate from the sandbox rather than 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 may 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 Snowflake manages and rotates automatically, mounted at /snowflake/session/token. Neither the user's OAuth token nor the v0 proxy is involved at that point.
The security model rests on five rules:
- credential injection happens only at each endpoint's authentication fields;
- requests using 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; and
- generated code uses Snowflake without ever reading the user's OAuth token.
The proxy attached credentials server-side for roughly 13,000 requests in its first 15 days in production and logged zero placeholder-misuse rejections.
The same principle applies to other integrations: generated code often must authenticate without receiving the user's long-lived credential. The key is to inject credentials only into protocol-defined authentication fields, never by rewriting arbitrary request data.
Contributors




