Guide · Cursor · Updated
Is my Cursor app secure? A plain check before you launch
You described what you wanted, accepted change after change, and now Cursor has helped you ship a real app with a real address. Before you post the link, give it one evening: look at what you shipped the way a stranger would, and at the workshop it came out of. This is the list we work through ourselves. Nobody can promise you an app is secure, us included. You can check the places where the trouble usually is.
New to this? Start with our vibe coding security checklist, eleven checks for any AI-built app, whatever tool made it. This guide goes deeper on apps built in Cursor.
The honest short answer
Cursor is an editor, not a host. It doesn’t run your app, choose your database or publish anything for you. You own every layer, so no platform setting quietly protects you. If a route forgets to check who is asking, nothing upstream will notice.
Cursor is also more open about its own risks than most tools. Its agent security page starts with “AI can behave unexpectedly due to prompt injection, hallucinations, and other issues,” and its hardening guide calls security a shared responsibility: Cursor runs the platform, and you decide how to configure it. So the useful question isn’t “is Cursor secure?” It’s two smaller ones. Does the app you deployed check who is asking, everywhere? And is the editor that wrote it set up so that a key, a stray file or a poisoned instruction can’t walk out the door?
What makes a Cursor app different
- You have the code. Everything is on your machine and usually in git, so you can search the source and the build, not just poke at the live site.
- The agent works on your machine. It reads files, edits them and runs commands with your permissions. Cursor’s docs say reading and editing workspace files need no approval (configuration files aside), and terminal commands do by default.
- It reads more than your prompt. Rules files,
AGENTS.md, web pages and MCP server output all reach the model. Any of them can carry instructions you never wrote. - The stack is up to you. Most Cursor apps we see are Next.js or Vite, often with Supabase or Firebase. Our examples use Next.js.
Most of what follows applies just as well to Windsurf, Claude Code and other editors where an agent reads your project and runs commands for you. The settings have other names; the mistakes are the same.
The ten checks
The first four are about the workshop: your repo, your editor and what the agent can reach. The last six are about the app you deployed. Only test an app you own, and use read-only requests against anything live.
- Read what the agent changed before it ships
- Keep .env out of git, and out of its history
- Keep your secrets away from the model
- Treat MCP servers, rules files and other people’s repos as code
- Nothing secret behind NEXT_PUBLIC_ or VITE_
- Every API route and Server Action checks who is asking
- Every record belongs to the person asking, down to the database
- Your framework, your packages and Cursor itself are patched
- Headers, CORS and source maps on the live site
- Anything that costs money has a limit
You need git, curl and grep; Cursor’s terminal works. We ran each command against a Next.js app we made careless on purpose, with fake keys, and against lockship.dev, and say what came back.
01
Read what the agent changed before it ships
Cursor’s agent security page says it plainly: agent edits to workspace files “save immediately to disk. Always use version control so you can revert changes.” It adds a warning that’s easy to skim past: “If you have auto-reload enabled, agent changes might execute before you can review them.” Your dev server picks up the change before you’ve read it. Fine on your laptop. Not fine once you deploy without looking.
We write LockShip with AI agents too. If we could keep one habit from this page, it would be this: read the diff before anything ships. The changes that matter are rarely big. A check moved out of a route “to simplify things.” A variable renamed so the browser can see it. A database policy dropped so a query would stop failing.
Commit your own work before you start a big agent task, so the diff afterwards only shows what the agent did. Then look at it:
# What changed since your last commit?
git status --short
git diff --stat
# Read the parts that decide who can see what, line by line
git diff -- '*route.ts' '*actions.ts' '*middleware.ts' '*proxy.ts' \
'*next.config.*' 'package.json' '*migrations*'For a second pair of eyes, Cursor’s Agent Review runs a code review on your local changes, from the Source Control tab or with /agent-review. Its docs suggest the Deep setting for “security-sensitive code.” Treat it as a reviewer, not a verdict.
Then check how much the agent can do without you, in Settings → Agents → Approvals & Execution. Cursor’s Run Modes page lists three modes. Auto-review, the recommended default, runs allowlisted calls, sandboxes shell commands where it can and sends the rest to a classifier. Allowlist only runs what you’ve listed. Run Everything is for when “You accept the risk and want zero prompts.” The docs add: “Auto-review is not a security boundary. The classifier can make mistakes.” Keep Auto-review or a short allowlist, and leave File-Deletion and External-File Protection on.
02
Keep .env out of git, and out of its history
Your keys live in .env files, and those files should never reach your repo. Next.js’s docs say the default create-next-app template adds all .env files to .gitignore, and “you almost never want to commit these files to your repository.” Projects an agent set up in a hurry don’t always get that line.
The trap is that .gitignore doesn’t work backwards. Git’s own documentation says: “Files already tracked by Git are not affected.” If .env was committed once, ignoring it later changes nothing. Three commands tell you where you stand:
# 1. Is a .env file tracked right now? (.env.example is fine)
git ls-files | grep -E '(^|/)\.env'
# 2. Was one ever committed, on any branch?
git log --all --oneline --name-only -- '.env*' '**/.env*'
# 3. Does .gitignore cover every name you use? No line = not ignored.
git check-ignore -v --no-index .env .env.local .env.productionIn our test repo, where we committed .env first and ignored it later, the first command still listed it. The second found it in history, plus a .env.local on another branch. The third showed the subtle one: a .gitignore line reading just .env didn’t cover .env.local or .env.production. You want the first two to print nothing except perhaps .env.example, and the third to print a line for every name.
If something turns up, the fix starts with the keys, not with git:
- Rotate first. GitHub’s guide to removing sensitive data says that “as a first step you need to revoke and/or rotate that secret,” and that once you have, rewriting history “may not be warranted.” Make new keys at each provider and revoke the old ones.
- Stop tracking the file and fix the ignore rule, so the next commit can’t bring it back:
# Stop tracking it (the file stays on your disk)
git rm --cached .env
# .gitignore: every .env file except the example
.env*
!.env.exampleGitHub also suggests avoiding git add . and git commit -a, and reading git diff --cached before you commit, which matters more when an agent commits. And use push protection. GitHub turns it on by default for your own account, where it stops you pushing secrets to public repositories. For a private repo it is off by default, and has to be enabled on the repository with GitHub Secret Protection.
03
Keep your secrets away from the model
Anything the agent reads goes to a model. Cursor’s ignore file docs describe the main control: list files in .cursorignore, and Cursor blocks them from Agent, Tab, Inline Edit and @ mentions. Some protection is already on. Cursor ignores everything in your .gitignore, and its default ignore list includes .env*.
The same page is honest about the limits, and they matter. “The terminal and MCP server tools used by Agent cannot block access to code governed by .cursorignore.” And: “complete protection isn’t guaranteed due to LLM unpredictability.” So the agent can’t open .env in the editor, but cat .env or printenv in the terminal still puts every key in the conversation. If you see a command that would print secrets, say no.
Next, find the secret files that aren’t named .env, because the default list won’t catch them:
# Files in your project that usually hold secrets
find . -path ./node_modules -prune -o -path ./.git -prune -o -path ./.next -prune -o -type f \
\( -name '.env*' -o -name '*.pem' -o -name '*.key' -o -name '*credentials*.json' \
-o -name '*service-account*.json' -o -name 'secrets.json' \) -printIn our test project it found a .pem key, a Firebase service account file and a credentials.json. Add patterns for whatever turns up, here or in Cursor’s global ignore list, which is empty by default:
# .cursorignore (same syntax as .gitignore)
**/*.pem
**/*.key
**/credentials.json
**/*service-account*.json
**/secrets.jsonThree more habits:
- Don’t paste keys into the chat. Put them in
.envyourself and tell the agent the variable’s name. It can write code that uses a key without ever seeing it. - Turn on Privacy Mode in Cursor’s settings. Its security page says that then “we will not train on your data.” It doesn’t stop the agent reading a file;
.cursorignoreand your approvals do that. - Use the smallest key that works. Give the agent a test project or a restricted key, never the production secret.
04
Treat MCP servers, rules files and other people’s repos as code
This is the genuinely new part. The agent follows instructions, and they can come from a rules file, a README, or a support ticket an MCP server fetched for you. OWASP lists prompt injection first among its risks for AI applications, and notes that injected instructions “do not need to be human-visible/readable, as long as the content is parsed by the model.” Four real cases:
- An MCP config written by the agent. In CVE-2025-54135, Cursor’s advisory explains, the agent could create
.cursor/mcp.jsonwithout approval, so a prompt injection could add a server and run code on your machine. Aim Security showed it with a Slack message read through a Slack MCP server, BleepingComputer reported. Fixed in 1.3.9. - An approved MCP server that changed later. In CVE-2025-54136, Check Point found that approval was tied to a server’s name, not its command. Someone with access to a shared repo could commit a harmless entry, wait for approval, then swap the command. Cursor’s advisory says it now asks again whenever an entry changes.
- A rules file with hidden instructions. Pillar Security showed rules files carrying instructions in invisible Unicode, which the model reads and a reviewer doesn’t see. Per SC Media’s report, Cursor said managing that risk is up to users. Its rules docs say rule contents “are included at the start of the model context.”
- A repo that runs code when you open it. Cursor’s docs say Workspace Trust is “disabled by default.” Oasis Security showed that a
.vscode/tasks.jsonset to run on folder open then executes as soon as you open the project. Cursor told BleepingComputer it kept the default because “Workspace Trust disables AI and other features our users want to use within the product.”
None of this means “don’t use MCP.” It means look at what you’ve connected and what came in with other people’s code:
# 1. Which MCP servers can this project, and your account, start?
cat .cursor/mcp.json ~/.cursor/mcp.json 2>/dev/null
# 2. Invisible characters in rules and agent files (zero-width, bidi, tag characters)
find . -path ./node_modules -prune -o -type f \( -name '*.mdc' -o -name 'AGENTS.md' \
-o -name '.cursorrules' -o -name 'mcp.json' \) -print \
| while read -r f; do
perl -CSD -ne 'print "$ARGV line $.: hidden character\n" if /[\x{200B}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{FEFF}\x{E0000}-\x{E007F}]/' "$f"
done
# 3. Tasks that run the moment the folder opens
grep -rn '"folderOpen"' .vscode 2>/dev/nullOn a test folder with a rules file hiding zero-width spaces, an AGENTS.md hiding a tag character, a clean rules file and a folder-open task, it flagged both poisoned files by line, skipped the clean one and printed the task. You want the second and third commands to print nothing. For each MCP server listed, ask who wrote it and what it can reach.
Cursor’s MCP docs say “Only install MCP servers from trusted developers,” use “restricted API keys with minimal required permissions,” and remember they “can access external services and execute code on your behalf.” Keep per-tool approval on. Be most careful with a server that reads text strangers write, like tickets or emails, and can also reach your data. General Analysis demonstrated this with Cursor and the Supabase MCP server: a support ticket told the agent to copy a private table into the thread, and with the service_role key, which skips Row Level Security, it could. Supabase’s MCP docs now recommend project_ref scoping, read_only=true, and production only when a task needs it.
If you open repos you didn’t write, Cursor’s docs suggest enabling Workspace Trust with "security.workspace.trust.enabled": true, or opening them in a plain text editor.
05
Nothing secret behind NEXT_PUBLIC_ or VITE_
This is the mistake we’d bet on finding in a Cursor app that calls a paid API. Next.js’s docs explain that a variable starting with NEXT_PUBLIC_ is inlined at build time and “will be inlined into any JavaScript sent to the browser.” Vite’s rule is the same for VITE_: these variables “should not contain sensitive information such as API keys.”
It usually goes like this. A browser component calls OpenAI, the key comes back undefined, and the quickest fix is the public prefix. The app works, and the key is in a file anyone can download. OpenAI’s key safety page is blunt about it: “Never deploy your key in client-side environments like browsers or mobile apps.”
You have the code, so check it from the inside:
# 1. Every public variable your .env files define (names only)
grep -hoE '^(NEXT_PUBLIC_|VITE_)[A-Z0-9_]+' .env* 2>/dev/null | sort -u
# 2. Build for production, then search what browsers will download
npm run build
grep -rEohI 'sk_live_[A-Za-z0-9]+|rk_live_[A-Za-z0-9]+|whsec_[A-Za-z0-9]+|sb_secret_[A-Za-z0-9_-]+|sk-(proj|ant)-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|postgres(ql)?://[^:/"]+:[^@"]+@' \
.next/static dist 2>/dev/null | cut -c1-14 | sort | uniq -cOn our careless test app, the first command listed NEXT_PUBLIC_OPENAI_KEY and NEXT_PUBLIC_STRIPE_SECRET. The second found both fake keys in .next/static, the Stripe one in a chunk only an admin screen loads. The database password, without the prefix, appeared nowhere. On LockShip’s own build it found nothing, the answer you want.
A site name, a Stripe pk_ key or Supabase’s publishable key are meant to be public. A Supabase secret or legacy service_role key is not. Supabase’s key docs say a secret key “bypasses Row Level Security, so it must never leave your control.” If a real secret turns up, treat it as already leaked. Rotate it at the provider, then ask Cursor to move the call to the server:
Find every place client-side code reads a variable that starts with NEXT_PUBLIC_ or VITE_ and holds an API key, token or password. Move each call that uses it into a server route handler or server-only module, read the key there with process.env under a name without a public prefix, and delete the public variable from .env files. Don't print or echo any key values. List what you moved and change nothing else.Rebuild and run the search again. Our guide to leaked API keys in your frontend bundle has a script that checks your live site the same way, including files that load later, plus a way to search your git history for keys.
06
Every API route and Server Action checks who is asking
A Next.js app has doors besides its pages. Every route.ts under app/api is an address anyone can call with no browser and no login. So is every Server Action. Next.js’s data security guide says an exported Server Action “is reachable via a direct POST request, not just through your application’s UI,” and that “a page-level authentication check does not extend to the Server Actions defined within it.” OWASP puts the rule in one line: “Access control is only effective when implemented in trusted server-side code.”
Agents often get the page right and the door behind it wrong: the dashboard redirects signed-out visitors, while the route it fetches from answers anyone. List the doors:
# Next.js route handlers
find . -path ./node_modules -prune -o -path ./.next -prune -o -name "route.[jt]s*" -print
# Server Actions: every file or function marked "use server"
grep -rnE --include=*.ts --include=*.tsx --include=*.js --include=*.jsx \
--exclude-dir=node_modules --exclude-dir=.next "[\"']use server[\"']" .Then ask each route that returns data for its data, the way a stranger would, with no cookie at all:
curl -i https://your-app.com/api/notesOn our test app, the careless route answered 200 OK with every user’s notes and email address. The route that checked the session answered 401 Unauthorized. A route meant for signed-in people should give you the second. If you get data back, have Cursor add the check:
Go through every route handler under app/api and every Server Action ("use server"). For each one that reads or changes user data, make sure the first thing it does is check that someone is signed in, on the server, and return 401 (or throw) if not. Don't rely on middleware or on hiding buttons. List every route and action, whether it needed the check, and what it checks now. Change nothing else.Server Actions need a code check rather than a test from outside, because testing one means sending it a POST, and you shouldn’t send POST requests to a live app to see what happens. Open every "use server" match from the list above. In each function, the first lines should find out who is calling and stop if it’s nobody. Next.js’s guide also says proxy.ts (formerly middleware) and route.ts files “Have a lot of power,” which is one more reason the check belongs in each route, not only in middleware.
07
Every record belongs to the person asking, down to the database
Check 6 asks “is anyone signed in?” This one asks “is this theirs?” A route checks for a login, then returns invoice 1042 to whoever asks. Change the number and you’re reading someone else’s invoice. Next.js’s guide names it: beyond authentication, check authorization, which “prevents Insecure Direct Object Reference (IDOR) vulnerabilities.” OWASP’s API list ranks it first.
Make two test accounts, A and B. Create something as A, note its ID, then sign in as B and ask for it, in the browser or from the terminal with B’s cookie:
# Signed in as account B: copy the Cookie header of any request
# from DevTools → Network. Then ask for something account A owns.
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Cookie: PASTE_ACCOUNT_B_COOKIE" \
https://your-app.com/api/notes/ID_THAT_BELONGS_TO_ACCOUNT_AOn our test app, the login-only route gave account B a 200 with A’s note in it. The careful route returned 404, and 200 for B’s own note. That’s what you want. Try your admin page as an ordinary user too.
Two more things. Next.js warns that passing a whole database row to a Client Component “exposes all the fields,” so a card that shows a name can ship the email and role too. And server code using Supabase’s secret key skips Row Level Security, so your route’s check is the only one.
Here is who should see what in this app: [describe it, e.g. "each customer sees and edits only their own invoices; only I can change plans"]. Go through every route handler, Server Action and database query. Make sure each one only returns or changes records the signed-in user owns, by putting their user id in the query itself, and returns 404 for anyone else's record. Return only the fields the page needs, never whole database rows. List each place you changed and the rule it enforces now.If the browser talks to the database directly, through Supabase or Firebase, the database rules are a second lock, and often the only one. Supabase’s docs put it in one sentence: “A table in an exposed schema without RLS is readable and writable by any role with a grant on it.” Firebase’s guide to insecure rules warns that with open rules, “anyone who guesses your project ID can steal, modify, or delete the data.” Ask a table for a row the way a stranger would, with only the public key from your page:
curl -s "https://YOUR-PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*&limit=1" \
-H "apikey: YOUR_PUBLISHABLE_KEY"For anything private, you want an error or an empty list. Our Supabase RLS checklist has SQL to audit every table and policy, and the Firebase rules checklist has tested rules for Firestore and Storage.
08
Your framework, your packages and Cursor itself are patched
Some bugs aren’t in your code at all. On 14 October 2026, Next.js shipped 16.4.1 and 15.5.28 to fix, among others, two critical remote code execution bugs that need no login, one of them in image optimization with SVG files (CVE-2026-96889). An app that was fine on the 13th needed an upgrade on the 14th. Nobody in your repo did anything wrong. Find out what version your live site runs:
APP=https://your-app.com
curl -s "$APP/" | grep -oE '/_next/static/chunks/[^"]+\.js' | sort -u \
| while read -r f; do curl -s "$APP$f"; done \
| grep -oE 'window\.next=\{version:"[^"]+"'On lockship.dev, and on our test app, it printed window.next={version:"15.5.28". If yours prints nothing, check package.json instead; you have the code, so that’s easy. Then ask npm about everything else your app ships with. Its audit command asks the registry for a report of known vulnerabilities in your dependencies:
# Known advisories in what your app ships with. Read it; don't --force it.
npm audit --omit=devRead it rather than counting. On LockShip it reported nine, all in build tooling, and suggested npm audit fix --force, which would have moved us to a new major version of Next.js and swapped another package for a very different version. That’s a project, not a fix, and it is exactly the kind of command an agent will happily run if you let it. Upgrade on purpose instead, and read the diff. On GitHub, Dependabot alerts will tell you when a new advisory affects a package in your repo, so you don’t have to remember to look.
Cursor is software too, and it gets patched. Both MCP bugs in check 4 were fixed in updates, which only helps if you install them. Cursor’s security page says critical incidents are communicated by email to affected users. Keep the editor updated, and treat its update prompt the way you’d treat one from your browser.
09
Headers, CORS and source maps on the live site
Now the part a stranger sees first, on your live address:
- Security headers. Missing ones don’t leak data, but they tell browsers how to protect your visitors. OWASP’s cheat sheet has recommended values, and Next.js lets you set them all in one place with headers() in
next.config. Next.js also adds anx-powered-byheader by default, which you can turn off withpoweredByHeader: false. - CORS. It decides which other websites may read your API’s answers. A common agent fix for a CORS error is to echo back whatever origin asks and allow credentials. MDN’s guide explains that a credentialed response must name an explicit origin instead of the
*wildcard. Echoing every origin satisfies that rule while defeating its purpose: any site your user visits can read their data. - Source maps and stray files. Next.js leaves browser source maps off in production by default, its docs say, “to prevent you leaking your source on the client.” Agents sometimes turn them on while chasing a bug.
APP=https://your-app.com
# Security headers (and the one you want gone)
curl -sI "$APP" | grep -iE "^(content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|x-powered-by)"
# CORS: pretend to be another website
curl -s -o /dev/null -D - -H "Origin: https://evil.example" "$APP/api/me" \
| grep -iE "^access-control-allow-(origin|credentials)"
# Source maps and files that should never be public
f=$(curl -s "$APP/" | grep -oE 'src="/[^"]+\.js"' | cut -d'"' -f2 | head -1)
for p in "$f.map" /.env /.env.production /.git/config; do
curl -s -o /dev/null -w "%{http_code} %{content_type} $p\n" "$APP$p"
doneOur careless test app showed what bad looks like. The only header in the first list was X-Powered-By: Next.js. The CORS check came back with access-control-allow-origin: https://evil.example and access-control-allow-credentials: true. The source map answered 200 as JSON, with our original code inside. The .env and .git paths were 404, as they should be.
On lockship.dev, the first command listed a Content Security Policy, Strict-Transport-Security, X-Frame-Options: DENY, nosniff and a referrer policy, with no x-powered-by. The CORS check printed nothing, which means other sites can’t read the answer. The map and the files were refused. If your CORS check shows someone else’s origin, ask Cursor to allow only your own domain. If a map answers 200, remove productionBrowserSourceMaps and redeploy.
10
Anything that costs money has a limit
A route that calls an AI model, sends email or texts a phone number costs you money every time someone calls it. Without a limit, a script can call it ten thousand times overnight, and you find out from the bill. OWASP’s API list calls this unrestricted resource consumption and includes services “paid for per request, such as sending emails/SMS/phone calls.” Next.js’s data security guide also suggests rate limiting expensive operations. Sign-in and password reset need limits too, or someone can guess passwords all night.
Agents rarely add limits unless you ask, because nothing breaks without them. Try a few quick reads of one of your own cheap, read-only endpoints. Don’t do this to an AI route; you’d be paying for the test.
# 30 quick reads of one of your own cheap, read-only endpoints. GET only.
for i in $(seq 1 30); do
curl -s -o /dev/null -w "%{http_code}\n" "https://your-app.com/api/search?q=test"
done | sort | uniq -cThirty lines of 200 means nothing stopped you. A limit shows up as 429 after a while. Not every endpoint needs one, but every endpoint that costs money, sends a message or checks a password does.
Add rate limiting to sign-in, sign-up, password reset, and every route or Server Action that calls a paid service (AI models, email, SMS). Limit per signed-in user where there is one and per IP address otherwise, and return 429 when the limit is hit. Tell me which library or platform feature you used and where the limits are set.If you deploy on Vercel, its firewall has rate limiting rules on every plan, including one rule per project on the free Hobby plan. Also set a monthly spending limit at your AI provider, so a missed route costs you an annoying amount rather than a frightening one.
What a clean result means
If you got through all ten and found nothing, that’s genuinely good news. It means nothing obvious is open today, not that the app is safe forever. The next agent task can add a route without a check, a new MCP server can bring new instructions, and a new advisory can land on a Tuesday. Read the diff every time (check 1), repeat checks 6 and 7 whenever you add something that saves or shows data, and run check 9 after every deploy that touches next.config.
Where LockShip fits
We built LockShip because the outside-in parts of this list are easy to forget and dull to repeat. You paste your app’s address and prove you own it. The scan then reads your deployed app the way a stranger would. It fetches your homepage and a few linked pages (GET only), follows the scripts they load, including ones that load later, reads public source maps, and searches it all for secret keys and database connection strings. It tries /.env and /.git, and calls up to eight /api/ addresses named in your JavaScript, once each, with a plain GET and no credentials. It reads your headers, cookies, CORS answer to a made-up origin, certificate and Next.js version, and checks what your Supabase or Firebase public keys can read. That covers the outside of checks 5, 6, 8 and 9, and part of 7.
What it can’t do matters as much. It scans the URL you deployed and never sees your repo, so checks 1 to 4 are yours alone: no git history, .cursorignore, Run Mode, MCP servers or rules files. It never signs in, so it can’t do the two-account test in check 7. It never sends POST, PUT or DELETE, so Server Actions and routes that change data go untested. It doesn’t test rate limits or send attack payloads. Most Cursor apps leave no sign of the editor in the page, so the scan usually won’t name Cursor, and its fix prompts use wording for any AI builder; they work the same pasted into Cursor’s chat. It is a point-in-time, best-effort check, not a penetration test. A clean result means “nothing obvious from the outside right now,” not “safe.”
The summary is free: a score and the single worst thing we found. If you want everything, the $39 Launch Safe certificate includes every finding with a fix prompt you can paste into Cursor, a PDF, and seven days of re-scans while you fix things. A passing re-scan gives you a badge you can put on your site, and Monitor re-scans weekly for $9 a month after you launch.
Want a second pair of eyes from the outside?
Paste the address of the app you built in Cursor, prove you own it, and see what a stranger can read. The summary is free.
Run a free scanBefore you rely on a result, read the disclaimer.
Questions people ask
Is an app built with Cursor less secure than one written by hand?
Not by nature. Cursor writes ordinary code, usually Next.js or Vite with a database, and it can be as safe as anything else. The difference is speed and attention: an agent can add a route or rename a variable in seconds, and nobody may read the change. The mistakes in this guide are the same ones people make by hand. They just arrive faster, so the habit that matters most is reading the diff before it ships.
Does .cursorignore keep my .env file away from the AI?
Partly. Cursor’s docs say it already ignores .env files by default, along with everything in your .gitignore, and blocks ignored files from Agent, Tab, Inline Edit and @ mentions. The same docs say the terminal and MCP tools the agent uses cannot honour .cursorignore, and that complete protection isn’t guaranteed. So keep terminal approvals on, and say no to any command that would print your keys.
Is it safe to let Cursor’s agent run commands without asking me?
Cursor recommends Auto-review, which runs allowlisted calls, sandboxes shell commands when it can, and sends the rest to a classifier. Its docs are clear that this is a best-effort guardrail, not a security boundary. Run Everything turns every check off. For a project that holds real keys or talks to a production database, we would keep Auto-review or a short allowlist and read what it runs.
I pushed my .env file to GitHub. Is deleting it enough?
No. Deleting the file in a new commit leaves it in the history, and anyone who cloned the repo already has it. GitHub’s own advice is that the first step is to revoke or rotate every secret in it. Do that at each provider, then stop tracking the file, fix your .gitignore, and decide whether rewriting history is worth it. Once the keys are rotated, it often isn’t.
Can LockShip check my Cursor project?
It checks the app you deployed, not the project on your laptop. LockShip scans your live address the way a stranger would: the JavaScript it serves, source maps, exposed files, API routes it can find, headers, CORS, your Next.js version, and what your Supabase or Firebase keys can read. It can’t see your repo, your git history, your Cursor settings, your MCP servers or your rules files. Checks 1 to 4 on this page are yours alone.
Keep reading
- Vibe coding security checklist: what to check before you launch an AI-built app
- Leaked API keys in your frontend bundle: how to find them and what to do
- Is my v0 app secure? A plain check before you launch
- A Supabase RLS checklist for vibe-coded apps
- A Firebase Security Rules checklist for vibe-coded apps
- Is my Replit app secure? A plain check before you launch
Sources
We checked each of these on 18 October 2026. We ran every command above against a small Next.js 15.5 test app with fake keys, built and served on our own machine, against a throwaway git repo, and against lockship.dev. Cursor changes quickly, so if something here disagrees with its docs, trust the docs and tell us at hi@lockship.dev.
- Agent security, Cursor docs
- Run Modes, Cursor docs
- Ignore file, Cursor docs
- Model Context Protocol (MCP), Cursor docs
- Rules, Cursor docs
- Agent Review, Cursor docs
- Security and privacy hardening, Cursor docs
- Security, Cursor
- CVE-2025-54135 advisory (GHSA-4cxx-hrm3-49rm), Cursor on GitHub
- AI-powered Cursor IDE vulnerable to prompt injection attacks, BleepingComputer
- CVE-2025-54136 advisory (GHSA-24mc-g4xr-4395), Cursor on GitHub
- Cursor IDE’s MCP vulnerability (MCPoison), Check Point Research
- New vulnerability in GitHub Copilot and Cursor: how hackers can weaponize code agents, Pillar Security
- How AI coding assistants could be compromised via rules file, SC Media
- Cursor “Open-Folder” autorun vulnerability, Oasis Security
- Cursor AI editor lets repos autorun malicious code on devices, BleepingComputer
- LLM01: Prompt injection, OWASP GenAI Security Project
- Model Context Protocol (MCP), Supabase docs
- Supabase MCP security: how prompt injection leaked private tables, General Analysis
- gitignore, Git documentation
- Removing sensitive data from a repository, GitHub docs
- Push protection, GitHub docs
- Dependabot alerts, GitHub docs
- Environment variables, Next.js docs
- Data security, Next.js docs
- Next.js security update, 14 October 2026, Next.js blog
- headers, Next.js docs
- poweredByHeader, Next.js docs
- productionBrowserSourceMaps, Next.js docs
- Env variables and modes, Vite docs
- API keys, Supabase docs
- Row Level Security, Supabase docs
- Avoid insecure rules, Firebase docs
- Best practices for API key safety, OpenAI Help Center
- npm audit, npm docs
- A01:2025 Broken Access Control, OWASP Top 10
- API1:2023 Broken Object Level Authorization, OWASP API Security Top 10
- API4:2023 Unrestricted Resource Consumption, OWASP API Security Top 10
- HTTP Security Response Headers Cheat Sheet, OWASP
- Cross-Origin Resource Sharing (CORS), MDN
- WAF rate limiting, Vercel docs