pipx; Node.js with npm; Gitleaks; Semgrep; Trivy (optional, for Docker/IaC files); a CI runner such as GitHub Actions. Tool versions change often — run --version on each scanner and check its documentation for the flag set that matches your installed release.
What to do: install three independent scanner layers and confirm each one runs on your machine before you point it at the AI-written code.
Why: AI assistants generate a lot of code fast, and the typical failure modes are different from human-written code: a live API key pasted into a config file, a dependency name the model invented, and a plausible-looking function with an injection bug. No single tool covers all three, so you stack them.
| Layer | Question it answers | Tool | What it reads |
|---|---|---|---|
| Secrets | Did the assistant paste a real key or token into a file? | Gitleaks | the whole repo, including Git history |
| SCA (software composition analysis — checks third-party libraries for known CVEs) | Does a library version I depend on have a published vulnerability? | `pip-audit` (Python), `npm audit` (Node.js) | lockfiles and manifests |
| SAST (static application security testing — reads source code without running it) | Does the code contain injection, weak randomness, unsafe deserialization, etc.? | Semgrep | source files |
| Container / IaC | Does a Dockerfile or YAML manifest run as root or expose a port? | Trivy | `Dockerfile`, `*.yaml` |
git installed and the project is a Git repository (git rev-parse --is-inside-work-tree prints true)python3 --versionnode --version and npm --versionreports/)Install the scanners:
pipx install pip-audit
brew install gitleaks semgrep trivy
On Windows use winget or scoop instead of brew — check each tool's documentation for the current package name.
Example: after installation, gitleaks version prints a single version string and semgrep --version prints a version plus a Python path. If a command prints command not found, the binary is not on your PATH — reopen the terminal or add ~/.local/bin to PATH.
Before the first run, exclude directories that produce noise and false positives:
cat > .semgrepignore <<'EOF'
node_modules/
.venv/
dist/
build/
.cursor/
EOF
Why this works: Semgrep and Gitleaks both walk the filesystem; excluding vendored and tool-cache directories stops them from reporting the same issue hundreds of times.

What to do: commit everything the assistant wrote onto a dedicated branch, then list every dependency the project declares and check that each package actually exists in its public registry.
Why: scanners only see committed files — a half-saved file in an editor is invisible to them. And a model occasionally references a package that does not exist; an attacker can register that name later and your next npm install pulls their code. That class of risk is invisible to CVE scanners because there is no CVE yet.
git switch -c sec/ai-code-review
git add -A
git commit -m "chore: freeze AI-generated code before security review"
git status --short
git diff --stat main...HEAD
Example output (your numbers will differ):
42 files changed, 3120 insertions(+), 118 deletions(-)
git status --short must print nothing — a clean tree means the scan covers exactly what is committed. If your agents create many small commits and you need a cleaner history for review, see Jujutsu or Git: What to Choose for AI Agent Workflows.
Now inventory the dependencies:
pip freeze > requirements.lock.txt
npm ls --depth=0
Check each direct dependency name against its registry:
npm view <package-name> version
pip index versions <package-name>
Example: if the assistant wrote import { parse } from "safe-json-parser-pro", run npm view safe-json-parser-pro version. A real package prints a version number. A hallucinated one prints:
npm error code E404
npm error 404 Not Found - GET https://registry.npmjs.org/safe-json-parser-pro - Not found
How to check: the commit exists (git log --oneline -1) and every package name in requirements.lock.txt and in package.json returns a version from the registry, not a 404.

What to do: run secrets, dependency, and static analysis in that order, and write each result to a machine-readable file inside reports/.
Why: order matters. A leaked key must be rotated within minutes, while a style-level SAST finding can wait. Writing reports to files gives you a diffable artifact you can attach to a pull request and compare between runs.
mkdir -p reports
gitleaks detect --source . --report-format json --report-path reports/gitleaks.json -v
pip-audit -r requirements.txt -f json -o reports/pip-audit.json
npm audit --audit-level=high
semgrep --config auto --sarif --output reports/semgrep.sarif .
trivy fs .
Why this works: Gitleaks matches entropy and known key patterns in text, pip-audit and npm audit compare your resolved versions against public CVE feeds, and Semgrep parses the code into a syntax tree so a finding like "user input reaches a SQL string" survives reformatting and renaming. SARIF is a JSON report format that code-scanning dashboards can read directly.
Example: a Semgrep finding looks like this on screen — python.lang.security.audit.formatted-sql-query, the file path, the line number, and a code snippet. A Gitleaks hit prints RuleID, File, Line, and a redacted Secret.
Wire the same commands into CI so no one has to remember them:
name: ai-code-security
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pipx install pip-audit
- run: pip-audit -r requirements.txt
- run: npm audit --audit-level=high
- run: gitleaks detect --source . --redact -v
- run: semgrep --config auto --error .
If your team already runs Claude Code with subagents, put these commands in a subagent checklist so the reviewer agent always executes the same three layers — see Claude Code for Teams: Setting Up Subagents and a Shared AI Superagent.
How to check: ls -la reports/ lists gitleaks.json and semgrep.sarif, and every command above finished without a stack trace.
What to do: read the reports, fix the blocking findings, then re-run the scanners in "fail on finding" mode so the check can be used as a merge gate.
jq 'length' reports/gitleaks.json
jq -r '.[] | "\(.File):\(.StartLine) \(.RuleID)"' reports/gitleaks.json
Example: jq 'length' printing 0 means no secrets were found anywhere in history. Any other number is a list you must work through line by line.
Fix each item in this order: rotate the leaked credential first (deleting the file does not invalidate the key), then upgrade or replace the vulnerable dependency, then patch the SAST findings. After fixing, re-run with hard failure enabled:
gitleaks detect --source . --exit-code 1 --redact
semgrep --config auto --error .
echo $?
How to check: echo $? prints 0, the CI job named ai-code-security is green on the pull request, and git log -1 --stat shows the fix commit. If the number is not 0, a finding is still open — go back to the report.
Problem. The install step fails on a package the assistant recommended
ERROR: No matching distribution found for pdf-tools-propip install --index-url https://<your-internal-index>/simple pdf-tools-pro. Re-run pip index versions <package-name> until it resolves.Problem. Gitleaks blocks the build on a test fixture
RuleID: generic-api-key reported against tests/fixtures/config.yamlcat > .gitleaks.toml <<'EOF'
[allowlist]
paths = ['''tests/fixtures/.*''']
EOF
gitleaks detect --source . --config .gitleaks.toml --exit-code 1 --redact
Problem. The CI step fails even though the local run passed
Error: Process completed with exit code 1.npm audit --audit-level=high exits with code 1 whenever a high-severity advisory exists, and CI resolves fresh transitive versions that your local node_modules does not have.rm -rf node_modules package-lock.json && npm install && npm audit --audit-level=high — then commit the updated lockfile together with the upgrade. Never use npm audit fix --force on AI-generated code without reviewing the resulting diff.You now have a repeatable gate: freeze the AI diff, verify dependency names, run secrets/SCA/SAST into reports/, and merge only when echo $? returns 0. Next, add the same job to every repository where assistants write code, and if your code cannot leave your network, route the review through a self-hosted model as described in How to connect a local LLM to Claude Code.
Can Cursor or Claude Code introduce security vulnerabilities?
Yes — the assistant writes code faster than a human reviews it, and the recurring issues are leaked credentials, invented package names, and plausible-but-unsafe logic such as string-concatenated SQL. The scanners in this guide target exactly those three categories.
Does Cursor have a built-in security scanner?
This guide does not depend on any editor feature; it uses external scanners so the result is the same regardless of which assistant wrote the code. Check the vendor's documentation for the current built-in feature set.
Is it safe to send proprietary code to a cloud AI assistant?
That depends on your contract and data policy, which this guide cannot verify for you. If policy forbids sending code to a third party, keep the scanners local and use a self-hosted model for the review step.
How often should I run these scanners?
On every pull request via CI, plus one full-history Gitleaks run whenever a new contributor or repository is onboarded, because a secret committed months ago stays in history until it is rotated.