AI-coded apps

    Which Lovable prompts add CSP and input sanitization manually?

    Code editor with three Lovable security prompts pinned next to a React project: a Content-Security-Policy header for vercel.json, a Zod schema inside a Supabase Edge Function, and a DOMPurify wrapper around a dangerouslySetInnerHTML call in a component

    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.
    • dangerouslySetInnerHTML is 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.json and set the Content-Security-Policy header 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. Use default-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: email as a valid email string, password as 8 or more characters, displayName as a string of 1 to 60 characters. Reject the request with a 400 response and a short error message if validation fails. Use z.object and safeParse. 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 email and displayName before 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 dangerouslySetInnerHTML in the project. For each one, wrap the input in DOMPurify.sanitize() before passing it to the prop. Install dompurify and @types/dompurify. Configure DOMPurify to forbid the script and iframe tags and to strip on* event-handler attributes. Use RETURN_TRUSTED_TYPE: true when 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 no onerror attribute."

    "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?

    ConcernBuilt-in scan flags itManual prompt required to fix
    Exposed API keys in client bundleYesYes, move secret to Edge Function env
    Supabase RLS missing or permissiveYesYes, write RLS policy in a prompt
    dangerouslySetInnerHTML without sanitizerPattern flaggedYes, add DOMPurify call
    Missing input validation on Edge FunctionPattern flaggedYes, add Zod schema
    Missing Content-Security-Policy headerNoYes, edit vercel.json or index.html
    Weak password rulesNoYes, add to Zod schema and Supabase auth config
    Open CORS on Edge FunctionSometimesYes, set explicit Access-Control-Allow-Origin
    Outdated dependency CVEYes (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.
    • #lovable.dev
    • #csp
    • #input sanitization
    • #supabase edge functions
    • #zod
    • #dompurify
    • #ai-coded apps
    • #xss

    Frequently asked questions

    Can I rely on Lovable's built-in security scan to find missing CSP and sanitization?
    The built-in scan catches some patterns, like dangerouslySetInnerHTML without a sanitizer and missing Supabase RLS, but it does not flag a missing Content-Security-Policy header. Per the Lovable security overview, the scan is a triage queue, not a patch. Treat it as a starting list and write a follow-up prompt for each finding that names the file path, the library, and the exact policy or schema.
    Is a CSP meta tag in index.html as good as a response header?
    For a static Lovable SPA, a meta tag in index.html is the realistic option and covers most XSS payloads, but the MDN CSP guide notes meta tags do not support report-uri or frame-ancestors. If your Lovable project deploys to Vercel, prefer the response header in vercel.json because you get reporting and full directive coverage. Reserve the meta tag for static hosts without header control.
    Why does my Zod schema reject valid input in the Edge Function?
    Supabase Edge Functions run on Deno, not Node. The Zod import path uses a Deno URL such as https://deno.land/x/zod/mod.ts, not the npm package name. Lovable sometimes writes the Node import, which fails silently and returns the raw body as invalid. Check the function logs in the Supabase dashboard; an import error appears as a 500 response before the schema ever runs. Switch the import and redeploy.
    Do I still need DOMPurify if I never use dangerouslySetInnerHTML?
    No, as long as you render every user string through React's default curly-brace path, which escapes HTML automatically. The risk appears the first time a feature requests rich text, markdown previews, email composition, or notification HTML; AI-generated code often reaches for dangerouslySetInnerHTML in those cases. Install DOMPurify before that feature lands so the safe pattern is ready when the prompt arrives.
    Does CSP block Supabase realtime websockets?
    Yes, by default. The realtime connection uses wss://<project>.supabase.co, which falls under the connect-src directive. If connect-src is 'self' only, the websocket fails silently and your subscriptions return empty. Add wss://*.supabase.co to connect-src alongside the HTTPS variant. The same rule applies to Supabase Storage signed URLs in img-src, where the bucket origin has to be listed for signed images to render.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free