ai newsDo., 15. Okt.
EilmeldungAlibaba Qwen N1 AI glasses go on sale in China

Sicherheitslücke: KI-Editoren empfehlen risky Offenlegung von OpenAI Keys

Der übliche Fix dangerouslyAllowBrowser:true verlagert den OpenAI API Key in öffentliche JavaScript-Bundles und setzt ihn jedem Website-Besucher aus. Das Muster ist als CWE-522 eingestuft.

dev.toDienstag 19:31 · 13.10.DE5 Min. Lesezeit
Bild: dev.to

Posted on Oct 13 • Originally published at safeweave.dev

You are building an AI feature in a Vite or Next.js app. You install openai, create the client in a React component, and the page crashes with an error that starts "It looks like you're running in a browser-like environment." You paste it into Cursor or Claude Code.

The answer comes back in seconds: add dangerouslyAllowBrowser: true, and since the browser cannot read process.env.OPENAI_API_KEY, rename it to VITE_OPENAI_API_KEY. The chat works. You ship.

Your key is now public. The error you silenced was the SDK telling you exactly that.

The vulnerable pattern is an LLM client constructed in browser code with a key from a public environment variable. Here is the shape that comes back after the AI "fixes" the error in a Vite + React app:

// src/components/Chat.tsx // CWE-522: Insufficiently Protected Credentials import OpenAI from ' openai '; const openai = new OpenAI ({ apiKey: import. meta. env. VITE_OPENAI_API_KEY, dangerouslyAllowBrowser: true, }); export async function ask ( prompt: string ) { const res = await openai. chat. completions. create ({ model: ' gpt-4o-mini ', messages: [{ role: ' user ', content: prompt }], }); return res. choices [ 0 ]. message. content; } Enter fullscreen mode Exit fullscreen mode The Next.js version is the same with process.env.NEXT_PUBLIC_OPENAI_API_KEY in a 'use client' component.

Two things make this a leak, not a risk:

No exploit is needed. The attacker is anyone who opens DevTools, and the bill is yours.

AI editors optimize for making the error disappear, and the error message itself contains the option that does it. OpenAI's SDK refuses to run in a browser by default, explains why, and names dangerouslyAllowBrowser as the switch. The Anthropic TypeScript SDK has the same option. From the model's point of view, the fix is printed right there in the stack trace.

The second half follows from the first. Once the client lives in the browser, process.env.OPENAI_API_KEY is undefined, so the next error is a missing key. The shortest path is the public prefix, because that is what Vite and Next.js docs say to use when the browser needs a variable.

Plenty of tutorials and demo repos do exactly this, because it is the fastest way to get a chat box on screen. The OpenAI SDK README even lists cases where the flag "might not be dangerous": internal tools with trusted users, keys with very limited scope, short-lived dev keys. A production app with public sign-up fits none of them.

Remove dangerouslyAllowBrowser, rotate the exposed key, and move the model call into a server route that authenticates the caller, rate limits per user, and caps output tokens. The browser calls your route, never the provider.

// app/api/chat/route.ts (server only) import OpenAI from ' openai '; import { NextResponse } from ' next/server '; import { getSession } from ' @/lib/auth '; // your auth import { rateLimit } from ' @/lib/rate-limit '; // e.g. Upstash, Redis const openai = new OpenAI ({ apiKey: process. env. OPENAI_API_KEY }); // no NEXT_PUBLIC_ export async function POST ( req: Request ) { const session = await getSession (); if (! session ) return NextResponse. json ({ error: ' unauthorized ' }, { status: 401 }); if (! ( await rateLimit ( session. userId ))) { return NextResponse. json ({ error: ' slow down ' }, { status: 429 }); } const { prompt } = await req. json (); if ( typeof prompt!== ' string ' || prompt. length > 4000 ) { return NextResponse. json ({ error: ' bad input ' }, { status: 400 }); } const res = await openai. chat. completions. create ({ model: ' gpt-4o-mini ', max_tokens: 500, messages: [{ role: ' user ', content: prompt }], }); return NextResponse. json ({ text: res. choices [ 0 ]. message. content }); } Enter fullscreen mode Exit fullscreen mode The client component only talks to your route:

// 'use client' const res = await fetch ( ' /api/chat ', { method: ' POST ', headers: { ' Content-Type ': ' application/json ' }, body: JSON. stringify ({ prompt }), }); const { text } = await res. json (); Enter fullscreen mode Exit fullscreen mode A Vite app has no server of its own, so the route goes in whatever backend you already have: a Supabase Edge Function, a Cloudflare Worker, a Vercel or Netlify function, or a small Express server. The rules are the same. The key lives in that backend's secrets, never in a VITE_ variable.

Q: Is dangerouslyAllowBrowser: true ever safe? A: Only where every user is trusted, like an internal tool, or with a key that has a tiny scope and a short life. For a public app, no. Anyone who loads the page can read the key.

Q: Can I hide the key with obfuscation or environment variables? A: No. Anything the browser needs to send a request, the user can read. VITE_ and NEXT_PUBLIC_ variables are inlined into the bundle as plain strings, and the key also appears in every request header.

Q: My key was exposed. Is deleting it from the code enough? A: No. Rotate it in the provider dashboard first. Old builds, git history and caches keep the original value.

I've been running SafeWeave for this. Its cloud repo scans (Cloud and Cloud Plus plans) flag an AI SDK client created with dangerouslyAllowBrowser: true and an LLM key read from a NEXT_PUBLIC_, VITE_, EXPO_PUBLIC_ or REACT_APP_ variable, and the live app scan checks your deployed site's code for provider keys. It also hooks into Cursor and Claude Code as an MCP server. Even without it, a grep of your build output for key prefixes and one look at the Network tab will tell you if you have this problem. The important thing is to look before someone else does.

Originaltitel: Why Cursor Ships Your OpenAI Key to Every Visitor (CWE-522)Quelle: dev.to (dev.to). Original öffnen