OMAR
Field NotesCV
Never Put Your API Key in the Browser
← All Notes
Security08 May 2026 · 7 min read

Never Put Your API Key in the Browser

Every AI feature I ship sits behind a server proxy that redacts personal data before it leaves the building. Here is the shape of that proxy, and why each part exists.

The fastest way to add an AI assistant to a site is to call the provider's API from the frontend. It works immediately, the demo is impressive, and you have published your API key to everyone who opens dev tools.

Every AI feature I ship sits behind a server proxy instead. Here is what that proxy does and why each part exists.

The key never leaves the server

This is not negotiable and it is not about obscurity. A key in a frontend bundle is a key you have given away — it will be scraped, it will be used, and you will find out from the bill.

The browser talks to your endpoint. Your endpoint talks to the provider. That is the whole architecture, and everything below is refinement.

app.post('/api/assistant', requireSession, rateLimit, async (req, res) => {
  const prompt = buildPrompt(redact(req.body.message), context)
  const reply = await callModel(prompt)     // key lives here, in env
  res.json({ reply: sanitize(reply) })
})

Redact before the request leaves your building

On the Avenue project the assistant helps visitors find materials and book consultations. People type things into it that they should not — phone numbers, full addresses, occasionally a card number "just in case".

Sending that to a third party is a data protection problem you created on their behalf. So the message is scrubbed before it goes anywhere:

const PATTERNS = [
  [/\b[\w.%+-]+@[\w.-]+\.[a-z]{2,}\b/gi, '[email]'],
  [/\b(?:\+?20|0)1[0-9]{9}\b/g, '[phone]'],
  [/\b(?:\d[ -]*?){13,19}\b/g, '[card]'],
  [/\b\d{14}\b/g, '[national-id]']
]

export const redact = text =>
  PATTERNS.reduce((s, [re, tag]) => s.replace(re, tag), text)

Regexes are imperfect and this is defence in depth, not a guarantee. It still removes the overwhelming majority of accidental disclosures, and it does so before the data crosses a boundary you do not control.

Rate limit twice

An AI endpoint is a direct line from an anonymous visitor to your credit card. It needs stricter limits than the rest of your API.

I apply two: a short window to stop bursts, and a daily ceiling per session and per address to cap the worst-case bill. Above that, the endpoint returns a polite message rather than an error — a rate-limited assistant that says "give me a moment" reads as busy, not broken.

A hard monthly spend cap at the provider is the backstop, and it is the one that lets you sleep.

Treat the model's output as untrusted input

The output is generated text influenced by whatever the user typed. If you render it as HTML, you have built an XSS vector where the payload is written by a stranger via a model.

Render as text. If you must support formatting, allow a strict subset — bold, lists, links with an explicit protocol check — and sanitise everything else. Never pass generated content into innerHTML or v-html unsanitised, and never let it reach an eval-like sink.

Constrain what it can talk about

An assistant on an interior design site should discuss materials, services and bookings. Left unconstrained, it will happily write poetry, answer medical questions, and be screenshotted saying something embarrassing under your client's logo.

The system prompt sets the scope, refuses off-topic requests, and never claims to be human. That is a brand risk control as much as a technical one, and clients care about it more than they care about latency.

Log carefully

You need logs to debug and to see what people ask for. You do not need raw user messages sitting in a log aggregator forever.

Log the redacted text, a hashed session identifier, token counts, latency and outcome. That is enough to improve the feature and small enough that a log export is not a breach.

Fail like a normal feature

Providers have outages and rate limits of their own. When the call fails, the assistant should degrade to something useful — a contact link, a search box, the phone number — not a spinner that never resolves.

An AI feature is an outbound data flow with a cost attached. Design it like one: authenticated, limited, redacted, sanitised, logged, and capped.

The checklist

  • Key server-side only, in environment config
  • Session or origin check on the endpoint
  • Redaction before the outbound call
  • Burst plus daily rate limits, and a provider spend cap
  • Output rendered as text or strictly sanitised
  • Scope enforced in the system prompt
  • Redacted logs with retention
  • A graceful fallback when the provider is down
AIProxyPIIRate Limiting

Need this built properly?

I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.

Keep Reading