Demo, all content is generated
Question

OpenAI key got used by someone else, $412 in one night

Solved · 3902 views · asked by ravi_k · edited

Woke up to OpenAI emails about usage. $412 in about 8 hours, my app has maybe 20 users. The usage page shows tons of requests to gpt-4o from my key that are not from my app (my app uses gpt-4o-mini).

My app is a React frontend on Replit that calls OpenAI directly with import.meta.env.VITE_OPENAI_KEY. I never put the key on GitHub. How did they get it???

What do I do now, will OpenAI refund?

What I’ve tried

Deleted the key immediately, made a new one and put it in Replit secrets again.

Comment
Don't put the new key back in the same place. Read the answer before redeploying. lena_ops · edited
ok waiting, app is offline now ravi_k · edited
Also turn on secret scanning / push protection on GitHub for the future, it catches keys before they're pushed. amir_h · edited

3 answers

Marked as helpful by the asker
lena_ops · edited

They got it from your website. Anything prefixed VITE_ is baked into the JavaScript bundle that every visitor downloads. Open your site, DevTools > Sources, search sk- and you'll find it. Bots crawl for exactly this.

Replit Secrets doesn't help when the frontend reads it, because the build copies the value into public code.

Now:

  1. The old key is deleted, good. Check the OpenAI dashboard for other keys/projects you don't recognize.
  2. Put the new key only on a server. Smallest possible change: a tiny backend endpoint (Express on the same Repl, or a serverless function) that calls OpenAI, and your React app calls that.
    app.post("/api/chat", requireUser, rateLimit, async (req, res) => {
      const r = await openai.chat.completions.create({
        model: "gpt-4o-mini",          // fixed on the server, not from the client
        messages: req.body.messages.slice(-10),
        max_tokens: 500,
      });
      res.json(r.choices[0].message);
    });
    Note the model and limits are decided server side. Otherwise the endpoint itself becomes a free proxy.
  3. Put auth + a per-user rate limit on that endpoint.
  4. In OpenAI: use a separate project for this app, set budget alerts, and keep auto-recharge off. With prepaid credits, the balance is effectively your hard cap.

Refund: open a support ticket, explain the key was exposed and show the model mismatch. Not guaranteed but people do sometimes get it back.

Comment
found it in the bundle in 5 seconds. i feel so stupid. moving it to a server route now ravi_k · edited
Not stupid, the tooling makes this way too easy to do. Most AI-generated React starters do exactly this. chidi_eze · edited
Update for anyone finding this: OpenAI refunded $380 after about a week. New setup has been fine. ravi_k · edited
chidi_eze · edited

Adding: grep your whole project for any other VITE_ / NEXT_PUBLIC_ / EXPO_PUBLIC_ vars. Only things that are meant to be public belong there (Supabase anon key, Stripe publishable key, analytics id). Service keys, secret keys, AI keys: never.

Comment
Correct, as long as RLS is on. chidi_eze · edited
only had VITE_SUPABASE_ANON_KEY besides that one, which is ok if i understand right ravi_k · edited
halima_s · edited

This happened to me with Gemini. Same cause, same fix. Wish Replit warned about it.

Comment