The headers are set by the host: what 86 public vibe-coded apps send to your browser
When a browser loads a web page, the server sends a few short instructions along with it. These are called response headers. Some of them are about security: "only talk to me over HTTPS," "don't let other sites put me in a frame," "only run scripts from these places." They don't fix bugs. They limit how much damage a bug, or a hostile page, can do. Think of them as seatbelts, not brakes.
We wanted to know how often apps built with AI coding tools ship with these seatbelts on. So on October 15, 2026 we viewed 95 publicly published apps, one at a time and at human pace: 25 built with Lovable, 25 with Bolt, 24 with Replit and 21 with v0. We recorded the security headers each page sent. 87 apps loaded and 8 did not. We later excluded one of the 87 because its robots.txt disallowed our crawler when we re-checked the results, which leaves 86.
Here is the short version:
- Every one of the 86 apps sent HSTS. That's the header that tells browsers to always use HTTPS. Good news across the board.
- Almost none sent a Content-Security-Policy (CSP). 84 of 86 (98%) did not.
- Clickjacking protection and Permissions-Policy were also missing almost everywhere. 83 of 86 (97%) and 84 of 86 (98%).
- Within each platform the results were close to identical. For example, 17 of 17 Replit apps and 19 of 19 v0 apps sent exactly the same set of headers. That kind of consistency usually means the host decides, not the person who built the app.
What this is not: a list of hacked apps or vulnerable apps. A missing header is a hardening gap. It is not a breach, and nothing in this study shows that any app was compromised or exploitable. We measured only what any visitor's browser already receives. We did not log in, send attack traffic, read any data, or test any credentials.
The numbers
Missing security headers among the 86 analyzable apps, by platform:
| Platform | Analyzable apps | Missing CSP | Missing HSTS | Missing X-Frame-Options / frame-ancestors | Missing Referrer-Policy | Missing Permissions-Policy | Missing X-Content-Type-Options |
|---|---|---|---|---|---|---|---|
| Lovable | 25 | 24 (96%) | 0 (0%) | 23 (92%) | 0 (0%) | 24 (96%) | 0 (0%) |
| Bolt | 25 | 24 (96%) | 0 (0%) | 24 (96%) | 24 (96%) | 24 (96%) | 23 (92%) |
| Replit | 17 | 17 (100%) | 0 (0%) | 17 (100%) | 17 (100%) | 17 (100%) | 17 (100%) |
| v0 | 19 | 19 (100%) | 0 (0%) | 19 (100%) | 19 (100%) | 19 (100%) | 19 (100%) |
| All | 86 | 84 (98%) | 0 (0%) | 83 (97%) | 60 (70%) | 84 (98%) | 59 (69%) |

We also checked two other things a browser can see:
- Exposed source maps: 0 found. One caveat: we only followed
sourceMappingURLcomments that the app's own JavaScript points to. So "0" means "none found that way," not "proven none exist." - Active mixed content (an HTTPS page loading scripts over plain HTTP): 0 found.
Both results are good. HTTPS hygiene in this sample was clean.
The story is the defaults
Look across each row of the table and a pattern shows up. Inside each platform, nearly every app sent the same headers:
- Lovable: 23 of 25 apps sent the same three security headers: HSTS, Referrer-Policy and X-Content-Type-Options. Two apps sent more. One added a CSP and clickjacking protection, the other clickjacking protection and Permissions-Policy. We didn't try to work out how those two did it.
- Bolt: 23 of 25 apps sent HSTS and nothing else from our list. One app also sent X-Content-Type-Options. One app sent all six.
- Replit: 17 of 17 apps sent HSTS only.
- v0: 19 of 19 apps sent HSTS only.
Most people who publish an app from one of these tools never touch a server configuration. Many don't know one exists. When almost every app on a platform sends the same headers, the most likely explanation is that the platform's hosting layer sets them by default and almost nobody changes them. That's a guess from the pattern, not something we confirmed with the platforms. But it's a strong pattern.
To be fair to the platforms, there's good news here too:
- All four already enable HSTS for the apps we saw, which is the header that most directly protects visitors on hostile networks.
- Lovable's default already covers half the list. Referrer-Policy and X-Content-Type-Options were on 25 of 25 Lovable apps. Lovable also offers an optional "Trust center" page that reports, among other things, whether an app restricts framing, sets a CSP and sets a Permissions-Policy (Lovable docs). So the platform already treats these as signals worth showing.
- The remaining gaps are mostly fixable once, at the platform level. A safe default for clickjacking protection, X-Content-Type-Options, Referrer-Policy and Permissions-Policy rarely breaks a typical single-page app. CSP is harder, because a strict policy has to match what each app actually loads. Even so, a platform can offer a starter policy, a "report-only" mode, or a one-click toggle.
That's why we're framing this around defaults rather than builders. One platform change would move these numbers more than a thousand individual blog posts telling builders to "add security headers."
What each header does, in plain words
| Header | What it tells the browser | What it helps with | How bad is it to be missing? |
|---|---|---|---|
| Content-Security-Policy (CSP) | "Only load scripts, styles, images and connections from these places." | Limits the damage if someone manages to inject a script into the page (cross-site scripting). | It's a second line of defense. Missing CSP doesn't mean the app has an injection bug. It means there's no backstop if one turns up. It's also the hardest header to get right. (MDN, Vercel) |
| Strict-Transport-Security (HSTS) | "Always use HTTPS for this site, even if someone types http://." | Stops downgrade tricks on hostile networks, like public Wi-Fi. | Present on all 86 apps. (MDN) |
X-Frame-Options / CSP frame-ancestors |
"Don't let other websites show me inside a frame" (or "only these sites may"). | Clickjacking: a hostile page loads your app in an invisible frame and tricks people into clicking buttons in it. | Matters most for pages with sensitive actions, like login, payments or settings. frame-ancestors is the modern version, and X-Frame-Options is the older one that browsers still honor. (MDN XFO, MDN frame-ancestors) |
| Referrer-Policy | "When someone clicks a link off my site, share only this much about where they came from." | Keeps full page addresses, which can include IDs or tokens in the URL, from leaking to other sites. | Modern browsers already default to a fairly safe policy. Setting it explicitly makes the behavior predictable. (MDN) |
| Permissions-Policy | "This page (and anything embedded in it) may or may not use the camera, microphone, location, and so on." | Stops embedded third-party content from asking for powerful features the app never needs. | Low risk for most apps. It's a cheap way to switch off what you don't use. (MDN) |
| X-Content-Type-Options: nosniff | "Trust the file type I declared, and don't guess." | Stops a browser from treating, say, an uploaded text file as a script. | A one-line fix with almost no chance of breaking anything. (MDN) |
One gotcha: these need to be real HTTP response headers. Putting X-Frame-Options in an HTML <meta> tag does nothing (MDN). A CSP can be set with a <meta http-equiv="Content-Security-Policy"> tag (MDN), but the frame-ancestors directive is not supported there (MDN).
A sensible starting set
This is a reasonable baseline for a typical single-page app. Treat it as a starting point and test it before you rely on it:
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.supabase.co wss://*.supabase.co; frame-ancestors 'self'; object-src 'none'; base-uri 'self'
Notes:
- Start CSP in report-only mode.
Content-Security-Policy-Report-Onlytells the browser to report what would be blocked without actually blocking anything. Vercel's docs recommend this as the first step (Vercel). Open your app, check the browser console for violations, adjust the policy, then rename the header toContent-Security-Policy. - The
connect-srcline is an example. Replace the Supabase entries with whatever APIs, analytics and auth providers your app actually calls. Remove them if you don't use Supabase. - If you use a camera, microphone or location, change the Permissions-Policy, for example
camera=(self). - HSTS: every platform in this study already sends it, so you don't need to add it.
How to add these headers, platform by platform
We only used each platform's own documentation (and the docs of the host it points to). Where a platform doesn't document a way to set headers, we say so. Checked Oct 15, 2026.
Lovable (apps published to *.lovable.app or a custom domain through Lovable)
- We could not find a documented way to set custom response headers on Lovable hosting. Lovable's publishing docs and security overview cover publishing, access control and security scans, but not header configuration.
- What you get by default: in our sample, Lovable-hosted apps already sent HSTS, Referrer-Policy and X-Content-Type-Options.
- Check your own app: Lovable's optional Trust center (Project settings → Publishing) reports whether the live app restricts framing, sets a CSP and sets a Permissions-Policy, along with other checks.
- Partial option inside Lovable: a CSP can go in a
<meta http-equiv="Content-Security-Policy">tag inindex.html(you can ask Lovable to add one). This is the generic browser feature described on MDN, not a Lovable-documented feature. It can't provideframe-ancestors, X-Frame-Options or Permissions-Policy. - Full control: Lovable documents deploying and hosting outside Lovable on hosts such as Netlify, Vercel or Cloudflare Pages. There you can set headers with that host's configuration (see the Netlify and Vercel sections below). Lovable notes that the parts you move off its platform become yours to run and secure.
Bolt (Bolt hosting on *.bolt.host, or Netlify)
- Is bolt.host Netlify-based? Not confirmed. Bolt's docs describe Bolt hosting (the default, with a free
.bolt.hostdomain) and Netlify hosting as two separate options. We found no official statement about what infrastructure runs bolt.host, so we don't assume it behaves like Netlify. - Bolt hosting: we could not find a documented way to set custom response headers in Bolt's help center. We did not test whether Netlify-style files work on bolt.host.
- If you publish to Netlify instead: you have to choose Netlify in Project settings → Domains & Hosting before the first publish. Bolt's docs say a project already published to Bolt hosting can't be switched, and you'd need an unpublished copy (Bolt docs). On Netlify, you can set headers two ways (Netlify docs):
- a plain-text
_headersfile in the publish directory. For a Vite app, putting it inpublic/_headersusually gets it copied intodist/:/* X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() - or a
[[headers]]block innetlify.toml(Netlify file-based configuration):toml [[headers]] for = "/*" [headers.values] X-Frame-Options = "SAMEORIGIN" X-Content-Type-Options = "nosniff" Referrer-Policy = "strict-origin-when-cross-origin" Permissions-Policy = "camera=(), microphone=(), geolocation=()"
Replit deployments (*.replit.app)
- Static deployments: documented. Add one
[[deployment.responseHeaders]]entry per header to the.replitfile at your project root, then republish (Replit docs): ```toml [[deployment.responseHeaders]] path = "/*" name = "X-Frame-Options" value = "SAMEORIGIN"
[[deployment.responseHeaders]] path = "/*" name = "X-Content-Type-Options" value = "nosniff"
[[deployment.responseHeaders]] path = "/*" name = "Referrer-Policy" value = "strict-origin-when-cross-origin"
[[deployment.responseHeaders]]
path = "/*"
name = "Permissions-Policy"
value = "camera=(), microphone=(), geolocation=()"
``
Replit reserves a short list of headers you can't set, such asSet-CookieandServer. None of the six security headers are on that list.
- **Apps that run a server (non-static deployments):** we didn't find a Replit platform setting for response headers on these. Your server code sets them, for example in middleware. Replit's [security checklist](https://docs.replit.com/learn/security-checklist) says to add security headers "in index.html or through your back-end." One caution: its example puts X-Frame-Options and X-Content-Type-Options in` tags, and browsers ignore X-Frame-Options there (MDN). Send them from the server as real headers.
v0 / Vercel (*.vercel.app or a custom domain on Vercel)
v0 deploys to Vercel, so Vercel's header configuration applies. Use one of these:
vercel.json, using theheadersproperty (Vercel docs, Vercel headers overview):json { "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-Frame-Options", "value": "SAMEORIGIN" }, { "key": "X-Content-Type-Options", "value": "nosniff" }, { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }, { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" } ] } ] }next.config.js/next.config.ts, using an asyncheaders()function, which is the usual choice for Next.js apps (most v0 output is Next.js) (Next.js docs):js module.exports = { async headers() { return [ { source: '/:path*', headers: [ { key: 'X-Frame-Options', value: 'SAMEORIGIN' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }, ], }, ] }, }- For a CSP in Next.js, follow the official Next.js CSP guide, which covers nonce-based policies. Vercel's CSP page recommends starting with report-only.
- You can also just ask v0 to add these to your project's config.
After any change: republish, then load your live site with your browser's developer tools open (Network tab → the page request → Response Headers) to confirm the headers are there.
Methodology
- What we looked at: 95 apps that were published publicly and indexed by search engines: Lovable 25, Bolt 25, Replit 24, v0 21. Each was viewed once, one at a time, at human pace, on October 15, 2026.
- What counted as analyzable: the published page returned HTTP 200. 87 apps qualified. One of them was later excluded because its robots.txt disallowed our crawler at verification time, so 86 are counted.
- What we checked (narrowed v1):
- Response headers on the page itself: whether each of the six headers above was present. We recorded presence only and did not grade policy quality. A weak CSP would count as "present." A report-only CSP (
Content-Security-Policy-Report-Only) counts as missing, because it blocks nothing. - Exposed source maps: we followed
sourceMappingURLcomments in the JavaScript the page itself loads, and counted a map as exposed only if it was actually served. - Active mixed content: whether an HTTPS page loaded scripts over plain HTTP.
- What we didn't do: no logins, accounts or cookies. No requests to paths the app doesn't link to (no
/.env, no/.git, no database API roots). No attack payloads or fuzzing. No reading or writing of any app data. No use of any key or token. - Anonymization: we report aggregates only. We don't name any app, publish any URL, or show screenshots that could identify an app. Per-app records stay internal.
- Verification: every automated finding was re-checked by hand on Oct 17, 2026 against a fresh view of each page; all counted findings were confirmed and none were false positives. Separately, a second researcher re-checked a random 5-app sample of the negative results (about 5% of the analyzable apps), repeating the header, source-map and mixed-content checks. All 5 matched our original records exactly. Two limits: mixed content was checked in the static HTML only, not in resources loaded at runtime, and n=5 is a coarse check, not a statistical audit.
- Rounding: all percentages use standard rounding of the raw counts (for example 84/86 = 98%, 83/86 = 97%, 59/86 = 69%), applied the same way in the text and the chart.
- Why no per-app notices: header gaps, source maps and mixed content are aggregate hygiene signals under our disclosure policy. They don't trigger individual owner notices. This study found no secret-class issues because it didn't look for them (see below).
Scope caveats (please read before quoting)
- Row Level Security (RLS) was not measured. Proving that RLS is missing or broken would require reading data, which we don't do. Nothing here says anything about whether these apps' databases are protected.
- Secret and API-key checks were out of scope for v1. This study doesn't say whether any app exposes credentials, in either direction.
- The source-map result has a limit. We only followed
sourceMappingURLcomments, so "0 exposed" means none were found that way, not that none exist. - The sample frame is narrow. These are public, search-indexed published apps, not a random sample of all apps built on these platforms. Polished public apps may differ from private or internal ones. A few entries (6 of the 86 analyzable) were demos or templates rather than products, though their headers matched the rest of their platform.
- Small numbers per platform. With 17 to 25 apps per platform, one app moves a platform's percentage by 4 to 6 points. Read per-platform figures as patterns, not precise rates. Don't use them to rank platforms.
- 8 apps were unreachable and are excluded from every percentage: 6 Replit apps returned 404, 1 v0 app returned 404, and 1 v0 app returned 402 (Payment Required). One Replit app was excluded after its robots.txt disallowed our crawler at verification time. That's why Replit (17) and v0 (19) have fewer analyzable apps than were sampled.
- Presence isn't quality. We checked whether a header exists, not whether its value is strong.
- Headers reflect the host as much as the builder. As discussed above, these results mostly measure hosting defaults.
- This is a point-in-time snapshot from October 15, 2026. Platforms and apps change constantly, and a single platform-side change could alter these numbers overnight.
- Hardening, not breach. Missing headers are hardening gaps. They are not vulnerabilities in themselves, and nothing in this study shows that any app was compromised.
What's next
This was a narrowed first pass. We'd like to run a larger, more systematic study, including a bigger sample per platform. Before we automate anything, we're asking each platform for written permission. We've also shared these aggregate numbers with Lovable, StackBlitz (Bolt), Replit and Vercel (v0) ahead of publication. As of October 23, 2026, none had sent a human reply; we'll update this piece if any of them comments or changes its defaults.
If you build on one of these platforms, the fix for most of this is a few lines of configuration. If you run one of these platforms, the fix for all of your users is one default.