decizii / securitate / chei API

Cum păstrezi cheile API
în siguranță.

Greșeala #1 a vibe coderilor: scapi cheia API. Bot-uri scrapează GitHub și Discord în continuu. În prima săptămână de proiect, 80% dintre developeri au făcut deja măcar o greșeală din cele 5 de mai jos. Hai să le facem vizibile — ca să te eviți.

Lectură: 14 minPentru: oricine folosește .envTonul: direct, fără jargon
01 · mental model

Env variables există în 4 lumi diferite.

Aceeași variabilă — același nume — poate să existe (sau NU) în 4 contexte cu reguli complet diferite. 80% din confuziile despre „de ce nu merge cheia mea?" și „de ce e expusă cheia mea?" vin din amestecul ăsta.

01

Local dev

~/.env.local · laptop-ul tău

Fișierul pe care îl vezi tu când lucrezi local. Trăiește pe disk-ul tău, nu pleacă nicăieri (dacă l-ai .gitignore-uit).

vizibil doar local
02

Build time

pnpm build / next build

Next.js citește valorile la moment de build și le „îngheață” în output. Variabilele cu prefix NEXT_PUBLIC_ ajung în bundle JS livrat la browser.

NEXT_PUBLIC_ → public
03

Server runtime

process.env în Node.js

Pe serverul aihost (sau Vercel), variabilele se citesc la fiecare request. Server actions, API routes, getServerSideProps — toate aici.

private la server
04

Client browser

window. + bundle JS

Browserul vede DOAR ce a fost compilat în bundle (NEXT_PUBLIC_ + ce-i pasezi explicit). Orice e aici e pentru ORICINE deschide DevTools.

vizibil oricui
Regula de aur: dacă variabila ajunge în lumea 4 (browser), e publică. Nu există „scuns parțial". Nu există „obscured". Cineva care apasă F12 o vede instant.
02 · capcana clasică

NEXT_PUBLIC_ — prefixul care te omoară.

Next.js are o regulă simplă: orice variabilă cu prefix NEXT_PUBLIC_ e inclusă în bundle-ul JavaScript livrat la browser. Asta înseamnă vizibilă oricui deschide DevTools. Iată ce poți / NU poți pune cu acest prefix.

VariabilăCu NEXT_PUBLIC_?Detaliu
Supabase ANON keyDA, sigurDa — e public by design. RLS policies la nivel DB protejează datele.
URL aplicației (NEXT_PUBLIC_APP_URL)DA, sigurDa — e oricum vizibil în adresă.
Google Analytics / GTM IDDA, sigurDa — astea sunt PUBLICE prin natură (rulează în browser).
Stripe Publishable keyDA, sigurDa — există anume pentru client (vs. secret key care NICIODATĂ).
Anthropic / OpenAI / Gemini API keyNU, NICIODATĂNU. Cu NEXT_PUBLIC_ → cheia ta în bundle JS → atacator face drain la cont $$.
Supabase SERVICE_ROLE keyNU, NICIODATĂNU. Bypass total RLS. Cu acces public = full DB read+write+delete.
DATABASE_URLNU, NICIODATĂNU. Conține user+parolă Postgres. Atacator se conectează direct.
JWT_SECRET / BETTER_AUTH_SECRETNU, NICIODATĂNU. Cu el atacatorul semnează tokens valide de oricare user.
GitHub Personal Access TokenNU, NICIODATĂNU. Permite push/pull la repo-urile tale, ștergeri, totul.
Test rapid: deschide site-ul tău live, F12 → Sources → caută ID-ul cheii (ex. „sk-ant" sau „sb-"). Dacă apare — cheia e publică. Rotezi acum.

Demo concret: cum verifici dacă ai scăpat ceva

# 1. Descarci HTML-ul + JS-ul bundle-ului curl -s https://site-ul-tău.com > page.html # 2. Cauți pattern-uri suspecte curl -s https://site-ul-tău.com | grep -oE 'sk-[a-zA-Z0-9_-]+' curl -s https://site-ul-tău.com | grep -oE 'eyJ[a-zA-Z0-9_-]+' # 3. Și pentru fișierele JS bundled curl -s https://site-ul-tău.com/_next/static/chunks/app-XXX.js | grep 'sk-ant' # Dacă găsești ceva: ROTEAZĂ cheia ACUM și apoi fix codul.
03 · glosar amenințări

10 chei comune și ce face atacatorul cu fiecare.

Severity bazat pe pagube reale. Roșu sus = ești fript. Verde = poți lăsa public (cu RLS configurat). Pentru fiecare cheie: dacă e safe public, ce face și exact ce-ți strică un atacator.

ANTHROPIC_API_KEYcritic
Public? NU public, NU NEXT_PUBLIC_

Cheia ta către API-ul Anthropic (Claude). Plătită per-token de tine, fără cap.

Dacă scapă: Atacator scapată = drain cont. 1000-5000$ overnight cu un script care face inferență paralelă. Anthropic NU returnează banii.
OPENAI_API_KEYcritic
Public? NU public, NU NEXT_PUBLIC_

Cheia OpenAI (GPT, Codex, embeddings). La fel — pay-as-you-go, fără limită implicită.

Dacă scapă: Aceeași poveste: atacatorul rulează GPT-4 turbo într-un loop. 500-3000$ noaptea. Setezi „spending limit” în dashboard ca prim safety net.
GEMINI_API_KEYatenție
Public? NU public, NU NEXT_PUBLIC_

Cheia Google AI Studio / Gemini. Free tier mai generos, dar tot are quota.

Dacă scapă: Free tier blocat instant la abuz. Pe paid tier, drain $$ similar cu OpenAI/Anthropic.
SUPABASE_ANON_KEYok public
Public? DA — folosește prefix NEXT_PUBLIC_

Cheia publică Supabase. E proiectată să fie în browser. Securitatea reală vine din RLS policies pe tabele.

Dacă scapă: Singură nu face mare lucru — DOAR dacă RLS-urile tale sunt slabe. Verifică-le. Dacă o tabelă n-are RLS = cheia anon vede tot.
SUPABASE_SERVICE_ROLE_KEYcritic
Public? NICIODATĂ public

Cheia super-admin Supabase. BYPASS total la RLS. Toate restricțiile sunt ignorate.

Dacă scapă: Cea mai periculoasă cheie din stack-ul Supabase. Cu ea: read all data, modify all, delete all, plus reset password la useri. Data breach total în 5 minute.
DATABASE_URLcritic
Public? NICIODATĂ public

postgres://user:parolă@host:5432/db — connection string complet. Cu el atacatorul se conectează direct la DB.

Dacă scapă: DROP TABLE users; · exfiltrare orice tabel · INSERT user admin propriu. Game over fără logging suplimentar la nivel DB.
BETTER_AUTH_SECRET / JWT_SECRETcritic
Public? NICIODATĂ public

Cheia cu care semnezi cookie-urile de sesiune. Dacă o ai, generezi sesiuni valide pentru orice user.

Dacă scapă: Atacator generează cookie semnat „eu sunt admin” → are acces ca admin în panel-ul tău. Toate userii săi sunt invalidați dacă o rotezi (logout forțat).
GITHUB_TOKEN (PAT)critic
Public? NICIODATĂ public

Personal Access Token GitHub — conform scope-ului, poate face push/pull/delete la repo-urile tale.

Dacă scapă: Repo privat scapat în public. Cod rescris cu backdoors. Branch-ul main rescris cu force push. Atacator face un commit cu numele tău.
CLOUDFLARE_DNS_API_TOKENcritic
Public? NICIODATĂ public

Token Cloudflare cu permisiuni DNS. Folosit de Traefik pentru ACME DNS-01 challenge.

Dacă scapă: Atacator schimbă DNS-ul → redirectează tot traficul la serverul lui (phishing perfect). Sau emite cert SSL valid pentru domeniul tău.
SMTP_PASS / Resend API keyatenție
Public? NICIODATĂ public

Credentialele cu care trimiți email. Magic link, parole reset, notificări.

Dacă scapă: Atacator trimite phishing de pe domeniul tău („verifică contul aici” cu link malițios). Reputația domeniului = arsă. SPF/DKIM nu te salvează.
04 · threat model

Ce te costă, de fapt, fiecare leak.

Date concrete. Nu „ai putea pierde bani" — sume reale, cu timeline, văzute în comunitate în 2024-2025.

Cheie scapatăCost / pagubăCât de repede
ANTHROPIC_API_KEY scapată1000-5000$ overnightdrain în <8h
OPENAI_API_KEY scapată500-3000$ overnightdrain în <8h
SUPABASE_SERVICE_ROLE_KEYdata breach total<5 minute
DATABASE_URL publicDROP/exfiltrate orice<5 minute
GITHUB_TOKEN cu repo:writecod rescris / repos furate<10 minute
BETTER_AUTH_SECRETspoof oricărui userimediat
Pe Anthropic / OpenAI: setezi spending limit în dashboard ca prim safety net. Dacă scapă cheia, măcar bill-ul e cap-ed la cât ai zis tu (50$, 100$, ce te face confortabil). Default-ul e UNLIMITED.
05 · greșeli concrete

5 moduri în care îți scapi cheia săptămâna asta.

Nu sunt teoretice. Toate cinci s-au întâmplat unor developeri pe care îi cunoști (sau ție, dar n-ai realizat încă). Pentru fiecare — fix concret.

Greșeala 01

Commit .env în git

Faci git add . fără să verifici. .env intră în repo. Crezi că-l ștergi cu un commit nou — NU FUNCȚIONEAZĂ. Git history păstrează blob-ul pentru totdeauna. GitHub indexează commit-uri vechi pentru bot-uri de scraping. În sub 30 minute primești un email „cheia ta a fost detectată într-un repo public”.

Fix: Adaugă .env* în .gitignore ÎNAINTE de primul commit. Dacă deja ai commitat: rotezi imediat cheia (nu te baza pe `git rebase` sau `git filter-branch` — nu sunt suficiente). Folosește `gitleaks` ca pre-commit hook.
Greșeala 02

Lipești cheia în chat / Discord / StackOverflow

Cer ajutor. Lipești tot blocul de cod cu DATABASE_URL real. Postul rămâne acolo. Bot-uri scrapează GitHub Issues, Discord public, Stack Overflow non-stop. Cheia ta e descoperită în câteva ore.

Fix: ÎNAINTE să lipești cod, înlocuiește cheia cu <REDACTED> sau sk-...xxx. La AI chat (ChatGPT/Claude direct), conversațiile pot fi folosite pentru training — la fel, redactează.
Greșeala 03

Screenshot din terminal cu cheie pe ecran

Faci poză la ecran ca să arăți unui prieten un bug. În colțul terminalului — output-ul lui `env` cu toate variabilele. Posti pe Twitter/Telegram. Atacatorul face screenshot zoom + OCR.

Fix: ÎNAINTE de orice screenshot, rulează clear && reset sau deschide un tab nou. Pentru video-uri/loom, folosește blur peste secțiunile cu chei.
Greșeala 04

Prefix NEXT_PUBLIC_ pus greșit

Vrei să folosești cheia într-un component React. Nu funcționează (e undefined). Adăugi NEXT_PUBLIC_ — funcționează acum. NU ai realizat că tocmai ai pus cheia secretă în bundle JS livrat la browser.

Fix: Regulă fixă: orice componentă React care are nevoie de o cheie SECRETĂ trebuie să facă fetch la un API route (/api/foo) care rulează server-side. Niciodată nu rezolvi un „undefined” client cu NEXT_PUBLIC_ pentru chei sensibile.
Greșeala 05

Cheie scapată în error logs / Sentry

Throw error cu mesajul „Failed to call Anthropic with key sk-ant-...”. Sentry / Datadog / log file pe server captează stack trace-ul. Cheia ta e acum în log retention 30-90 zile, accesibilă oricui din echipă (sau atacator dacă scapă logs).

Fix: Niciodată nu pune chei sau date sensibile în mesaje de eroare. Folosește console.error("Anthropic call failed", err.message), nu err brut. Configurează Sentry să facă scrubbing pe pattern-uri ca sk-*, eyJ*.
06 · galleria gafelor

Anti-pattern gallery. Văzute pe net.

Exemple anonimizate, dar reprezentative. Recunoști vreunul în propriul trecut? E timpul să rotești cheile.

Realizezi că ai făcut una? Nu intra în panică, dar acționează acum. Vezi planul de incident response mai jos.
07 · dormi liniștit

Checklist + plan de incident response.

Configurația minimă să nu te trezești cu un email de la Anthropic „cheia ta a generat 4321$ peste noapte".

Setup defensiv (10 min one-time)

  • .gitignore include .env, .env.local, .env.*.local ÎNAINTE de primul commit. Verifică cu git check-ignore .env.local.
  • Pre-commit hook cu gitleaks sau trufflehog. Detectează automat pattern-uri sk-ant-*, sk-proj-*, eyJ* etc. înainte să commit-ezi.
  • Spending limits setate în Anthropic + OpenAI dashboard. Default e UNLIMITED — schimbă la cap maxim confortabil pentru tine (50-200$/lună).
  • Rotire periodică a cheilor sensibile (90 zile pentru chei plătite, 30 zile pentru chei prod în aplicații cu useri reali).
  • Separă cheile pe medii: cheie dev cu spending cap mic + cheie prod cu monitoring. NU folosi aceeași cheie pe local și pe prod.
  • Niciodată cheia în context AI (chat ChatGPT / Claude direct / Cursor). Rămâne în logs lor.
  • Niciodată cheia într-un screenshot. Înainte de poză: clear + tab nou.
  • Sentry / log scrubbing pe pattern-uri sk-*, eyJ*, ca să nu ajungă chei în error logs.
  • RLS policies active pe TOATE tabelele Supabase. Test cu cheia ANON: dacă vezi date private, ai bug.
  • Permisiuni 600 pe fișierele cu chei (chmod 600 .env.local) — doar tu le poți citi.

⚠ Plan de incident response (5 minute)

Realizezi că ai scăpat o cheie. Ordinea contează — fă-le în ordinea asta:

  1. Rotează imediat cheia în dashboard (Anthropic / OpenAI / Supabase / GitHub). Cheia veche devine invalidă instant. Asta e PRIMUL lucru — nu mai e nimic mai important.
  2. Verifică log-urile / billing-ul dashboard-ului serviciului. Vezi dacă a fost folosită deja. Dacă da — contact support pentru fraud chargeback (Anthropic / OpenAI acceptă în primele 24h dacă demonstrezi leak).
  3. Update .env.local cu noua cheie pe toate mediile (local + container aihost).
  4. Repar source-ul leak-ului: scoate din git history (cu git filter-repo + force push, dar cheia e deja compromisă, asta e cleanup), șterge tweet-ul, redactează postul, actualizează screenshot-ul.
  5. Lecție: ce a permis leak-ul? Adaugă fix permanent (gitleaks pre-commit, training pentru tine, etc.) ca să nu se mai întâmple.

Container aihost are deja setup-ul defensiv.

Containerul tău vine cu .gitignore corect pentru .env*, iar agentul AI se loghează cu contul tău — nu ții nicio cheie API a agentului pe server. Nu-ți rezolvăm noi greșelile umane (lipirea cheii în chat) — dar default-ul e safe.

Vezi și stack-ul modern explicat · WordPress vs vibe coding · ghid migrare WordPress → Next.js