Is your Cursor app safe?
Is Cursor safe to build production apps with? The code usually runs fine. Working and protected are different tests, and only one of them gets run.

Logos are the property of their respective owners, shown to indicate compatibility.
In short
- A Cursor-built app is usually safe to run. Whether it is safe to expose is a separate test, and nothing runs it for you.
- Generated code answers the question you asked. Which rows a visitor may see is a question that belongs in the database.
- The usual way a secret key gets published is a refactor from server to browser, then adding NEXT_PUBLIC_ or VITE_ to make the error go away.
Cursor gives you real control (your repository, your files, your deploy), and that changes where the risk sits. You are not wondering what a builder did on your behalf. You are reviewing a lot of code that works, quickly, and deciding what to read closely.
Here is the part that guide after guide gets wrong: asking whether the AI writes insecure code is the wrong question. Generated code is written to satisfy what you asked. "Load the user's orders" is satisfied by code that loads orders. Nothing in that sentence says whose, so nothing in the answer decides it, and the place that decision belongs is not in the file you are reviewing.
Working and protected are two different tests
Only one of them runs by itself.
Think of it as a shop. Your app is the shopfront: every page you build is handed in full to every visitor, because a browser cannot render what it was not sent. Your database is the stockroom, a separate building on the internet with its own lock and its own address.
When you run your app and it works, you have tested the shopfront: the right things appear on the right screens for you. You have not tested the lock. The stockroom is reachable directly, without going through your shop at all, and nothing about your app loading correctly says anything about what it returns to someone who skips the front door.
That is the test that never runs on its own, in any project, generated or hand-written. It becomes easier to skip when the code arrives faster than you can read it.
Where keys go wrong in a Cursor project
In the move from server to browser, almost always.
There are two kinds of credential and they look almost the same. A
publishable key names your project and nothing more (sb_publishable_… in
new Supabase projects, anon in older ones), and it is designed to live in a
browser. A secret key, sb_secret_… or the older service_role, ignores
every rule you have written and can read and write every row you have.
The sequence that publishes one is ordinary. Code that used a secret key on the
server gets refactored into a component that runs in the browser. The value
comes back undefined, so the variable is renamed to whatever prefix the build
tool wants (NEXT_PUBLIC_ in Next.js, VITE_ in Vite), and the app starts
working. Vite's own documentation is blunt about what that prefix does: those
values are bundled into your source code at build time.
.gitignore does not help here. It governs what gets committed, and your build
output is generated afterwards. If you would rather be sure which of your keys is which,
there is a one-minute check.
The check that belongs in the database
Row Level Security, if your data is in Supabase: a per-table switch that decides row by row who may read what.
This is the answer to the "load the orders" problem. Rather than trusting every query in the codebase to filter correctly, the database refuses to return rows the visitor is not entitled to, whatever the query said, and whoever wrote it. One rule, enforced at the only place every request has to pass through.
Two details decide whether you have it. Supabase enables Row Level Security by default for tables created in the dashboard's Table Editor, and not for tables created by running SQL, which is how a migration file creates them. And the setting only turns the check on; the policy behind it decides who passes, and a policy can permit everyone anyway.
How to check your own Cursor project in about ten minutes
Grep your own build output, not your source. Build the app, then search the
generated files for service_role, sb_secret_, and sk_live_. Anything that
turns up is published. This is faster and more honest than reading source,
because it is what visitors actually receive.
Read your environment file for prefixes. Every NEXT_PUBLIC_ or VITE_
variable is public by design. For each one, ask whether you would print it on
your homepage.
Open Authentication → Policies in Supabase. Any table showing Row Level Security as disabled is readable by anyone holding your project address, which is in your app.
Then look from outside. Our free scan does the last step for you. It reads your live site as a visitor and gives you a grade in about 20 seconds, no account needed: scan your app.
What to do
- An app that works has passed one test. Whether the database refuses a stranger is a separate test that nothing runs for you.
- Generated code answers the question you asked. "Which rows may this person see" is a question that belongs in the database.
- The common way a secret key gets published is a refactor from server to browser followed by a prefix rename.
.gitignoreprotects your repository, not your build output.- On Supabase, tables created by running SQL start without Row Level Security, and enabling it still leaves every row readable until a policy says otherwise.
Build your project and search the output for service_role and sk_live_
before you do anything else; it takes a minute and the answer is unambiguous.
After that, the 10-minute security checklist is the shortest route
through the rest.
What can actually go wrong with a Cursor-built app
None of these mean you did something wrong. They come with letting AI write code fast. Here's what's worth checking:
A secret key written straight into the code
When the AI wires up a service, it sometimes puts the key right in the code to make things work, and if that code runs in the browser, anyone can read it. Some keys are meant to be public and that's fine; Reeve reads your app's loaded code, finds any keys, and tells you which are safe and which need to move to the server.
A committed .env or exposed .git folder
Keys are supposed to live in a .env file that never ships. But it's easy to accidentally commit .env, or deploy the hidden .git folder, so the whole history, keys included, becomes downloadable. Reeve checks whether either is reachable from the outside.
Your database left open (RLS off)
If your app stores data (often in Supabase or another Postgres), there's a rule (Row Level Security) for who can read or change each row. If it's off, your tables can be open to anyone who finds the address. It's the most common serious issue, and it's invisible unless you check.
Public file storage
If your app accepts uploads, they live in storage "buckets." A public bucket means anyone can list or download what's inside, so a private file can end up visible to everyone. Reeve checks whether your buckets are listable; it never downloads anyone's files.
Source maps left on
A "source map" reveals your app's original code to anyone who looks. Useful while building, but in production it gives strangers a readable copy of how your app works and makes other gaps easier to find. Worth tidying. Reeve checks whether yours are exposed.
Missing safety headers & open endpoints
Small settings that tell browsers how to protect your visitors, plus whether your data endpoints answer to anyone or any website. Each one is minor on its own, but together they add up. Reeve flags what's missing.
What Reeve is, and what it isn't
Reeve is a free, read-only check from the outside, like an inspector trying the doors without going in. It catches the common, high-impact mistakes fast. It is not a full security audit, and a clean grade isn't a guarantee; it means the obvious doors are shut.
Cursor is an editor, not a host, so it doesn't deploy or guard your app for you; more of that is on you and whatever code the AI produced. Cursor's own tools can help review code as you go, and if you use Supabase its advisor flags database issues. What Reeve adds: an outside view of the app you actually shipped, in plain words you can act on. And if you'd rather not be the one checking, Reeve can keep an eye on it.
Want it handled, not just checked?
Reeve Care keeps watching your app, backs up your data, and helps you fix what breaks, so you can keep building instead of worrying.
Learn about Reeve CareFAQ
Is code written by Cursor's AI secure?
It can be good code, but "written by AI" doesn't mean "checked for safety." The AI optimises for making things work, which sometimes means a key in the wrong place or a missing database rule. The only way to know is to check what's exposed. Reeve does that free in about 20 seconds.
I think I committed a .env file. Is that dangerous?
It can be, if the file (or the hidden .git folder) is reachable on your deployed site, because it may contain live keys. Reeve checks from the outside whether either is downloadable, so you know whether to rotate those keys.
How do I know if my Cursor app is leaking API keys?
The usual cause is a key written directly into code that runs in the browser. Reeve reads your app's loaded code, finds any keys, and tells you which are safe to be public and which need to move to the server, without storing the real values.
Will scanning my app change anything?
No. Reeve only looks at what's already public from the outside. It never logs in, never changes anything, and never downloads your files. Read-only, like checking whether a door is locked without stepping inside.
Does Cursor write insecure code?
That is the wrong question to ask of any assistant, including a human one. Generated code is written to satisfy the thing you asked for, and "make this page load the orders" is satisfied by code that loads all the orders. Nothing in the request said which orders, so nothing in the answer decides it. Whichever way you phrase the request, the rule about who may read which rows has to live in the database.
My .env file is in .gitignore. Am I covered?
For your repository, mostly. But .gitignore has no effect on what your build tool publishes. A variable prefixed for the browser (NEXT_PUBLIC_ in Next.js, VITE_ in Vite) is compiled into the JavaScript your visitors download regardless of where it was stored, and it stays in your version history if the file was ever committed before the rule was added.
How do I check what my app actually exposes rather than reading the code?
Load your own site, open your browser DevTools, and look at the Network tab while the page loads. Everything listed there is what a visitor receives. Reading it that way takes a few minutes and tells you more than reading the source, because it is the same view an outsider gets.