İçeriğe atla

Rehber

LLM API anahtarınız büyük ihtimalle frontend paketinizde duruyor

VITE_ ve NEXT_PUBLIC_ değişkenleri neden tarayıcıya düşer, bir build’de sızmış OpenAI ya da Anthropic anahtarı nasıl aranır ve sorunu çözen sunucu route’u nasıl yazılır.

· 7 dk okuma · Yazan: vetkit ekibi

Camdan bir tarayıcı penceresinin içinde sergilenen büyük bir anahtar; bir robot hiçbir şeyin farkında olmadan camı parlatırken bir geliştirici telaşla işaret ediyor ve eldivenli bir el anahtara uzanıyor.Camdan bir tarayıcı penceresinin içinde sergilenen büyük bir anahtar; bir robot hiçbir şeyin farkında olmadan camı parlatırken bir geliştirici telaşla işaret ediyor ve eldivenli bir el anahtara uzanıyor.

Bir asistandan React uygulamanıza bir sohbet kutusu eklemesini istiyorsunuz. OpenAI SDK’sını kuruyor, anahtarı import.meta.env.VITE_OPENAI_API_KEY üzerinden okuyor ve özellik ilk denemede çalışıyor. Sorun şu ki, tarayıcının geliştirici araçlarını açan, anahtarı JavaScript’inizde bulan ve sizin hesabınız üzerinden istek göndermeye başlayan herkes için de çalışıyor.

Bu, yapay zekâ destekli projelerde en sık gördüğümüz hatalardan biri ve code review’da kolayca gözden kaçıyor, çünkü yanlış görünen hiçbir şey yok. Anahtar koda gömülmemiş. git tarafından doğru şekilde yok sayılan bir .env dosyasında duruyor. Kod onu bir ortam değişkeninden okuyor; herkesin yapmanız gerektiğini söylediği şey de bu.

Nasıl oluyor

Frontend build araçlarının çalışma zamanında ortam değişkenleri yoktur. Tarayıcı statik bir paket çalıştırır. Bu yüzden kodunuz import.meta.env.VITE_SOMETHING ya da process.env.NEXT_PUBLIC_SOMETHING okuduğunda, bundler build sırasında bu ifadeyi değerin kendisiyle değiştirir. Build’den sonra ortada bir değişken kalmaz; yalnızca her ziyaretçinin indirdiği bir JavaScript dosyasında duran bir string kalır.

Framework’ler bu konuda açık. Next.js, public değişkenlerin tarayıcıya gönderilen her JavaScript’e gömüldüğünü söylüyor. Vite, istemci koduna yalnızca VITE_ önekli değişkenleri açıyor; tam da diğerlerini yanlışlıkla sızdırmayasınız diye. Önek, bilinçli bir tercihtir. Bir asistan öneksiz ad tarayıcıda çalışmadığı için VITE_OPENAI_API_KEY adını seçtiğinde, bu tercihi sizin yerinize yapmış olur.

Daha sessiz bir varyantı da var. Bazı başlangıç şablonları anahtarı Vite’ın define seçeneğiyle pakete kopyalar; örneğin vite.config.ts içinde 'process.env.GEMINI_API_KEY': JSON.stringify(...). Değişkenin public öneki olmadığı için güvenli görünür, ama etkisi tamamen aynıdır.

Resmî SDK’lar bunu engellemeye çalışıyor. OpenAI Node SDK’sı, dangerouslyAllowBrowser: true vermedikçe tarayıcıda çalışmayı reddediyor ve README’si nedenini açıklıyor: bu, gizli kimlik bilgilerinizi istemci tarafı kodda açığa çıkarır. Asistanlar bazen bir hatayı ortadan kaldırmak için bu bayrağı ekliyor. Genel HTTP çağrıları ya da tarayıcı kontrolü olmayan SDK’lar gibi diğer istemciler ise sizi hiç uyarmıyor.

Hangi değişkenler herkese açık

Bir değişken şu öneklerden biriyle başlıyorsa, değerinin uygulamanızı yükleyen herkes tarafından okunabildiğini varsayın:

  • Vite’ta (ve onun üzerine kurulu framework’lerde) VITE_
  • Next.js’te NEXT_PUBLIC_
  • Create React App’te REACT_APP_
  • Expo’da ve onunla geliştirilen React Native uygulamalarında EXPO_PUBLIC_
  • Vue CLI’da VUE_APP_, Gatsby’de GATSBY_
  • Astro’da ve SvelteKit’in public env modüllerinde PUBLIC_

Bu önekler, bir analitik site kimliği ya da kendi API’nizin adresi gibi zaten herkese açık olması amaçlanan değerler için sorun değildir. Bir sağlayıcı gizli anahtarı için ise asla uygun değildir: OpenAI, Anthropic, Google Gemini, Mistral, bir Stripe secret key, bir veritabanı service role anahtarı.

Build’inizi kontrol edin

En hızlı test, uygulamayı build edip çıktıda anahtarın önekini aramaktır. OpenAI proje anahtarları sk-proj-, Anthropic anahtarları sk-ant-, Google API anahtarları ise AIza ile başlar.

bash
npm run build# Vite writes to dist/, Next.js static chunks live in .next/static/grep -rEo "sk-(proj|ant)-[A-Za-z0-9_-]{8}|AIza[0-9A-Za-z_-]{8}" dist .next/static 2>/dev/null

Bu komut bir şey yazdırıyorsa, anahtar paketinizdedir. Yayındaki bir siteye de aynı şekilde bakın: geliştirici araçlarını açın, Sources ya da Network sekmesine gidin ve yüklenen script’lerde arama yapın. Siz otuz saniyede bulabiliyorsanız, siteleri tam da bunu arayarak tarayan bir script de bulabilir.

Çözüm

Sağlayıcı çağrısını kontrol ettiğiniz bir sunucuya taşıyın ve tarayıcının onun yerine bu sunucuyla konuşmasını sağlayın. Next.js’te bu bir route handler’dır. Bir Vite single-page uygulamasında aynı host üzerinde bir serverless fonksiyon olabilir. Anahtar öneksiz bir adla kalır; böylece bundler onu hiçbir zaman koda gömmez.

app/api/chat/route.ts
import OpenAI from 'openai'; // Server-only: no NEXT_PUBLIC_ prefix, never shipped to the browser.const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); export async function POST(request: Request) {  const user = await requireSignedInUser(request); // your auth check  await rateLimit(user.id); // your limiter: per user, per minute   const { message } = await request.json();  if (typeof message !== 'string' || message.length > 4000) {    return Response.json({ error: 'Invalid message' }, { status: 400 });  }   const response = await client.responses.create({    model: 'gpt-5-mini',    input: message,    max_output_tokens: 800,  });  return Response.json({ reply: response.output_text });}

Çağrının çevresindeki üç ayrıntı, onu taşımak kadar önemli. Yetki kontrolü olmadan route’unuz modele açılan ücretsiz ve anonim bir proxy hâline gelir; bu, birkaç ek adımla yaşanan aynı sızıntıdır. Rate limit olmadan, oturum açmış tek bir kullanıcı bile yüksek bir fatura çıkarabilir. Çıktı sınırı olmadan, tek bir istek özelliğinizin ihtiyaç duyduğundan çok daha fazla token üretebilir.

Model adını ve sınırları da sunucuda tutun

Model adını ya da max_output_tokens değerini tarayıcı gönderiyorsa, bir ziyaretçi bunları değiştirebilir. Bu değerlere sunucu tarafında karar verin ve istemciden yalnızca kullanıcının mesajını kabul edin.

Anahtar zaten yayına çıktıysa

Değişkeni koddan kaldırmak tek başına işe yaramaz. Eski build’ler tarayıcılarda ve CDN’lerde önbellekte kalır ve anahtar çoktan kopyalanmış olabilir. Onu ele geçirilmiş kabul edin:

  1. Sağlayıcının panelinde yeni bir anahtar oluşturun ve eskisini iptal edin.
  2. Yeni anahtarı yalnızca sunucu tarafı ortam ayarlarına, public öneki olmayan bir adla koyun.
  3. Sağlayıcının kullanım sayfasında tanımadığınız trafik olup olmadığını kontrol edin ve bir harcama limiti belirleyin.
  4. Yeniden deploy edin, ardından yeni paketin temiz olduğunu doğrulamak için yukarıdaki build aramasını tekrarlayın.

Anahtar herhangi bir zamanda git’e commit edildiyse, dosyayı silmiş olsanız bile anahtarı yenileyin. Git geçmişi eski değeri saklar.

Yayına çıkmadan yakalayın

Bu hatayı otomasyonla ortadan kaldırmak kolay. Bir secret scanner depoya commit edilmiş anahtarları yakalar. Ancak yok sayılan bir .env dosyasında duran bir anahtarı göremez; bu yüzden kodu okuyup public bir değişkenin bir LLM istemcisine aktarıldığını fark eden bir kontrole de ihtiyacınız var.

Bu ikinci kalıp, vetkit’in tam taramalarındaki LLM ve ajan güvenliği kurallarından biridir: istemci tarafı bir ortam değişkeninden (örneğin import.meta.env.VITE_* ya da process.env.NEXT_PUBLIC_*) gelen ve tarayıcı kodunda bir LLM SDK istemcisine ulaşan bir değeri işaretler ve çağrının sunucu tarafına nasıl taşınacağını açıklar. vetkit’in ücretsiz Security Quick Scan’i commit edilmiş gizli bilgileri, bilinen açıkları olan bağımlılıkları ve temel güvenlik yapılandırmasını kapsar. Hiçbiri, elde edebileceğiniz en doğrudan kanıt olan kendi build çıktınıza hızlıca bakmanın yerini tutmaz.

Erken erişim talep edin

Ekibinizden ve denetlemek istediğiniz depolardan kısaca bahsedin. Ekipleri küçük gruplar halinde aramıza alıyoruz; size e-postayla dönüş yapacağız.

Bu bilgileri yalnızca talebinize yanıt vermek için kullanırız. Gizlilik politikası