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 · flow real pe aihost

Cum folosește Claude Code variabilele tale.

Pe containerul aihost, cheia ta nu pleacă de pe server (decât când Claude o trimite la API-ul Anthropic, ca antet HTTP). Iată fluxul exact, pas cu pas.

1. Pe serverul aihostÎn containerul tău user, cheia ANTHROPIC_API_KEY trăiește în ~/.aihost/agent-keys.env, fișier cu permisiuni 600 (doar tu îl citești).
2. Login bash~/.bashrc face source ~/.aihost/agent-keys.env automat la fiecare SSH-in. Cheia devine variabilă de mediu în shell-ul tău.
3. claude binaryCând rulezi claude, citește process.env.ANTHROPIC_API_KEY și o trimite în antet la API-ul Anthropic.
4. Aplicația taÎn ~/project/.env.local ai variabilele Next.js. process.env.X le accesează server-side. NEXT_PUBLIC_X ajunge în bundle JS la browser.
5. Claude Code agentulCând îi ceri ceva, agentul poate citi fișiere din proiectul tău (.env.local inclusiv). NU le trimite la Anthropic decât dacă tu îi pui textul în context.
6. Logs AnthropicConversațiile cu Claude se păstrează la Anthropic (per ToS-ul lor). Niciodată lipi cheia ta în chat — devine parte din log-uri.
Ce face aihost pentru tine by default: ~/.aihost/agent-keys.env e creat la provision cu permisiuni 600 (doar tu citești). ~/project/.env.local e în .gitignore-ul template-ului. Containerul nu expune process.env la rețea. Logs Anthropic — pe cont propriu (nu trimite cheia ta nimănui altui).
# Ce vezi în containerul tău aihost: $ ls -la ~/.aihost/ -rw------- 1 you you 312 May 5 12:30 agent-keys.env $ cat ~/.aihost/agent-keys.env # export ANTHROPIC_API_KEY="sk-ant-..." # Claude Code # export OPENAI_API_KEY="sk-..." # Codex # export GEMINI_API_KEY="..." # Gemini CLI # După ce decomentezi linia ta și pui cheia: $ source ~/.aihost/agent-keys.env $ claude # pornit, cu cheia citită din env
05 · 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.
06 · 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*.
07 · 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.
08 · 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 ~/.aihost/agent-keys.env). Pe aihost setat by default.

⚠ 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 ~/.aihost/agent-keys.env permisiuni 600, .gitignore corect, separare clară între cheile agent și cheile aplicației, niciodată expuse la internet. 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