← All posts
Security

Cursor App Security Checklist: What to Review Before You Ship AI-Written Code

The LaunchSure team

Cursor is the closest thing the industry has produced to a step change in development speed. Describe the feature, let the agent work across the codebase, review the result, keep moving.

That speed is real, and none of what follows argues against it. But it changes how code gets reviewed. Write a function by hand and every line passes through your head before it exists. When an agent produces a fourteen-file diff, you review the outcome instead: run the app, click the thing, accept. The code itself gets read once, quickly, or not at all.

This Cursor app security checklist closes that gap: ten areas, each with something specific to look at and something specific to do, covering the distance between “the feature works” and “I’m comfortable with strangers using this.”

Why AI-written code can carry risk that reads as finished

AI-generated code doesn’t look risky, and that’s the whole problem. Hand-written code produced in a hurry usually looks like it: inconsistent naming, a // TODO flagging its own debt. AI-written code arrives idiomatic and often commented, carrying every surface signal of having been reviewed. So it never triggers the instinct that makes you look harder.

Three things compound that:

The model builds what you asked for, not what you assumed. Ask for “an endpoint that returns a user’s invoices” and you get exactly that. The check confirming the requester is entitled to those invoices wasn’t in the prompt, so it isn’t in the code and its absence produces no error and no failing test.

Context resets. Conventions set in week one (this is where auth lives, every query scopes by tenant) don’t survive into a fresh session in week six unless your rules file puts them there.

The diff is bigger than your attention. Reviewing a five-line change is habit. Reviewing a multi-file edit mid-flow is work, and accept-all is right there. Over a few hundred commits, some fraction of your code has never been read by a human.

None of this makes Cursor-built software unsafe by nature. It means the review that once happened incidentally, while typing, now has to be done deliberately, once, before launch.

Review generated authentication logic

Auth is what models write most fluently, and fluency is the risk: the code reads correctly whether or not it is correct, and library APIs shift between versions in ways that quietly turn a check into a no-op.

Check: confirm tokens are verified, not merely decoded, a jwt.decode() where jwt.verify() belongs will trust a token anyone can forge. Confirm sessions expire, reset tokens are single-use and time-limited, and login is rate-limited. On Next.js, check your middleware matcher really covers the routes you think it does; one that skips /api is a common and expensive surprise.

Fix: anything you can’t trace end to end, trace now or replace it with a maintained library’s documented path.

Check authorization and user roles

Authentication asks who you are. Authorization asks whether you may do this specific thing to this specific record and it’s where the most common serious finding lives. A handler that confirms a valid session, then queries by the ID in the request without asking whose record it is, passes every test you’d think to run.

Check: create two accounts with real data in each. Log in as the first, find any request containing an identifier, and swap in the second account’s ID. If data comes back, that’s a launch blocker, not a backlog item. Then check roles: if isAdmin lives on a row the user can update, they can promote themselves.

Fix: enforce ownership in the query itself where: { id, userId: session.user.id } rather than where: { id } so it fails closed rather than returning someone else’s data.

Search for exposed secrets and API keys

Because Cursor works directly in your repository, this check has a dimension hosted builders don’t: git history. A key deleted three months ago still sits in the commit that introduced it, readable by anyone who can clone the repo.

Check: grep the working tree for sk_, secret, api_key, and password. Then search history git log -p -S 'sk_live', or gitleaks detect across the repo, which catches patterns you wouldn’t think to grep for.

Fix: rotate every key that was ever committed. Deleting the line does not invalidate the credential; only the provider’s rotate button does.

Review environment variable handling

Most frameworks expose a prefix to the browser bundle by design NEXT_PUBLIC_ in Next.js, VITE_ in Vite. Some values belong there; others don’t, and a prefix added during debugging to make something “just work” is how a server-side key ends up in JavaScript your users download.

Check: sort every environment variable your app reads into two columns (safe in the browser, server-only) then verify by searching your production bundle for the server-side ones. Check whether any secret is read inside a client component.

Fix: unprefix anything server-side, move the call behind an API route, and rotate whatever already shipped to browsers.

Scan dependencies and lockfiles

Two risks live here. The ordinary one is stale packages with published advisories. The newer one is package names that don’t mean what you think: models occasionally suggest imports for packages that don’t exist, and attackers register those names because they know it happens.

Check: run npm audit --production (an unrun audit is not a clean one) and read what it reports rather than reflexively running --force. Scan package.json for dependencies you don’t recognise and confirm each is the package you meant, with a plausible download count and repository. Make sure the lockfile is committed and CI installs with npm ci.

Fix: patch anything with a critical or high advisory in a path you reach, and remove packages left over from abandoned experiments.

Validate user input and form handling

React escapes text by default, which handles most cross-site scripting risk for free. The exceptions are wherever that default was switched off, and wherever validation lives only in the browser.

Check: search for dangerouslySetInnerHTML and innerHTML, and for each hit ask whether the content can originate from a user. Then test your API directly with curl, bypassing the form: an oversized string, a missing field, a wrong type. Client-side validation is a UX feature and takes ten seconds to route around.

Fix: validate on the server with a schema (Zod or equivalent) at the entry point of every route and action. Cap upload size and check file types server-side rather than trusting the Content-Type header.

Check server actions and API routes

This deserves its own pass, because it’s the check most often missed. A server action is not a private function only your form can call. It compiles to a public HTTP endpoint, callable by anyone who finds it. The same goes for every route handler. Being unlinked in your UI is not access control.

Check: enumerate every server action and API route, and confirm each has an explicit session check and an ownership or role check inside the function body, not merely in the page rendering the form. Pay closest attention to anything that deletes, changes a role, exports data, or triggers a payment. Then log out and call a few directly.

Fix: put the check at the top of every action and handler, and consider a withAuth() wrapper so next month’s action inherits it by default rather than by memory.

Review database access patterns

Agent-written queries tend to be correct about shape and careless about scope. In multi-tenant apps that shows up as a query missing its tenant filter which returns perfectly plausible data in single-account testing.

Check: search for raw query calls ($queryRawUnsafe, string-concatenated SQL) and confirm each parameterises rather than interpolates. Read your list-returning queries and confirm every one is scoped to the current user or organisation. With Postgres row-level security, verify policies are enabled on every table and test as an anonymous client, RLS defined but not enabled isn’t running. Confirm your service-role key never leaves the server.

Fix: parameterise everything, scope by default, and connect with a least-privilege role rather than the database owner account.

Remove unused generated code

Iterating with an agent leaves sediment: a replaced first version of a route, a debug endpoint that dumps session state, a seed script with hardcoded admin credentials. Each is live code with none of the review the current path received.

Check: find routes and components nothing imports, npx knip surfaces most of them quickly. Try /debug, /test, /seed, and /admin logged out, and look for commented-out blocks holding real credentials.

Fix: delete rather than comment out. It’s in git if you need it, and the safest endpoint is the one that doesn’t exist.

Confirm production deployment settings

The last category is configuration, where your live environment differs from the one you built in and the defaults are often wrong for the internet.

Check: debug mode off and source maps unpublished; users seeing a generic error message while the detail goes to your logs, not a stack trace with file paths and query fragments; CORS naming specific origins instead of *; cookies Secure, HttpOnly, and SameSite; HTTPS enforced; rate limits on auth endpoints. Check your logs aren’t recording tokens or personal data in plain text.

Fix: work through your host’s production checklist, then load the live site with devtools open and confirm the headers match.

Cursor red flags before launch

If any of these are true, get a second opinion before launch day rather than after it.

You’ve accepted agent diffs you haven’t read. Everyone has. But it means there’s code in production nobody has reviewed, and no way to know which code it is.

The app was built across many sessions over months. The longer the project, the more likely conventions drifted in ways no single diff would reveal including whether every route still checks a session.

You’re taking payments or storing personal data. The bug that’s an inconvenience in a side project becomes a breach, with disclosure obligations, once real customer records sit behind it.

None of these means your app is broken. Each names a specific unknown and unknowns cost far less to close before launch than during it.

What LaunchSure checks

A structured Cursor code review of your own work is worthwhile, and for an internal tool it may be enough. What it can’t give you is independence: you’re reviewing code you already believe in, and you can’t cite yourself as evidence to a customer.

A LaunchSure check reviews your app against a published standard covering the ground above, by someone with no stake in the answer. What each badge does and doesn’t attest to is set out on our standards and disclosure page. The same patterns recur across tools, which is why we wrote up the five risks we find in AI-built apps: they appear regardless of what generated the code, because they’re what a generator has no reason to add unless asked.

You get a plain-language report: every finding named without jargon, rated by severity, paired with a concrete fix. Not a scanner dump! Read a sample report to see the format. If you’re weighing how deep a review you need, AI code review vs security audit sets out where each is the right call.

If your app passes, you get a credential with a valid-through date, and anyone can verify a LaunchSure badge themselves, the part that keeps working after launch, on a landing page, in a pitch deck, or in your first business customer’s security questionnaire.

Before you ship

Cursor got your app built at a speed that wasn’t available two years ago, and the checklist above isn’t a knock on that. Every application ever shipped, hand-written or generated, has the same gap between working and launch-ready. What’s changed is that the review which used to happen incidentally now has to be scheduled. Spend an afternoon walking the perimeter, fix what surfaces, then decide whether you want an independent pass on top.

Built with Cursor? Get your AI-written code checked before you ship. Check your app and we’ll tell you exactly what we find.

Want proof your app is safe to ship?

Check my app