Modul 6 / 12
Secrets og API keys
Alt som når browseren er offentlig. Lær å finne lekkasjer i bundles og storage.
Typer nøkler
- ›Public/publishable keys (Supabase anon, Stripe pk_): laget for browseren. Sikkerhet kommer fra RLS/server.
- ›Secret keys (sk_, service_role, OpenAI): kun server. Lekkasje = full tilgang.
- ›Environment variables: VITE_* bygges inn i JS-bundlen. process.env leses kun på server.
- ›Database credentials / connection strings: aldri i klient, aldri i repo.
- ›Service-role credentials: bypasser alt — behandles som root-passord.
Hvorfor sendt = eksponert
Browseren må kunne lese verdien for å bruke den, og det kan brukeren også. Minifisering, base64 eller å «skjule» i en variabel endrer ingenting.
Er en secret noen gang sendt til klienten: roter den. Fjerning fra koden hjelper ikke — den ligger i gamle bundles og git-historikk.
Vulnerable · ts
// Klientkomponent
const openai = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_KEY,
dangerouslyAllowBrowser: true,
}); Secure · ts
// server function
export const ask = createServerFn({ method: "POST" })
.middleware([requireSupabaseAuth])
.inputValidator(z.object({ q: z.string().max(2000) }))
.handler(async ({ data }) => {
const key = process.env.OPENAI_KEY; // kun server
...
});Slik leter du i browseren
- ›Sources: Ctrl+Shift+F søk i alle filer etter sk_, service_role, secret, eyJ, apiKey, password
- ›Network: se på headers — sendes en secret som Authorization til et tredjeparts-API direkte fra browser?
- ›JS bundles: last ned og grep (under)
- ›Local Storage / Session Storage: tokens er forventet, API-nøkler og roller er det ikke
# Lokalt etter build
grep -rEo "(sk_(live|test)_[A-Za-z0-9]+|service_role|eyJ[A-Za-z0-9_-]{20,})" dist/ | sort -u
# Decode en eyJ-token og sjekk "role"
echo "<payload-del>" | base64 -d # role: "service_role" = kritiskKontroller i din egen app
Labs for denne modulen
Quiz
Scenario 1
Du finner en JWT i bundlen. Payload inneholder "role":"anon". Er det kritisk?
Scenario 2
Payload inneholder "role":"service_role". Hva gjør du?