A Lovable build looks fine on the surface. The forms submit, the data lands in Supabase, and the deploy is live on a Vercel URL. Then a security scan flags a missing Content Security Policy header, an unsanitized rich-text field, and a sign-up endpoint that accepts any string as an email. None of that is visible in the UI, which is why it slips past most founders shipping their first AI-coded app.
Short answer
Lovable does not configure Content Security Policy or input sanitization on its own. You add both with specific prompts: a CSP delivered through hosting platform headers or a meta tag, Zod schemas inside Supabase Edge Functions for server-side validation, and DOMPurify for any field rendered through dangerouslySetInnerHTML. The validation prompt is server-side; the CSP prompt is at the deploy layer.
What you should know
- Frontend validation has no security value. Lovable's own security pitfalls guide states client-side checks can be inspected, modified, or bypassed and that validation belongs in Edge Functions.
- The built-in Code security review is a pattern scanner, not a fix. It surfaces XSS and input-handling issues but, per the Lovable security overview, does not replace manual review.
- CSP is not enabled by Lovable in the default build. You either set the header in the hosting platform configuration or inject a meta tag through the index.html template.
dangerouslySetInnerHTMLis the most common XSS path in AI-generated React. Any rich text, markdown render, or notification body that flows through that prop needs DOMPurify before insertion.- Zod schemas double as runtime validators and TypeScript types. One schema covers the Edge Function input check and the React form types in the same file.
- Supabase Row Level Security does not replace input sanitization. RLS controls who can read or write a row; it does not check what is inside the row.
Why does a Lovable build ship without CSP or input sanitization?
The short answer is that Lovable generates a working React and Supabase project, not a hardened one. The default output focuses on getting the user-visible flow working: a form, a database table, an Edge Function, a deploy. Headers and validation are configuration concerns that sit one layer below the prompt surface, and the model does not add them unless you ask.
The official Lovable documentation makes the gap explicit. The avoiding security pitfalls guide treats all input as untrusted and asks developers to validate and sanitize data in Edge Functions where checks cannot be bypassed. The security overview lists four automated scanners (RLS analysis, database check, code security review, dependency audit) but states the scanners cannot guarantee complete security. Neither doc mentions Content Security Policy at all.
That means two things for the founder. First, the project compiles and runs even when CSP and sanitization are missing, so the Lovable preview window never raises a flag. Second, the fix is a prompt away, but the prompt has to be specific enough that the model edits the right configuration file rather than dropping a comment in a component.
Which prompt adds a Content Security Policy to a Lovable deploy?
CSP belongs at the hosting boundary. The two common Lovable deploy targets, Vercel and the platform's own publish flow, both let you configure response headers. The prompt that gets Lovable to write the right file is platform-specific.
For a Vercel deploy:
"Add a Content Security Policy to this project for production. Create or edit
vercel.jsonand set theContent-Security-Policyheader with:default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.supabase.co; connect-src 'self' https://*.supabase.co wss://*.supabase.co; object-src 'none'; base-uri 'none'; frame-ancestors 'none'. Apply the header path to all routes."
For a static deploy where you cannot edit response headers, ask for a meta tag in index.html:
"Inject a Content-Security-Policy meta tag into
index.html. Usedefault-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self' https://*.supabase.co wss://*.supabase.co; img-src 'self' data:; object-src 'none'. Do not use a nonce because this is a static SPA."
The MDN CSP guide warns that 'unsafe-inline' defeats much of the purpose of having a CSP. Strict CSP with a per-request nonce is stronger, but it requires server rendering. For a Lovable SPA built with Vite, the meta tag with 'unsafe-inline' for script-src is the realistic starting point. Tighten to a nonce only if you migrate to a server-rendered framework later.
Verify after the prompt. Open the deployed site, open DevTools, and check the response headers tab for the Content-Security-Policy entry. If the value is missing, the prompt edited a file Vercel does not read; ask Lovable to confirm where vercel.json lives relative to the project root.
How do you prompt Lovable for server-side input validation with Zod?
Server-side validation in a Lovable app lives in Supabase Edge Functions. Lovable already uses Edge Functions for most server logic, so the prompt asks the model to add a schema check before the database write.
"In the Edge Function at
supabase/functions/create-account/index.ts, add a Zod schema that validates the request body. The schema must require:passwordas 8 or more characters,displayNameas a string of 1 to 60 characters. Reject the request with a 400 response and a short error message if validation fails. Usez.objectandsafeParse. Do not return the raw Zod error in the response."
The structural rule, per the Lovable pitfalls doc, is that the same check should not live only in the React form. A React form that validates with the same schema is fine for user feedback, but the server check has to be present and independent of the client. The Edge Function runs on Deno, which executes Zod the same way Node does, so the import path is the only friction point.
A two-line follow-up handles the most common omission:
"Also strip leading and trailing whitespace from
displayNamebefore validation, and lowercase the email before persisting."
If Lovable starts to write its own ad-hoc check with if (!body.email.includes('@')), push back. The OWASP Top 10 entry for injection names weak or missing input validation as the most common contributor to injection findings; a custom string check rarely covers the cases a schema validator like Zod's .email() covers.
What prompt sanitizes user HTML before rendering it?
The pattern to look for in the codebase is dangerouslySetInnerHTML. AI-generated React frequently reaches for it when a feature touches rich text, markdown rendering, or HTML email previews. Anything inserted through that prop bypasses React's default escaping and becomes a script-execution path the moment the source is user-controlled.
The prompt:
"Find every use of
dangerouslySetInnerHTMLin the project. For each one, wrap the input inDOMPurify.sanitize()before passing it to the prop. Installdompurifyand@types/dompurify. Configure DOMPurify to forbid thescriptandiframetags and to stripon*event-handler attributes. UseRETURN_TRUSTED_TYPE: truewhen the target browser supports Trusted Types."
The library reference is the DOMPurify project on GitHub, maintained by cure53 and the standard HTML sanitizer for React projects. It runs in the browser and in Edge Functions, which means the same call works on the server when an Edge Function renders an email body.
Two follow-ups close the loop:
"Add a Vitest unit test that feeds DOMPurify the payload
<img src=x onerror=alert(1)>and asserts the output contains noonerrorattribute."
"Move the sanitization to the Edge Function path where the HTML is stored, not only the component that renders it. The database should hold the cleaned version."
Storing the cleaned version is the practice that survives a frontend rewrite. If a future component forgets to call DOMPurify, the data on disk is still safe.
How does Lovable's built-in security scan compare to manual prompts?
| Concern | Built-in scan flags it | Manual prompt required to fix |
|---|---|---|
| Exposed API keys in client bundle | Yes | Yes, move secret to Edge Function env |
| Supabase RLS missing or permissive | Yes | Yes, write RLS policy in a prompt |
dangerouslySetInnerHTML without sanitizer | Pattern flagged | Yes, add DOMPurify call |
| Missing input validation on Edge Function | Pattern flagged | Yes, add Zod schema |
| Missing Content-Security-Policy header | No | Yes, edit vercel.json or index.html |
| Weak password rules | No | Yes, add to Zod schema and Supabase auth config |
| Open CORS on Edge Function | Sometimes | Yes, set explicit Access-Control-Allow-Origin |
| Outdated dependency CVE | Yes (audit) | Yes, run dependency update then re-scan |
The built-in scanner, per the Lovable security overview, runs four automated checks and prints a list. The list is useful as a triage queue, not a fix. Each item still requires a follow-up prompt that names the file path, the library, and the exact policy. The scanner does not write the patch.
For builders who want an external read of the compiled output before exposing it to real users, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-launch scanning aligned with OWASP MASVS for web and mobile builds. The point is to catch the gap before the first paying user does.
What to watch out for
Several traps appear during the sanitization and CSP work that are easy to miss.
The first is treating client-side Zod as the security layer. A founder who runs the same schema in the React form often deletes the Edge Function check by accident on a later refactor because the two checks look duplicated. They serve different consumers; both have to live.
The second is 'unsafe-eval' or wildcard * in CSP script-src. Lovable sometimes proposes either to make a third-party SDK work. Both effectively disable the policy. The correct fix is a specific origin in script-src (for example, https://js.stripe.com) and the SDK's documented integration pattern.
The third is forgetting that Supabase Storage signed URLs include a token. A CSP that locks img-src to 'self' blocks every signed image. Add https://*.supabase.co and, if relevant, the storage bucket origin.
The fourth is using DOMPurify.sanitize on a string that has already been escaped by React. Sanitizing twice does no harm, but if the original render path used the safe curly-brace pattern, wrapping it in dangerouslySetInnerHTML plus DOMPurify is more code surface than the safe default needs.
The fifth is omitting report-uri or report-to. Without a CSP reporting endpoint, a broken policy fails silently in production until a user complains. Add a reporting destination during the prompt phase.
Key takeaways
- Treat CSP and input sanitization as deploy-time prompts, not project-time defaults. Lovable will write them when asked, but the model does not add them on its own.
- Server-side validation is the only validation that counts. A Zod schema in the Supabase Edge Function is the line of defense; the React form schema is a UX nicety.
- Sanitize HTML at write time, not only at render time. Storing the cleaned version protects the data from later components that forget to call DOMPurify.
- Use the Lovable security scan as a triage list. It flags patterns but does not patch them; each finding still needs a specific follow-up prompt that names the file path.
- Some founders outsource the pre-launch security read to platforms like PTKD.com (https://ptkd.com), which scans the deployed app against OWASP MASVS findings before exposing it to real users.



