FOR PEOPLE SHIPPING BOLT APPS

What Bolt apps get wrong

In round 2026-07 of the Vibe Security Index we scanned 12 public repositories carrying Bolt's scaffold marker with all 212 rules. That is the thinnest slice in the round, so this page is written differently from its siblings — and the first section explains exactly how, because it changes what you are allowed to conclude from everything below it.

12 repositories. Read that first.

Every figure from this round is a count, not a rate. The sample shape and every row of the findings table are given as a fraction of the 12 repositories rather than as a percentage. A single project moves any rate here by about 8 percentage points: the critical-finding rate below would read “42%”, which is exactly the same measurement as “5 of 12” while sounding far more precise than anything we measured. It would also invite a side-by-side with a builder measured over 25 repositories, and that comparison is not one this sample supports. The research figures near the end of the page do carry percentages — they come from a separate and larger measurement, and each is printed with its confidence interval attached.

Why the slice is this size. Nothing failed to scan: every repository sampled was scanned successfully. The sample is small because the eligibility screen rejected 86 of the candidates it examined for this marker, against 12 that passed. The screen is published in advance and applied without ever seeing a scan result — it drops tutorials, starter kits, forks, and projects below a minimum size or source-file count. That is consistent with what the survivors look like: a median of 30.5 scanned files, the smallest in this round.

What it still clears. 12 is above the round's floor for publishing a builder at all, which is 10, so this builder is reported and ranked on the Index alongside the others rather than held back. It is still the thinnest slice in the round, and where a figure below cannot be stated honestly at this size, this page says so instead of printing it.

Read this too

  • This is not a claim that Bolt's product is insecure. We measured public repositories carrying its scaffold marker (bolt-vite-react-ts template in .bolt/config.json), edited by humans for an unknown period afterwards. The marker proves the project started from the scaffold. It does not prove any particular line was generated rather than written, and attributing a finding to the builder is not something this design supports.
  • Project size is a confound, and a large one. A median project here is a fraction of the size of a median project for the other builders in this round. A smaller codebase has fewer places for any finding to occur, so it scores better on every count on this page without that meaning the code is safer.
  • No repository is ever named. Not here, not on the Index, not in the working data. Publishing the list would mean pointing at live vulnerabilities in other people's projects, so the results are aggregate only and the per-repository rows are anonymised by design.
  • Static analysis has false positives, including ours. A finding is something a person should look at, not a proven breach. Where a finding on this list is partly our own noise, it says so directly underneath.

Round 2026-07, generated July 27, 2026. Full method, sampling frame and limitations: Index methodology.

The shape of the sample

5 of 12

had at least one critical finding in their own code

12 of 12

had at least one high or worse

13

median findings per project, application code only

across 12 projects

30.5

median scanned files per project

size is a confound on every count above

Read the second number carefully. Every project in the sample shows a high-or-worse finding because Missing Content Security Policy (CSP) fired on all 12 of them, and that check is rated high. It is a response-header setting on the host, not a flaw in anyone's code, and it is the last section below rather than the first for exactly that reason.

How this list is ordered

Sorted by raw occurrence count, this builder's highest-volume finding happens to be a security one. That is not true of every builder in this round, and the same severity filter is applied here regardless, so the pages stay comparable. Magic Numbers in Code (24 occurrences across 7 of 12 projects) and Console.log Left in Production Code (16 occurrences across 7 of 12 projects) are info severity, a call this codebase made deliberately and has not changed. They are worth cleaning up. They are not what someone shipping an app to real users is asking about, and a security page that led with them would be counting lines instead of answering the question.

So the list below is filtered by severity and then ordered by severity. Reach is only the tiebreaker, and on a sample this size that ordering has a consequence worth stating out loud. What leads is Secret Leaked in Error Response, which appeared in 2 of 12 projects. It leads because it is the most severe thing found, not because it is the most common — and on a sample this thin, a finding in a couple of projects is a lead worth reading rather than a pattern worth generalising.

Security findings that repeat, application code only

From round 2026-07. n = 12 repositories. “Projects” is how many of them contained the finding at least once; occurrences is the total across all of them. Counts, not rates — see the top of this page for why.

FindingSeverityProjectsOccurrences
Secret Leaked in Error ResponseVC148critical2 of 127
Missing Content Security Policy (CSP)VC020high12 of 1222
API Route Missing AuthenticationVC003high3 of 1245
Unprotected Next.js API RouteVC065high2 of 1214
Missing Security Meta Tags / HeadersVC027medium12 of 1244
Clickjacking — Missing X-Frame-OptionsVC056medium12 of 1223
Stack Traces Exposed in API ResponsesVC037medium3 of 128
API Route Without Request Body ValidationVC154medium2 of 127

This is the round's top-rules list with info and low severity removed. It is not the complete finding list for any project, and a check that never made this builder's top ten can still be the one that matters in yours. API Route Without Request Body Validation appears in the table without a section below — real, but too thinly spread in this sample to say anything specific about.

Each one, and what stops it coming back

Fixing the finding fixes one line. Changing the instruction that produced it fixes every line the next prompt writes, which is the only version of this that scales when the code is arriving faster than it can be read.

criticalVC148 · 2 of 12 projects · 7 occurrences

Secrets travelling back out in an error

Secret Leaked in Error Response

Why this workflow produces it

Start with the reach, because it is the whole context for this section: it appeared in a small number of the projects scanned, and the header above this paragraph gives the exact count. This page does not convert that into a percentage, and neither should you.

It leads this page on severity alone. A critical finding in a couple of projects is still the most serious thing this round found in this slice, and pushing it below a finding that reached every project would be sorting by comfort rather than by risk.

What the check matches is a server error response carrying a secret — or a variable named like one — back to whoever triggered the error. That shape turns up in the least convenient place imaginable here. Most projects with this scaffold are client-only, so when one does acquire a server file it is usually there for exactly one reason: to hold a key the browser must not see and to proxy the calls that need it.

That file then gets the treatment every new integration gets while you are trying to make it work. An error path that echoes the configuration back is genuinely useful when the upstream call keeps failing and you cannot see why — and there is no later moment where the app misbehaves to remind you it is still in there.

The change that prevents it

Draw a hard line at the response boundary: configuration goes to your logs, never into a response body. If you needed to see a value to debug something, what you needed was a log line, not a field in the JSON you send back.

Paste this into your project instructions, or into the prompt itself:

Never put an environment variable, a config object, an API
key, or any variable whose name contains key, token, secret
or password into an HTTP response body — including error
responses, and including diagnostic fields that only report
whether a value is set. Errors return a generic message and
a random correlation id. Log the detail server-side against
that same id.

The scan that verifies it

Scan, then force the failure on purpose: point the integration at a bad credential locally and read the entire response, headers included. That is the view someone else gets.

npx xploitscan scan . -f json \
  | jq '[.findings[] | select(.rule == "VC148")] | length'

VC148 is outside the free rule set. The free scan runs 30 of the 212 rules, so a free plan never evaluates it. On a free plan this command therefore returns 0 whether or not the finding is in your code — a zero from it is not a clean result.

Honest caveat. A secret-named variable in a response is not proof that a live credential was published. This check matches names and shapes, so some hits are a boolean saying whether a value is configured, or a redacted placeholder. It is rated critical because of the cost of being wrong in the other direction — the value in question is somebody's production key. Read every one of them; there are few enough that it will take minutes.

highVC003 · 3 of 12 projects · 45 occurrences

Server routes that answer whoever asks

API Route Missing Authentication

Why this workflow produces it

Read the reach and the occurrence count in the header above together, because they point in opposite directions. Few projects, many occurrences. The projects that have a server surface at all have a lot of unprotected handlers each, rather than many projects having one apiece.

The reason so few projects appear here is not a security property and should not be read as one. Most projects with this scaffold are client-only single-page apps — this is the smallest median project in the round by a wide margin — so most of them simply have no server on which to get this wrong.

Where a server does show up, it usually arrives late: the app already works, something needs to happen off the client, and a handler gets added to make that one thing possible. It inherits no conventions, because there were none to inherit. Nothing in the project establishes that a handler should check who is calling before it does the work.

And it never announces itself, because your own session is always present while you are testing. You never see the response the endpoint gives to somebody who is not signed in.

The change that prevents it

Write the rule down once, in your project instructions, so it applies to every handler generated after it rather than being remembered per handler.

Paste this into your project instructions, or into the prompt itself:

Every server route resolves the caller's identity as its
first statement and returns 401 before doing any work if
there is not one. Do this inside the handler itself, not
only in a middleware matcher — a matcher does not cover
routes added later. If an endpoint is deliberately public,
put a comment above it saying why.

The scan that verifies it

Scan and confirm the count drops. Then do the part a scanner cannot do for you: call each remaining handler with no cookie and no authorization header, and read what comes back.

npx xploitscan scan . -f json \
  | jq '[.findings[] | select(.rule == "VC003" or .rule == "VC065")] | length'

VC065 is outside the free rule set. The free scan runs 30 of the 212 rules, so a free plan never evaluates it. This command also counts VC003, which a free scan does run — so the number it returns on a free plan is the VC003 count alone, and it says nothing either way about VC065.

Honest caveat. Both of these checks read one file at a time, which cuts both ways. A route genuinely gated by middleware still reads as unprotected — our false positive, not your bug — and a route whose gate is imported from a helper we cannot follow reads the same way. Treat each hit as a handler worth opening rather than a confirmed hole.

mediumVC037 · 3 of 12 projects · 8 occurrences

Error handlers that hand back the internals

Stack Traces Exposed in API Responses

Why this workflow produces it

The generated catch block is almost always the same three lines: catch the error, put its message in the body, return a 500. It is a reasonable thing to write while building, nothing misbehaves afterwards, and so it never comes up again.

What ships to the caller is your ORM's error text, your table and column names, and often the query that failed — a free map of your schema for anyone who sends a deliberately malformed request and reads the reply.

The change that prevents it

Ask for the error contract explicitly, once, and it applies to every handler generated afterwards. Log the real error server-side, return a generic message and a correlation id to the caller.

Paste this into your project instructions, or into the prompt itself:

Every server error response returns a generic message and a
random correlation id, never error.message and never a
stack. Log the full error with that same id server-side so
it can still be traced. Apply this to every handler you
generate.

The scan that verifies it

Scan, then confirm by hand: send a request that is guaranteed to fail and read the response body. It should tell you nothing you did not already know about your database.

npx xploitscan scan . -f json \
  | jq '[.findings[] | select(.rule == "VC037")] | length'
highVC020 · 12 of 12 projects · 22 occurrences

The response headers a static host does not set for you

Missing Content Security Policy (CSP)

Why this workflow produces it

This is really three findings in the table above — the missing content-security policy, the missing frame-ancestors control, and the missing content-type and referrer policies. They travel together because they have one cause.

This scaffold builds to a static bundle served by a host. All of these are response headers, and a static file does not carry a response header. Something in front of it has to add them, and the default deployment target usually adds none.

Nothing looks broken as a result, because none of these headers change how a working app behaves. They only change what happens during an attack, which is why they survive every round of testing you do before launch.

The change that prevents it

Add them where the host reads them: a _headers file on Netlify or Cloudflare Pages, the headers array in vercel.json on Vercel. The content-security policy is the one worth real time and the one most likely to break something, so run it in report-only mode first and read the reports before you enforce it. The rest are close to free.

The scan that verifies it

Scan for all three, then check the live response and confirm the headers actually arrive: curl -sI https://your-app.example. The scan reads your repository; only the response proves a header is really being sent.

npx xploitscan scan . -f json \
  | jq '[.findings[] | select(.rule == "VC020" or .rule == "VC027" or .rule == "VC056")] | length'

VC027 and VC056 are outside the free rule set. The free scan runs 30 of the 212 rules, so a free plan never evaluates them. This command also counts VC020, which a free scan does run — so the number it returns on a free plan is the VC020 count alone, and it says nothing either way about VC027 and VC056.

Honest caveat. This cluster fired on every project in the sample, which says more about the scaffold than about any developer — and part of it is a measurement limit on our side. The scanner reads your repository, so a host that already injects these headers still reads as missing. Check the live response before you spend an afternoon on it.

The one we are still bad at, stated plainly

Everything above is a finding we detect well. This section is the opposite: a separate research pass over the same sampling frame went looking for request handlers with no authentication check, and on Bolt-shaped code the honest answer is that we flag very little of it — and that we cannot tell you precisely how little.

22

handlers in the Bolt slice where the analysis could find no authentication check

a small population, which is why the figure beside it is an interval

5%–33%

of those 22 that our shipped rules flag today — a 95% confidence interval, not a point estimate

the point estimate is 14%, and it is the least useful part of this figure

The interval is deliberately what is printed large. Over 22 handlers, the true coverage could be roughly one in twenty or roughly one in three, and the measurement cannot separate those. What it does support is a direction, and the direction is not flattering: whatever the exact value, our rules miss most of this shape on this kind of code. Quoting 14% on its own would imply a precision this pass did not earn — it is what the handful of caught handlers works out to, not the middle of the interval, which is a different and higher number.

This is not a builder ranking. The research pass behind these figures states in its own limitations that it does not support comparing one builder against another, so this page reports its own slice and nothing relative to anyone else's.

These figures come from a different pass with its own repository count, so the small-sample warning at the top of this page is not what is driving the width here — that is the handler count. The two caveats are separate and neither inherits the other.

For context from the same pass, across all four builders and 174 repositories: 51% [44%–58%] had any server-side handler at all, and of those, 69% [58%–77%] had at least one handler with no authentication check the analysis could find. Those two figures are round-level, not Bolt-specific, and are labelled that way on purpose — the research pass does not break them out per builder, so neither does this page.

What that does not mean

  • A shape is not a vulnerability. Nothing was exploited and no request was ever sent to a running application. A handler in this class is one where a static pass could not find an auth check. Some fraction are protected by means the method cannot see.
  • 47.5% of handlers were undecidable from a single file and were classified as such rather than guessed at. A handler goes to that bucket whenever protection might exist somewhere the analysis cannot look — middleware mounted before the route table, for instance. Every rate above excludes them, so these describe the decidable slice, not the whole corpus.
  • Handler counts are concentrated. A handful of large repositories contribute most of the handlers, so a handler-level percentage is closer to a statement about those projects than about a typical one.
  • Most projects with this scaffold have no server surface at all. A client-only app against a hosted backend cannot have a broken-auth handler, and that is the main reason only 22 handlers came out of this slice at all. A small population is not evidence that the handlers which do exist are in better shape.

We publish this because a scanner that only advertises what it catches is not much use for deciding whether to trust it. If your project has server-side handlers, read them yourself with one question in mind: who is this endpoint willing to answer? Our benchmark page carries the same disclosure in the other direction — a held-out access-control case that no scanner we test catches, including ours.

The scaffold column, and why its zero is not a win

Every count above covers application code only. Findings in code a builder copies into your project — a vendored component library, generated directories, committed tooling — are separated out before any headline figure is computed. They are reported, never hidden, and they never touch a grade.

For this builder that column is empty: 0 of 12 projects had a critical finding in vendored code, and the median project had 0 vendored findings of any severity. Every other builder in this round sits higher.

That gap is not a security result and must not be read as one. It measures how much of a component library each scaffold copies in. Nearly all the vendored findings anywhere in this study come from two files in one widely-copied component suite — and most of what they trigger is our own false positive, a charting component that writes CSS variables into a style element, which is theming rather than an injection sink. A scaffold that ships those files inherits all of it. One that does not inherits none. A zero here means this builder vendors less code, not that it writes safer code.

The Index checked that this is really the explanation rather than a size effect, and published the check: why one builder's vendored column is empty. Your own grade is computed on your own code by default for the same reason — you can opt into the stricter view with xploitscan scan . --grade-vendored if you want everything counted.

Find out which of these are in your project

Your own repository is a sample of one, which makes it the only sample on this subject with no small-n problem. Same application-versus-scaffold split. No credit card and no signup for the first scan — that one runs the 30 free rules rather than the 212 this round used, and the full set comes with a paid plan.

Numbers on this page are read from the published Vibe Security Index round 2026-07 at build time. Method and limitations: methodology.