π¨ Security Problem Statement
apps/web/.env documents:
VITE_GEMINI_API_KEY=your-gemini-api-key
The VITE_ prefix in Vite intentionally embeds variables into the client-side JavaScript bundle at build time. This means any user who opens DevTools β Sources on bookcatq.netlify.app can find and extract the Gemini API key in under 60 seconds.
With the key, anyone can:
- Make unlimited Gemini API calls billed to the BookCAT project's Google account
- Burn through the free tier quota, breaking mood search, daily quiz, and hot takes for all users
- Use the key for unrelated purposes
Affected Features
All AI features in BookCAT use this key:
- Mood-based book search (
/discover)
- Daily quiz generation
- Hot takes generation
- Book facts
Proposed Fix
Move all Gemini API calls to Supabase Edge Functions (already deployed for quiz, hot takes, book facts). The frontend calls the Edge Function, which calls Gemini server-side using a secret that never reaches the browser.
1. Remove VITE_GEMINI_API_KEY from apps/web/.env
2. Update Supabase Edge Function secrets:
supabase secrets set GEMINI_API_KEY=your_key_here
3. For mood search (currently calling Gemini from the browser):
Create a new Edge Function apps/web/../supabase/functions/mood-book-search/index.ts:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY")!;
serve(async (req) => {
const { mood } = await req.json();
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${GEMINI_API_KEY}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{ parts: [{ text: `Recommend 5 books for someone who feels ${mood}...` }] }],
}),
}
);
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
});
4. Update the frontend Discover page to call supabase.functions.invoke('mood-book-search', { body: { mood } }) instead of calling Gemini directly.
Files to Modify
| File |
Change |
apps/web/.env and .env.example |
Remove VITE_GEMINI_API_KEY |
apps/web/src/pages/Discover.jsx |
Call Edge Function instead of Gemini directly |
supabase/functions/mood-book-search/ |
New Edge Function |
π Impact
This is a P0 security issue on the live deployment at bookcatq.netlify.app. The Gemini API key is publicly accessible right now to any visitor. Rotating and moving it to Edge Function secrets costs nothing and permanently closes the exposure.
Suggested labels: security, critical, backend, gssoc:level2
I would like to work on this. Could you please assign it to me?
π¨ Security Problem Statement
apps/web/.envdocuments:VITE_GEMINI_API_KEY=your-gemini-api-key
The
VITE_prefix in Vite intentionally embeds variables into the client-side JavaScript bundle at build time. This means any user who opens DevTools β Sources onbookcatq.netlify.appcan find and extract the Gemini API key in under 60 seconds.With the key, anyone can:
Affected Features
All AI features in BookCAT use this key:
/discover)Proposed Fix
Move all Gemini API calls to Supabase Edge Functions (already deployed for quiz, hot takes, book facts). The frontend calls the Edge Function, which calls Gemini server-side using a secret that never reaches the browser.
1. Remove
VITE_GEMINI_API_KEYfromapps/web/.env2. Update Supabase Edge Function secrets:
supabase secrets set GEMINI_API_KEY=your_key_here3. For mood search (currently calling Gemini from the browser):
Create a new Edge Function
apps/web/../supabase/functions/mood-book-search/index.ts:4. Update the frontend Discover page to call
supabase.functions.invoke('mood-book-search', { body: { mood } })instead of calling Gemini directly.Files to Modify
apps/web/.envand.env.exampleVITE_GEMINI_API_KEYapps/web/src/pages/Discover.jsxsupabase/functions/mood-book-search/π Impact
This is a P0 security issue on the live deployment at
bookcatq.netlify.app. The Gemini API key is publicly accessible right now to any visitor. Rotating and moving it to Edge Function secrets costs nothing and permanently closes the exposure.Suggested labels:
security,critical,backend,gssoc:level2I would like to work on this. Could you please assign it to me?