Harden It — Security & Guardrails

Advanced AI Build Night · Harden It · Cheat sheet

Harden It — Security & Guardrails

Harden It — Security & Guardrails — cheat sheet

The one idea: Your Lovable app is probably wide open right now. Tonight you close the handful of holes that actually get small builders breached — secrets, the database, untrusted input, and abuse — *before* you ship.

Ship-tonight checklist

  • [ ] No secrets in the client. Grep the build for service_role, API keys, and tokens. Anything found goes to a server / env var.
  • [ ] RLS is ON for every table in the public schema. Default-deny, then add least-privilege policies.
  • [ ] Policies are real, not USING (true). Rows scope to (select auth.uid()).
  • [ ] Storage buckets are private unless they truly must be public. Bucket policies, not "public" toggle.
  • [ ] Input is validated before it touches the model or the DB. Length caps + a schema (zod).
  • [ ] Output is not trusted blindly. Validate shape; never render model HTML or run model SQL/commands raw.
  • [ ] A prompt-injection guardrail is in place (instruction hierarchy + treat retrieved/tool content as untrusted).
  • [ ] Rate limits + a spend cap on every AI / write endpoint.
  • [ ] Errors are quiet. No stack traces, keys, or SQL leaking to the client.
  • [ ] Keys you'll rotate are noted (you'll need to after tonight's audit).

OWASP Top 10 for LLM Apps — 2025 quick reference

Published 2025-03-12 · genai.owasp.org/llm-top-10

  • LLM01 Prompt Injection — untrusted text overrides your instructions. #1 three years running.
  • LLM02 Sensitive Information Disclosure — model leaks PII, secrets, or other users' data.
  • LLM03 Supply Chain — poisoned models, packages, or plugins.
  • LLM04 Data & Model Poisoning — tainted training / fine-tune / embedding data.
  • LLM05 Improper Output Handling — you trust model output into SQL, HTML, shell, etc.
  • LLM06 Excessive Agency — the model can do more (tools / permissions) than it should.
  • LLM07 System Prompt Leakage — your system prompt (and its secrets) get extracted.
  • LLM08 Vector & Embedding Weaknesses — RAG store leaks or gets poisoned.
  • LLM09 Misinformation — confident wrong answers users act on.
  • LLM10 Unbounded Consumption — no limits → cost-bomb / denial-of-wallet.

Supabase RLS — copy-paste starter

```sql -- 1. Turn RLS on. With no policies, this denies everything (safe default). alter table public.notes enable row level security;

-- 2. Owners can read their own rows. Wrap auth.uid() in a select so it's -- evaluated once per query, not once per row (the big perf gotcha). create policy "owner can read own notes" on public.notes for select to authenticated using ( (select auth.uid()) = user_id );

-- 3. Owners can insert rows only for themselves. create policy "owner can insert own notes" on public.notes for insert to authenticated with check ( (select auth.uid()) = user_id );

-- 4. Owners can update / delete only their own rows. create policy "owner can modify own notes" on public.notes for update to authenticated using ( (select auth.uid()) = userid ) with check ( (select auth.uid()) = userid );

create policy "owner can delete own notes" on public.notes for delete to authenticated using ( (select auth.uid()) = user_id );

-- 5. Index the column your policy filters on, or large tables crawl. create index if not exists notesuserididx on public.notes (userid); ```

Test policies from the client SDK / anon key — the SQL Editor bypasses RLS and will lie to you.

Secrets — do / don't

  • Do keep keys in env vars / a server route / an Edge Function. Use the anon (publishable) key in the browser.
  • Do rotate any key that ever touched client code or a public repo. Assume it's burned.
  • Don't ever ship the service_role (secret) key to the browser — it bypasses all RLS.
  • Don't put secrets in VITE_-prefixed vars — Vite inlines those into the public bundle.
  • Don't paste keys into prompts, URLs, query params, or logs.

Top 5 gotchas → fixes

  • service_role key in the browser → move it server-side; rotate the old one immediately.
  • RLS off (or USING (true)) → enable RLS, write owner-scoped policies, test with the anon key.
  • Public storage bucket → make it private; grant access with bucket policies per user.
  • No rate limit / spend cap → add per-IP + per-user limits and a dollar spend cap on AI endpoints.
  • Verbose error leakage → return a generic message to the client; log details server-side only.

Glossary

  • RLS (Row Level Security) — Postgres rule layer that decides which rows a given user can see/change.
  • anon vs service_role key — anon is safe for the browser and respects RLS; service_role bypasses RLS and is server-only.
  • Prompt injection — untrusted text (typed or retrieved) that hijacks the model's instructions.
  • Indirect injection — injection hidden in content the model reads (a page, a doc, a tool result), not typed by the attacker.
  • CSP (Content Security Policy) — a header that limits what scripts/origins a page may load, blunting XSS.
  • Denial-of-wallet — abuse that runs up your usage bill instead of taking you offline.

Template library

Copy a starting point, paste it into your assistant's instructions, then make it yours.

Find keys, tokens, and service_role usage that shouldn't be in client code.

Audit this app for exposed secrets
You are a security auditor reviewing a Vite + Supabase front-end app for leaked secrets.

Scan the repository and report, as a checklist:
1. Any hardcoded API keys, tokens, passwords, or connection strings in source.
2. Every use of the Supabase service_role / secret key — flag any that can reach the browser bundle.
3. Any secret exposed through a VITE_-prefixed env var (these get inlined into the public bundle).
4. Secrets passed in URLs, query params, or client-side logs.

For each finding: file + line, why it's a risk, the exact fix (move server-side / to an env var / Edge Function), and whether the key must be rotated. Don't change code yet — give me the list first.

Get least-privilege Row Level Security policies for your schema.

Generate RLS policies for these tables
You are a Supabase/Postgres security engineer. I will paste my table definitions.

For each table:
1. Enable Row Level Security.
2. Write least-privilege policies for select / insert / update / delete, scoped to the row owner via (select auth.uid()) = user_id (wrap auth.uid() in a select for performance).
3. Default to deny — only add a policy for access that's actually needed. Call out any table that legitimately needs public read.
4. Add an index on every column referenced in a policy.
5. Remind me to test with the anon key from the client SDK, not the SQL Editor.

Output runnable SQL with a one-line comment above each policy explaining who it lets do what. Here are my tables:

Harden a server route / Edge Function against bad input and abuse.

Add validation + rate limiting to an endpoint
You are hardening a server endpoint (Supabase Edge Function or serverless route) that calls an LLM and/or writes to the database. I'll paste the handler.

Add, with code:
1. Input validation — a zod (or equivalent) schema, plus a hard length cap on any free-text field before it reaches the model or DB.
2. Rate limiting — per-IP and per-authenticated-user limits, returning 429 when exceeded. Use a simple, dependency-light approach and note where the counter is stored.
3. A spend / quota guard — stop calling the model once a per-user daily cap is hit.
4. Quiet errors — never return stack traces, SQL, or keys to the client; log details server-side only.

Keep it minimal and production-shaped, not a framework. Explain each change in one line. Here's the handler:

Stress-test a system prompt against direct and indirect prompt injection.

Review my system prompt for injection risk
You are a red-teamer specializing in prompt injection (OWASP LLM01). I'll paste my system prompt and describe what untrusted content the model sees (user input, retrieved documents, tool/API results).

Do this:
1. List concrete ways an attacker could override my instructions — both direct (typed) and indirect (hidden in retrieved/tool content).
2. Show 2–3 example payloads that would likely work against the current prompt.
3. Rewrite the system prompt to establish a clear instruction hierarchy: privileged instructions first, and an explicit rule to treat all user/retrieved/tool content as data, never as commands.
4. Recommend layered defenses beyond the prompt: input/output validation, an allowlist for any tool the model can call, and least-privilege on what the model is allowed to do.

Here's my system prompt and the untrusted inputs:

Knowledge-file starter

Fill in the brackets, save it as a PDF or text file, and upload it as your assistant's knowledge.

Knowledge file
# [App name] — secrets + RLS hardening starter

## .env (server-side only — never committed, never VITE_-prefixed)
SUPABASE_URL=[your project url]
SUPABASE_SERVICE_ROLE_KEY=[secret — server / Edge Function only, NEVER the browser]
LLM_API_KEY=[secret — server only]

## .env (safe for the client — Vite inlines these into the public bundle)
VITE_SUPABASE_URL=[your project url]
VITE_SUPABASE_ANON_KEY=[anon / publishable key — respects RLS, OK in browser]

## Secrets checklist
- [ ] Grep the repo + built bundle for: service_role, secret, API key names, tokens.
- [ ] Any secret found in client code → move server-side AND rotate it.
- [ ] .env is in .gitignore; no secrets in git history.
- [ ] Storage buckets reviewed: private unless they must be public.

## RLS rollout (per table)
- [ ] alter table public.[table] enable row level security;
- [ ] select policy — owner only: (select auth.uid()) = user_id
- [ ] insert policy — with check ( (select auth.uid()) = user_id )
- [ ] update + delete policies — owner only
- [ ] index on [user_id] (and any other column a policy filters on)
- [ ] Tested with the anon key from the client, not the SQL Editor

## Abuse / cost guard (per AI or write endpoint)
- [ ] Per-IP rate limit
- [ ] Per-user rate limit
- [ ] Daily spend / quota cap
- [ ] Generic error responses (details logged server-side only)

Build 313 · Advanced AI Build Night · Presented by JD Fiscus