AgenticCLI
▮ shipguardis a free, local secure-ship agent for AI-built apps — 5 checks in ~2s.$ npm i -g @agenticcli/shipguard

Is Cursor's AI Output Secure? We Scanned a Seeded Diff

15 min · jul 2026

Short answer: "Cursor security review" now means two things: Cursor's own beta Security Reviewer agent, which comments on every PR, and checking the code Cursor writes. In a seeded, representative diff — a new admin API route with no auth guard plus a hardcoded OpenAI key — a real ShipGuard scan returned four high-severity findings and a DO_NOT_SHIP verdict.

A seeded Cursor-style diff went through shipguard scan on the current public binary and came back with four high-severity findings and a DO_NOT_SHIP verdict: two hardcoded copies of the same OpenAI key, a missing middleware.ts, and an admin API route with no session check. Capture ai-tool-harness-vuln, @agenticcli/[email protected], 2026-07-28. The terminal output is further down, and so is the fixture it read.

This scene is assembled, not reported: a solo founder asks Cursor to add an admin settings panel — a small API route so the team can flip a feature flag without redeploying. Cursor writes the route, wires it to a config object, and the panel works on the first try. The founder clicks "Accept All," the way Cursor is built to be used, and moves on to the next feature.

Nobody asked whether that route checks who is calling it. It doesn't. And the OpenAI key the config object needed for a separate feature is now sitting in the file too, in plain text.

That is not a story about Cursor being a bad tool. It's a story about a question nobody asked, because the panel worked.

Since 30 April 2026 the phrase "Cursor security review" has had two owners. Cursor ships a feature by that name: a Security Reviewer agent of its own.

The other owner is the question a developer actually types it for: whether the code Cursor just wrote is safe to ship. The change we seeded is representative rather than a captured session, and it ran through ShipGuard's free local CLI. We ran the same experiment against Claude Code's output in a separate piece, and the shape of the failure was the same.

At a glance

  • Cursor Security Review is a real, shipped Cursor feature: a Security Reviewer on every pull request, plus a scheduled Vulnerability Scanner, in beta on Teams and Enterprise plans.
  • Nothing in Cursor's own security agents reads the diff at the moment you accept it in the editor.
  • Andrej Karpathy's own post coining "vibe coding" names Cursor Composer directly and describes accepting every diff without reading it.
  • We ran a real scan, not a mockup: shipguard scan against a seeded fixture, captured with a real terminal renderer, with the fixture itself shown below.
  • The scan caught all four findings and returned DO_NOT_SHIP on the current public ShipGuard binary. One of the four is a structural Next.js check that would fire on an empty repo of that shape, so three of them are evidence about the diff itself.

What does a Cursor security review actually mean?

Search the phrase on 2026-07-29 and page one is mostly Cursor's own product: cursor.com/docs/security-review, cursor.com/docs/security-agents, cursor.com/security, and the changelog entry dated 30 April 2026. Snyk's teardown of the prompts behind those agents sits alongside them. Two of those Cursor pages document the feature and the changelog announces it. None of them is about the code Cursor writes for you.

That is a good result for a search engine and an awkward one for a developer who typed the phrase with an unreviewed diff still open, because the feature and the question share a name and do not share a scope. The rest of that page is independent explainers — TrueFoundry, Reco, Endor Labs, MintMCP, a Medium post — and they are the real competition for anyone typing the phrase.

Is the question about Cursor the editor, or the code Cursor writes?

Tool security is about Cursor the editor: what it is allowed to do on your machine, how it stores your code, whether its own agent can be talked into something. Output security is about the code that came out of it. Does the route Cursor just wrote check who is calling it? Does the config file it touched hold a live key? A perfectly audited editor can still hand you an admin route with no auth check. The editor did exactly what it was asked to do. The feature it produced is still broken.

The output half is the one a scan can reproduce, so that is the half measured here. We did not test Cursor's own editor, its account security or its telemetry. Cursor publishes its own security documentation for that, and independent researchers have published findings against the editor itself.

What is Cursor Security Review, the feature?

Cursor's changelog entry dated 30 April 2026 is unambiguous about the tier:

Cursor Security Review is now in beta on Teams and Enterprise plans.

— Cursor (Anysphere) · Cursor Security Review changelog, April 30, 2026

It ships two agents. The first is a reviewer:

Security Reviewer checks every PR for security vulnerabilities, auth regressions, privacy and data-handling risks, agent tool auto-approvals, and prompt injection attacks.

— Cursor (Anysphere) · Cursor Security Review changelog, April 30, 2026

The second runs on a schedule rather than on an event. Per Cursor's Security Agents documentation, "Vulnerability Scanner scans your codebase at rest. Use it to find pre-existing vulnerabilities, long-standing issues, and problems missed during PR review." Both are genuinely useful and neither is marketing: prompt injection and agent tool auto-approvals are two of the harder things to check for, and Cursor put them in the reviewer's remit by name.

How does a per-PR LLM reviewer differ from a local pre-ship scan?

Three ways that matter, and none of them is about quality. It runs later, at pull-request time rather than at accept-diff time. It runs on a tier, so a solo developer on a personal plan does not have it. And it is a language model reading a diff, which means its findings carry a model's judgment rather than a rule's.

ONE DIFF, SEVEN MOMENTS Where each check runs Cursor writes the diff You accept it It sits in your working directory shipguard scan local, free, on every diff git commit A pull request opens Cursor Security Reviewer plan-gated, on the pull request Merge The dashed stretch is the gap: after you accept and before a pull request exists, no Cursor security agent reads the code.
One diff, seven moments, and two checks that sit at different points on it. Cursor's Security Reviewer starts when a pull request opens, on the plans that carry it. A local scan reads the working directory, which is the stretch between accepting a diff and opening that pull request.

Randall Degges of Snyk, who read the published prompts behind Cursor's security agents and rated them well, named the consequence directly in I Read Cursor's Security Agent Prompts, So You Don't Have To: "the agent cannot mark its own homework. You need an independent validation layer confirming what the LLM found." Snyk sells a deterministic scanner, so read that as an interested party making a point that happens to be right.

Why does Cursor's fast-accept workflow make this worth checking?

Cursor is built around speed: inline suggestions, a chat-driven Composer mode, and a workflow that assumes you will accept most of what it proposes rather than hand-edit each line. That design choice is why Andrej Karpathy picked Cursor as his example when he coined the term that would define this entire category.

In the post that introduced vibe coding to general use, he named the tool directly: "It's possible because the LLMs (e.g. Cursor Composer w Sonnet) are getting too good." He then described his own habit in the same post, plainly:

I "Accept All" always, I don't read the diffs anymore.

— Andrej Karpathy, independent AI researcher/educator (post-OpenAI, post-Tesla) · original "vibe coding" post, February 2, 2025

This is a description of a workflow, not an accusation against a product. Plenty of features shipped this way turn out fine. A review step that is optional gets skipped under time pressure far more often than a review step that is mandatory.

Cursor's own Security Reviewer is mandatory — on the plans that have it.

What did we actually run, and against which version?

The fixture is ours and we wrote it to contain these findings. It plants a new Next.js API route at app/api/admin/config with no auth check, alongside a config module and an OpenAI client file that both hardcode the same key — styled as the "quick testing" shortcut a fast-accept session plausibly leaves behind.

A scan of it could not have come back clean, and saying otherwise would be theatre. Which of the seeded problems the free tier actually names, where it names them, and what verdict it reaches are not settled by how the fixture was written.

Terminal listing of the three seeded fixture files ShipGuard scanned: lib/config.ts and lib/openai.ts each hardcoding the same obviously fake sk-proj- OpenAI key, and app/api/admin/config/route.ts exporting GET and POST handlers with no session check anywhere

The seeded fixture, read out of the same bind-mounted directory the scan reads, inside the same container image. Capture ai-tool-harness-fixture, @agenticcli/[email protected], 2026-07-28.

Which binary produced the numbers below?

The Docker image this pipeline uses for terminal captures had @agenticcli/[email protected] baked in from an earlier build. The current public release, confirmed against the npm registry on 2026-07-28, is @agenticcli/[email protected]. A capture on the stale image would have been a claim about a tool that no longer exists in that exact form, so the image was rebuilt against 0.5.1 first.

Every result below reflects the binary a developer running npx @agenticcli/shipguard scan actually gets today. The scan itself is the same one anyone can run for free, no account required:

npx @agenticcli/shipguard scan --json

What did the real scan find?

DO_NOT_SHIP. Four findings, all high severity, captured directly from the running container:

ShipGuard scan output showing four high-severity findings, two hardcoded OpenAI API keys, a missing Next.js middleware.ts, and an unprotected admin API route, against a seeded Cursor-style fixture, ending in a DO NOT SHIP verdict

Real scan, capture ai-tool-harness-vuln, @agenticcli/[email protected], 2026-07-28.

Finding File Severity
Hardcoded OpenAI API key lib/config.ts:3 High
Hardcoded OpenAI API key lib/openai.ts:4 High
No Next.js middleware.ts found middleware.ts High
Unprotected API route handler app/api/admin/config/route.ts High

We ran the scan twice against a live binary rather than a static fixture. Capture ai-tool-harness-vuln stamps the two runs at 14:55:42 and 14:55:54 UTC on 2026-07-28, and their payloads are byte-identical once the timestamp is removed, which a hand-written transcript cannot produce.

Why is the same key two findings?

Two of these are the same underlying mistake in two places: the same OpenAI key, hardcoded once in the config module and again in the client file that reads it. That duplication happens naturally when one file gets built and a second file writes the value out again instead of importing it from the first.

If your stack is Next.js and Supabase specifically, our secret scanner walkthrough for that stack runs the same category of mistake end to end, and our teardown of a hardcoded Stripe key shipped by an AI coding tool explains why an agent writes the literal in the first place and why a linter stays quiet about it.

GitHub runs its own secret scanning over pushed repositories, which sits at a different point on the same timeline: after the key is in git history rather than before. Our comparison of GitHub's native secret scanner against dedicated CLI tools walks through what each side of that line is good for.

What does the missing guard actually expose?

The third finding is structural, checked on every Next.js project regardless of which feature prompted the scan: no middleware.ts covering the sensitive routes. The fourth is the one that matters most for this feature. The admin config route has no session check in either its GET or its POST handler, so anyone who finds the URL can read the current config or overwrite it.

That failure rhymes with two others AI-built apps hit constantly: a Supabase table shipped without Row Level Security and a Firebase project left in test-mode rules. Different mechanism, same missing guard.

Is this a Cursor-specific problem, or a fast-AI-coding problem?

Nothing about this failure pattern is unique to Cursor's model or its implementation. It is a consequence of how a fast-accept workflow meets an ordinary feature request, and it shows up across tools built the same way. OpenAI's own Codex Code Review team makes almost exactly this point about where a fixed rule helps and where it can't:

"Tests and linters work well for checks you can express deterministically; repository rules help capture the judgment that is harder to encode."

— OpenAI Developers · Custom Code Review rules for Codex

An auth check on an admin route is expressible deterministically: either a session gets verified before the handler runs, or it doesn't. It's precisely the class of check a fixed rule catches every time, regardless of which model or which editor wrote the route.

Does anyone have numbers on rules versus judgment?

OpenAI does, for its own product. Its own writeup, Custom Code Review rules for Codex, measures what happens when Codex's reviewer is taught specific repository rules instead of relying on general judgment, and reports: "In the primary suite, rule-guided variants recovered 98% of the required custom findings, compared with 58.3% in the baseline control."

That figure describes Codex Code Review, a different product measuring a different thing, and it is not a claim about ShipGuard or about Cursor. A fixed, deterministic check catches a category of mistake that general judgment, human or model, catches inconsistently.

If the tool writing your code is an autonomous agent rather than an editor you are driving, the same argument extends from code to permissions, which our piece on AI agent security covers.

What does ShipGuard check about Cursor, and what can't it check?

Tool security Output security
Covers What Cursor is allowed to do on your machine while it works What the feature it just wrote actually contains
Evidence in this piece Not measured here. Three third-party findings are cited below A real ShipGuard scan: four findings, DO_NOT_SHIP, shown above
Where to look instead Cursor's own security documentation, and the advisory databases A deterministic pre-ship gate, run the same way on every diff

What is already on the public record about Cursor the editor?

The tool-security half has a real public record and it is not ours. GitHub's Advisory Database carries GHSA-4cxx-hrm3-49rm against Cursor: "Cursor allows writing in-workspace files with no user approval." It was fixed in Cursor 1.3.9. Aim Labs, who found it, described the consequence in their own CurXecute writeup: "Cursor instantly executes any new entry added to ~/.cursor/mcp.json. No confirmation is required."

The same flaw is CVE-2025-54135 in the National Vulnerability Database, where NIST's own CVSS v3.1 base score is 9.8, critical. GitHub, as the reporting authority, scored the same entry 8.5, high. Which record you open changes how bad it looks.

The Register reported in April 2026 that a Cursor agent destroyed a startup's production database, quoting founder Jer Crane: "It took 9 seconds." The advisory, the Aim Labs disclosure and this incident are all about the editor and its agent, not about the code they produce.

Why doesn't ShipGuard integrate with Cursor at all?

It scans your working directory the same way regardless of which tool, or which human, wrote the code sitting in it. That is a deliberate design choice rather than a missing feature: a scan that only worked on one editor's output would be useless the moment a team used two tools in one repository, which is common.

The free tier covers secrets, auth gaps, database risk, payment-handler mistakes and deployment configuration. It runs entirely locally, with no code leaving your machine. It does not evaluate Cursor's own account security or its telemetry practices, and none of that is in scope for a scan that only ever sees your files.

The secrets check that caught both hardcoded keys above runs on RE2JS, a linear-time regex engine chosen so a crafted or unusually large file can't hang the scan through catastrophic backtracking. A scanner that can hang on one crafted file gets skipped under deadline pressure the same way an optional code review does, and a skipped scanner catches nothing. The same engine backs both the free-tier patterns and the broader corpus on paid plans.

How does this compare with the Claude Code output audit?

We ran a similar audit against Claude Code's output in a separate piece, with an honest caveat: different fixtures, different methodologies, not a head-to-head verdict on which tool is safer.

Audit What was tested Real ShipGuard verdict
This piece (Cursor) One seeded change: a new admin API route plus a hardcoded key, styled after Cursor's fast-accept workflow DO_NOT_SHIP, 4 of 4 findings high
Claude Code output audit Four independent feature prompts: an auth-gated dashboard, a Stripe webhook, a Supabase CRUD API, a file upload endpoint 1 generation clean, 3 of 4 DO_NOT_SHIP

The same two ingredients keep turning up in different combinations: authentication that exists somewhere in the app but not on the route that needed it, and a credential hardcoded as a "temporary" convenience that outlived its reason. The Claude Code run produced them in separate generations; this fixture holds both at once, because that is how we built it.

A pre-merge checklist before you accept a Cursor diff

None of this requires reading every character of every diff. Our rundown of twelve vibe-coding security checks is the longer list; these five are the ones this fixture actually exercised.

  • Check whether the new route derives its caller from a verified session, not from a query parameter or a request body field the caller supplied.
  • Check whether any config or client file hardcodes a key instead of reading it from an environment variable, including a fallback constant meant only "for local dev."
  • Check whether middleware.ts exists and its matcher actually covers the new route, not just the page that links to it.
  • Run an automated scan on the diff before merging, not only on the diffs that felt risky while you were reading them.
  • Check whether the route or feature actually needed the credential it was given at all, since a scoped-down key does less damage than a broad one if it ever does leak.

Does Cursor's own reviewer cover any of these?

The last two are the ones a fast-accept workflow structurally can't give you on its own. If your team is on Teams or Enterprise, the two checks overlap and that is fine. If you are one person on a personal plan, the pull-request reviewer is not there at all, and the working directory is the only place left to look.

Where to check this yourself

Run the exact free-tier command behind every finding above, no account, no code leaving your machine:

npx @agenticcli/shipguard scan --json

Add --strict if you want a CI step that actually fails the build on a finding like the ones above, rather than only reporting them:

npx @agenticcli/shipguard scan --changed --strict --json

A hardcoded key is the same failure whether it shipped from Cursor, Claude Code, Codex, or a human typing directly into the file. Our look at what a real secret scan found in a vibe-coded app covers that half of the problem in more depth than fits here.

See what ShipGuard actually checks for the full rule set behind today's four findings, including the categories that live beyond the free tier.

What does a clean scan actually prove?

A clean scan is not proof of security. It is proof that the specific patterns a scanner knows to check weren't present in that run. This one wasn't clean on the current public binary — and the fixture was built to look like an ordinary Tuesday feature request. That is exactly the case a deterministic gate exists for: not the diff that feels risky, but the one that doesn't.


Published by AgenticCLI — developer tools for teams shipping AI-assisted code. ShipGuard is a deterministic, CLI-first release gate that runs locally, with no code leaving your machine on the free tier. See all checks at agenticcli.dev/shipguard.

FAQ

Is Cursor safe to use?
Cursor is a code editor built on real AI models, and nothing about that architecture is inherently unsafe. Two different questions hide behind the word "safe", though. The first is whether the editor and its agent can be made to do something you did not ask for: the GitHub Advisory Database carries GHSA-4cxx-hrm3-49rm against Cursor itself, fixed in Cursor 1.3.9, and The Register reported a Cursor agent deleting a production database in April 2026. The second is whether the feature it just generated for you is safe to ship. Those are different risks with different owners, and only the second one is checkable from your own working directory with a free local scan.
Does Cursor check its own code for security bugs?
Yes, at pull-request time, on some plans. Cursor's own changelog dated 30 April 2026 says "Cursor Security Review is now in beta on Teams and Enterprise plans", and its Security Reviewer agent "checks every PR for security vulnerabilities, auth regressions, privacy and data-handling risks, agent tool auto-approvals, and prompt injection attacks". What no plan gives you is a check at the moment the diff lands in your editor. Andrej Karpathy, who coined "vibe coding" in a post naming Cursor Composer, described that moment plainly: "I "Accept All" always, I don't read the diffs anymore." A PR reviewer sees that code later, if a PR ever happens.
What did the seeded Cursor-style diff we scanned contain?
In the change we seeded, a new admin configuration API route and a hardcoded OpenAI API key shipped in the same diff. ShipGuard's free scan returned four findings: two hardcoded-secret matches for the OpenAI key, a missing Next.js middleware.ts warning, and an unprotected API route handler under /api/admin/config. None of it required an adversarial prompt. It is the ordinary shape of "make this feature work" code, and the same two ingredients turn up across tools: our audit of Claude Code's own output found a hardcoded credential in one generation and a missing authorization check in another.
How is Cursor's security model different from a deterministic pre-ship scan?
They run at different moments and answer to different rules. Cursor Security Review is an LLM agent that reads a pull request and comments on it, so its judgment is probabilistic and its findings are worth validating. Snyk's Randall Degges, reading the published prompts behind those agents, put the limit plainly: "the agent cannot mark its own homework. You need an independent validation layer confirming what the LLM found." Snyk sells a deterministic scanner, so read that as an interested party making a point that happens to be right. A deterministic scan like ShipGuard does not look at the editor or the pull request at all. It reads the working directory and checks fixed, repeatable rules, the same way every time, whichever tool wrote the code and however clean the demo looked.
Should I review every diff Cursor generates before merging it?
Reading every line is the right instinct, and a deterministic gate exists as a complement to that review, not a replacement for it. The realistic middle ground: read the code you do not fully understand, and run an automated scan on every generation regardless of how clean it looks. A working demo and a clean scan are two different claims. The fixture was built to look like an ordinary Tuesday feature request, and ShipGuard's free scan still named four specific findings a glance at the running app would never have surfaced.
Can a clean ShipGuard scan prove Cursor's output is secure?
No. A clean result means the specific patterns ShipGuard's free tier checks for, covering secrets, auth gaps, payment-handler mistakes, database risk and deployment configuration, were not present in that particular scan. It says nothing about business logic errors, authorization edge cases outside those patterns, or categories the free tier does not check yet. Treat a clean scan the way you would treat a passed test suite: real evidence, one gate cleared, not a certificate that nothing is wrong anywhere in the code Cursor just wrote.

────────[ ▮ gate ]────────

Don't ship the next one.

Free, local, no account. Catches this exact bug class before deploy.

$ npx @agenticcli/shipguard scan