# Security audit workflow in Cursor

URL: https://developertoolkit.ai/en/cursor-ide/operate/security/
Description: Audit a codebase for security bugs with Cursor: scanners first, agent review second, a failing exploit test for every finding, and CI gates that keep it fixed.

A security audit in Cursor pairs deterministic scanners with an agent review. Dependency and secret scanners run first and produce reproducible evidence. Cursor's agent then reviews authentication, injection, and API surfaces, and every finding it reports must come with a failing exploit test before any fix merges. CI gates keep the fixed classes of bug from coming back.

This guide is for developers and tech leads whose team already builds with Cursor. The launch is three weeks out, the product stores payment and personal data behind a public API, and the compliance lead wants a security assessment by Friday. There is no pentest budget, and nobody on the team has time to read the whole codebase line by line.

An agent audit does not replace a professional penetration test or a threat model. It does find the pattern-shaped bugs, such as string-built SQL, unescaped HTML, missing authorization checks, and committed keys, and it turns each one into a test you can run.

## What a Cursor security audit gives you

- A scanner baseline (dependencies, secrets, static analysis) that anyone can rerun and get the same answer.
- Two review prompts that make the agent report findings as evidence, with a file, a line, and an exploit path, not opinions.
- A fix prompt that writes the exploit test first, so each fix is proven by a test going from red to green.
- A CI workflow and a project rule that stop the same bugs from coming back.
- A sign-off table that tells the tech lead what to check instead of reading every diff.

## Before you start the audit: set up a safe session

Three decisions made up front keep the audit itself from becoming a security incident.

1. **Work on a branch.** Create `security-audit-2026-09` and keep every change there until the sign-off in the last section.
2. **Keep secrets out of the agent's context.** Anything the agent reads is sent to a model provider. Run the secret scanner with redaction (step 2), and do not paste `.env` contents into chat. Check your team's Privacy Mode and data settings on [privacy and security in Cursor](/en/cursor-ide/team/privacy-security/).
3. **Keep command approval on.** The agent runs tests and may propose commands during this audit. Never switch command execution to a mode that runs every command without asking. Cursor groups these options under **Run modes** (**Settings > Agents > Approvals & Execution** in the desktop app, checked 2026-10-12): **Auto-review** runs allowlisted calls at once and sends other calls to the sandbox or to its classifier, which Cursor says "is not a security boundary"; **Allowlist** means "Actions in your allowlist run without approval"; and **Run Everything** means "Every tool call runs automatically." For this audit, use Allowlist with a short allowlist, so the decision on every other command stays with you. Repository content is untrusted input: OWASP's LLM Top 10 2026 (published 2026-08-04) warns that attackers plant instructions in "a public GitHub issue, a support ticket, or a malicious npm package".

## Step 1: Run the dependency and static-analysis scanners yourself

Start with tools whose output is deterministic. An agent's finding is a hypothesis; a scanner's finding is reproducible evidence, and it costs no tokens. Run these in the terminal at the repository root and keep the JSON files for the agent.

```bash
# Dependencies: pick the lines for your ecosystems
npm audit --json > audit-npm.json
pip-audit -f json -o audit-pip.json          # pip install pip-audit (2.10.1 on 2026-09-26)
cargo audit --json > audit-cargo.json        # cargo install cargo-audit

# Static analysis: OWASP Top 10 ruleset
pipx run semgrep scan --config p/owasp-top-ten --json -o semgrep.json
```

Then hand the results to the agent for triage. The prompt asks for a plan, not changes, because dependency upgrades are where audits break working code.

**Copy-paste prompt: triage scanner output**

```text
Read audit-npm.json, audit-pip.json and audit-cargo.json (whichever are present)
and semgrep.json in the repo root.
Do not change any file yet.

1. List every dependency advisory with severity HIGH or CRITICAL: package, installed
   version, fixed version, advisory ID, and what an attacker can do with it.
2. For each one, search our code for imports of that package and say whether the
   vulnerable function is actually reachable from our code. Quote the call site.
3. Group the upgrades: (a) patch or minor bumps with no API change, (b) major bumps,
   (c) no fix available. For (c), propose a mitigation or a replacement package that
   exists on the public registry today, and show the `npm view <pkg> version` output
   that proves it exists.
4. For semgrep.json, drop findings in test fixtures and generated code, and list the
   rest by rule ID, file:line and a one-line reason it is or is not exploitable.

Write the result to security-findings.md under the heading "Scanner baseline".
```

The "prove it exists" line is there for a reason. OWASP's 2026 LLM04 entry names slopsquatting: assistants suggest plausible package names that do not exist, and attackers register those names in advance.

## Step 2: Scan git history for committed secrets

A key deleted from the working tree is still in git history, so scan history, not only files. [Gitleaks](https://github.com/gitleaks/gitleaks) does this, and its `--redact` flag keeps the secret values out of the report, which matters because the agent reads that report next.

```bash
# brew install gitleaks, or use the container image
gitleaks git --redact --report-path gitleaks.json
# docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:v8.30.1 git /repo --redact --report-path /repo/gitleaks.json
```

**Copy-paste prompt: triage leaked secrets**

```text
Read gitleaks.json. The secret values are redacted; do not try to recover them and
do not open .env files.

For each finding:
1. File, line, commit, author date, and the rule that matched (for example aws-access-token).
2. Classify it: CRITICAL if the file or variable name points to production,
   HIGH if you cannot tell, MEDIUM if it is clearly a test fixture or example.
3. Say where the code that uses it should read it from instead (environment variable,
   Cloudflare/AWS/GCP secret store, CI secret) and make that code change on this branch.
4. Add the exact provider console action needed to rotate it. Rotation is done by a
   human, not by you.

Also check: which .env* files are tracked by git, and whether .gitignore covers them.
Append everything to security-findings.md under "Secrets".
```

Removing a leaked key from the code does not make it safe. Rotate every CRITICAL and HIGH key first; rewrite history afterwards only if policy requires it.

## Step 3: Review authentication and authorization with the agent

Scanners are weak at authorization bugs, which is where an agent review earns its cost. Run this in [Plan Mode](/en/cursor-ide/build/agent-modes-deep-dive/) so the agent reads and reasons before it can edit anything.

**Copy-paste prompt: authentication and authorization review**

```text
Audit authentication and authorization in this repo. Read src/auth, src/middleware
and every route handler under src/api (adjust the paths if ours differ). Do not edit
code. Report findings only when you can point to the line.

Check:
1. Password storage: argon2, scrypt or bcrypt with a cost factor, never MD5/SHA-1/plain SHA-256.
2. Login: rate limit or lockout per account and per IP; does X-Forwarded-For let a
   client pick its own IP?
3. Sessions: expiry, regeneration after login, server-side invalidation on logout and
   password change; cookies set with Secure, HttpOnly and SameSite.
4. JWTs, if used: algorithm pinned (no "none", no HS/RS confusion), expiry checked,
   revocation path exists.
5. Authorization: build a table of every route with its HTTP method, whether it requires
   a session, and which ownership check it runs. Flag any route that loads a record by
   an ID from the request without checking that the record belongs to the caller (IDOR).
6. Admin routes: separated and protected by a role check on the server, not only in the UI.

For every finding give: severity (CRITICAL/HIGH/MEDIUM/LOW), file:line, the exact request
an attacker would send, and what they get back. Append to security-findings.md under
"Auth". Mark anything you are unsure of as "needs human check" instead of guessing.
```

The route table in item 5 is the most valuable artifact of the whole audit. A reviewer can scan 60 rows for a missing ownership check in minutes, which is much faster than reading 60 handlers.

## Step 4: Review injection and API hardening

Use a second, narrower prompt for input handling. Splitting the review keeps each context focused and each finding list short enough to check.

**Copy-paste prompt: injection and API surface review**

```text
Audit input handling in src/api, src/db and src/components. Do not edit code.

1. SQL: any query built with string concatenation or template literals from request
   data; any .raw()/.unsafe()/sql.raw call; table or column names taken from input.
2. XSS: dangerouslySetInnerHTML, v-html, innerHTML, or unescaped template output of
   user content; is a Content-Security-Policy header set, and what does it allow?
3. Command and code execution: exec, spawn with shell: true, eval, new Function, or
   child_process calls that include request data.
4. Path traversal and SSRF: file paths or outbound URLs built from request data.
5. API hardening: schema validation (zod or similar) on every body and query; request
   body size limits; error responses that leak stack traces or SQL; CORS origin list
   (flag "*" with credentials); webhook signature verification.

For each finding: severity, file:line, the malicious input, and the secure pattern in
one or two lines. Cross-check against semgrep.json and say which findings semgrep
already reported. Append to security-findings.md under "Injection and API".
```

The cross-check against `semgrep.json` tells you which findings are new. Findings that only the agent reports need the most scrutiny, because they are where false positives live.

## Step 5: Prove each finding with a failing test, then fix it

Do not accept a finding or a fix on the agent's word. Each CRITICAL and HIGH finding becomes a test that demonstrates the exploit against the current code. The fix is done when that test passes and the rest of the suite still does.

**Copy-paste prompt: exploit test first, then fix**

```text
Take the CRITICAL and HIGH findings in security-findings.md, one at a time, in order.

For each finding:
1. Write a test that performs the attack (the request, input or call from the finding)
   and asserts the safe behaviour: 401/403/404, escaped output, a parameterised query,
   a rejected path. Put it next to the existing tests for that module.
2. Run only that test and show me it FAILS on the current code. If it passes, the
   finding is a false positive: mark it so in security-findings.md and move on.
3. Make the smallest change that fixes it. No refactors, no new dependencies unless
   the finding requires one.
4. Run the new test (must pass) and the full test suite (must stay green). Show both.
5. Record in security-findings.md: finding, test name, fix commit summary, status.

Stop after each finding and wait for me to say "next". Do not commit.
```

Step 2 of the prompt is the false-positive filter. A finding that cannot be turned into a failing test is either not exploitable or not understood yet; neither case should produce a code change. For the wider pattern, see [test-driven development in Cursor](/en/cursor-ide/test/testing-tdd/).

## Step 6: Keep the audit's results in place with CI and rules

A one-off audit decays with the next hundred agent-written commits. Three layers keep it current.

**A CI gate.** Add a workflow that fails the pull request when it brings in an OWASP-ruleset hit or a secret, and whenever the dependency tree has a HIGH or CRITICAL advisory. The token is read-only, the checkout does not keep credentials, and every tool is pinned to a version. On `pull_request`, GitHub runs this workflow file from the pull request's merge ref, so a change that edits `security.yml` (an agent's included) can weaken or delete the gate in the same pull request. Put `/.github/` under `CODEOWNERS` with a required code-owner review in branch protection or a ruleset, and make the `scan` job a required status check.

```yaml
# .github/workflows/security.yml
name: security
on: [pull_request]
permissions:
  contents: read
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      # Pin actions to a full commit SHA in your own repo; a tag can be moved.
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
          persist-credentials: false
      # Fails on ANY high advisory, old or new. npm audit has no baseline.
      - run: npm audit --audit-level=high
      # Semgrep and Gitleaks report only what this pull request adds.
      - env:
          BASE_REF: ${{ github.base_ref }}
        run: >-
          pipx run semgrep==1.178.0 scan --config p/owasp-top-ten --error
          --baseline-commit "origin/$BASE_REF"
      - env:
          BASE_REF: ${{ github.base_ref }}
        run: >-
          docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:v8.30.1
          git /repo --redact --log-opts="origin/$BASE_REF..HEAD"
```

The three scanners treat existing findings differently, so check each against step 1 and step 2:

- **Semgrep** compares against the base branch with `--baseline-commit`, so a finding you closed as a false positive in step 5 does not fail every later pull request. Suppress it in code with a `nosemgrep` comment on that line as well, so the full scan from step 1 agrees.
- **Gitleaks** scans only the pull request's commits with `--log-opts`. The keys from step 2 stay in history after rotation, and a full-history scan would fail every pull request for good. To keep scanning all of history instead, commit the post-rotation report from step 2 and pass it as `--baseline-path gitleaks-baseline.json`.
- **npm audit** has no baseline and fails on any HIGH or CRITICAL advisory in the tree, including one you accepted in step 1. The gate turns green only when each such advisory is fixed or its package replaced. Record every accepted advisory in `security-findings.md` with its ID, an owner and a review date, so a red step is a known, dated decision and not a reason to switch the gate off.

Add a Gitleaks pre-commit hook as well, so a commit that contains a key is blocked on the laptop before it reaches CI. The setup is in the [Gitleaks README](https://github.com/gitleaks/gitleaks). For TruffleHog, Snyk and the Semgrep Guardian plugin, see [security gates for agent-written code](/en/ecosystem/observability/agent-security-gates/).

**A project rule.** Ask the agent to write a security rule from the audit, so new code follows the fixes. Cursor's rules are covered in [project rules](/en/cursor-ide/quick-start/project-rules/).

**Copy-paste prompt: turn the audit into a project rule**

```text
Create a Cursor project rule named "security" that applies to all source files.
Base it only on the fixed findings in security-findings.md. For each fixed class of bug,
write one imperative line naming our actual helper or library, for example:
"Build SQL only with the query builder in src/db/client.ts; never interpolate request data."
Keep it under 25 lines. Then list the lines you left out and why.
```

**Pull request review.** Cursor's **Bugbot** "reviews pull requests and identifies bugs, security issues, and code quality problems", and **Security Agents** "scan your code for security bugs, risky patterns, and vulnerabilities" (Cursor docs, checked 2026-08-28). Cursor's Security Review, added for Teams and Enterprise on 2026-09-23, posts one comment per pull request on exploitable bugs (Cursor changelog, checked 2026-10-12). Treat each as an extra reviewer, not a gate: an LLM reviewer can miss what a deterministic scanner catches. Setup and trade-offs are in [code review in Cursor](/en/cursor-ide/ship/code-review/) and the [AI code review bots comparison](/en/ecosystem/observability/ai-code-review-bots/).

If you want findings while the agent writes code, not only at pull request time, Semgrep's MCP server ships inside the `semgrep` binary. Install it first with `pipx install semgrep` (or `brew install semgrep`) so `semgrep` is on your PATH; `pipx run` from step 1 does not put it there. Then add it to `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "semgrep": { "command": "semgrep", "args": ["mcp"] }
  }
}
```

## How do you sign off a Cursor security audit without reading every line?

The tech lead signs off on evidence, not on the diff. Each row has an artifact that someone other than the agent can check.

| Claim                           | Evidence to check                                                      | Who checks                      |
| ------------------------------- | ---------------------------------------------------------------------- | ------------------------------- |
| No known vulnerable dependencies | `npm audit --audit-level=high` (and `pip-audit`) exit 0 in CI, or each remaining advisory is an accepted risk with an owner and a date | CI, plus the tech lead for accepted risks |
| No secrets in code or history   | Gitleaks exits 0 on every pull request's commits (or against the committed baseline); every finding in the step 2 report rotated, with rotation confirmed | Owner of each provider account  |
| Every route has an owner check  | The route table from step 3, reviewed row by row                        | Tech lead                       |
| Each fix closes its finding     | Exploit test failed before and passes after; full suite green           | CI, plus the reviewer spot-checks two tests |
| Nothing new slips in            | Security workflow required on the default branch; rule committed        | Tech lead                       |

Anything marked "needs human check" in `security-findings.md` stays open until a person closes it. Store the file with the release notes: it is the assessment the compliance lead asked for.

## When a Cursor security audit goes wrong

**The agent reports a clean result.** "No vulnerabilities found" means no known pattern matched. Business-logic flaws, such as a client-set price or API keys that survive account deletion, need a threat model. Recover by asking in Plan Mode: "You are an attacker who wants free orders from this checkout. List five attempts, and the file and line each one targets." Then turn each credible attempt into a test.

**False positives pile up.** An ORM call can look like string-built SQL. Recovery: the failing-test rule in step 5. If the exploit test passes on unchanged code, close the finding as a false positive with the test as proof, and do not change the code.

**A fix breaks a real feature.** CSRF tokens break API clients that never sent them, and a strict CORS list blocks a staging frontend. Recovery: the full suite runs after every fix, and each fix is a separate change, so you can revert one without losing the others. Add the broken client to the tests before you try again.

**A dependency upgrade breaks the build.** Major-version bumps change APIs. Recovery: apply group (a) upgrades from step 1 together, then do each major bump alone with the full suite. If a major bump is blocked, record the mitigation and the reachability check from step 1 as an accepted risk with an owner and a date.

**The agent adds a package that does not exist or is not the one you meant.** A near-miss name can be a slopsquatted package. Recovery: reject any new dependency that the agent did not verify on the registry, check the publisher and download history yourself, and keep `npm audit` and the lockfile diff in review.

**Repository text steers the agent.** A comment, fixture, or README that says "ignore previous instructions" is aimed at the auditor. Recovery: keep command approval on, stop the session when the agent proposes an action that the prompt did not ask for, and report the file as a finding in its own right.

## Where to go next after a Cursor security audit

- [Privacy and security in Cursor](/en/cursor-ide/team/privacy-security/) — Privacy Mode, data handling, and how to configure the agent's own execution safely.
- [Code review in Cursor](/en/cursor-ide/ship/code-review/) — Put Bugbot and human review on every pull request so the audit's fixes stay fixed.
- [Security audits with Claude Code](/en/claude-code/operate/security-audit/) — The same audit from the terminal, with hooks and headless CI runs.
- [Security scanning with Codex](/en/codex/operate/security-audit/) — The Codex version of this workflow, for teams that run more than one tool.

Back to the [Cursor operate overview](/en/cursor-ide/operate/).
