By , Senior Full-Stack Engineer14 min read

AI tools like Claude Code and Cursor let us ship features faster than ever. The flip side: more code flowing faster means more potential vulnerabilities shipping faster.

Semgrep calls this the "vibe coding" problem. We're generating code at a pace where the old workflow of "commit, wait for CI scan, fix issues" doesn't cut it anymore. Security scanning needs to happen while you're still in the editor, not after you've moved on to the next thing.

I run Semgrep at three points: real-time via their MCP server, automatically during preflight checks, and in CI through GitHub Actions. Here's how to set it up.

The Security Gap in AI-Assisted Development

When Claude Code generates a function, it's optimizing for "does this work?" not "is this secure?" LLMs pattern-match against training data that includes plenty of insecure code examples, and they'll happily reproduce those patterns if they solve the immediate problem.

Common issues AI-generated code can introduce:

  • SQL injection: String concatenation in database queries instead of parameterized statements
  • Hardcoded secrets: API keys and credentials placed directly in source files
  • Insecure deserialization: Parsing untrusted input without validation
  • Path traversal: File operations without proper input sanitization
  • Debug mode in production: Development flags left enabled in generated configs

If AI is generating code in real-time, security analysis should happen in real-time too. Waiting until CI to find out you have a SQL injection vulnerability is too late.

Three Layers of Security Scanning

I don't rely on a single checkpoint. I run Semgrep at three stages:

  1. Real-time: Semgrep MCP server scans code as the AI generates it
  2. Pre-commit: /workflow-preflight runs Semgrep before any commit
  3. CI: GitHub Actions blocks PRs with security findings

Most issues get caught at stage 1 or 2. CI is the safety net that rarely fires because the earlier layers caught everything. But when it does fire, I'm glad it's there.

Layer 1: Semgrep MCP Server for Real-Time Scanning

Semgrep released an open-source MCP server that plugs directly into Claude Code and Cursor. The AI can invoke Semgrep during code generation, catch a vulnerability, fix it, and re-scan to confirm the fix, all without you doing anything.

What the MCP Server Provides

The MCP server exposes these tools to the LLM:

  • security_check: Scans code against Semgrep's rule registry
  • semgrep_scan: Analyzes specific files with configurable rules
  • semgrep_scan_with_custom_rule: Applies custom patterns you define
  • get_abstract_syntax_tree: Returns AST for deeper semantic analysis
  • semgrep_findings: Pulls issues from Semgrep's AppSec Platform (if authenticated)

The LLM can check its own output, spot a problem, fix it, and verify the fix passed. You get secure code without lifting a finger.

Setting Up Semgrep MCP in Claude Code

# macOS
brew install semgrep

# Windows/Linux (via pip)
pip install semgrep

# Windows (via pipx for isolated install)
pipx install semgrep

# Verify install. MCP support landed in 1.146.0; check the
# Semgrep release notes for the current minimum if you hit issues.
semgrep --version

# Authenticate for Pro rules (optional but recommended)
semgrep login && semgrep install-semgrep-pro

Once Semgrep is installed and in your PATH, add the MCP server to Claude Code:

# Launch Claude Code
claude

# Add the Semgrep marketplace
/plugin marketplace add semgrep/mcp-marketplace

# Install the plugin
/plugin install semgrep-plugin@semgrep

# Run setup
/semgrep-plugin:setup_semgrep_plugin

# Enable if needed
/plugin enable semgrep-plugin@semgrep

Setting Up Semgrep MCP in Cursor

For Cursor, the setup involves configuring the MCP server in settings and adding hooks for automatic scanning:

// .cursor/mcp.json
{
  "mcpServers": {
    "semgrep": {
      "command": "semgrep",
      "args": ["mcp"]
    }
  }
}

Add hooks for automatic scanning after file edits:

// .cursor/hooks.json
{
  "version": 1,
  "hooks": {
    "stop": [
      {
        "command": "semgrep mcp -k stop-cli-scan -a cursor"
      }
    ],
    "afterFileEdit": [
      {
        "command": "semgrep mcp -k record-file-edit -a cursor"
      }
    ]
  }
}

Configuring LLM Behavior for Security Scanning

Installing the MCP server isn't enough. You need to tell the LLM to use it. Without explicit instructions, it won't scan consistently.

For Claude Code, add this to your project's CLAUDE.md or global ~/.claude/CLAUDE.md:

## Security Requirements

Security is a core requirement for all code in this project. After generating or modifying any code that handles user input, authentication, data access, or external APIs, invoke Semgrep to scan for vulnerabilities. If issues are found, fix them immediately and re-scan to verify remediation before considering the task complete.

For Cursor, add equivalent instructions to your User Rules in Settings > Rules:

Security is a core requirement. After generating or modifying code that handles user input, authentication, data access, or external APIs, invoke Semgrep to scan for vulnerabilities. Fix any issues found and re-scan to verify remediation.

Layer 2: Automatic Preflight Checks with ai-workflow

Real-time scanning catches most issues, but I want a checkpoint before anything gets committed. I added automatic Semgrep detection to /workflow-preflight in v1.9.1 of my ai-workflow plugin (release notes here). The plugin has shipped further releases since; the detection logic is the same but the surrounding feature set has grown.

How Semgrep Detection Works

The preflight command detects Semgrep through:

  • Config files: .semgreprc.yml, .semgrep.yml, .semgrep/ directories
  • GitHub Actions: Parses --config flags from existing CI workflows
  • Docs: Finds security commands referenced in project documentation
  • Docker fallback: Runs via Docker when the CLI isn't installed locally

If you already have Semgrep in CI, /workflow-preflight uses those same rules locally. No extra configuration needed.

The Preflight Sequence

Preflight checks run in this order:

  1. Formatting: Prettier, ESLint fix, rustfmt, gofmt
  2. Type checking: TypeScript, mypy, dotnet build
  3. Linting: ESLint, Ruff, golangci-lint, Clippy
  4. Security scanning: Semgrep SAST, dependency audits (npm/pnpm/yarn audit, pip-audit, cargo audit)
  5. Testing: Jest, Vitest, pytest, go test, cargo test
# Run preflight with security scanning
/workflow-preflight --fix

# Output:
# [Formatting] Running prettier... PASS
# [TypeCheck] Running tsc --noEmit... PASS
# [Linting] Running eslint... FIXED (2 issues)
# [Security] Running semgrep... PASS (0 findings)
# [Security] Running pnpm audit... PASS (0 vulnerabilities)
# [Testing] Running vitest run... PASS (127 tests)
#
# Preflight Status: READY TO COMMIT

Additional Security Detections

Preflight also detects and runs:

  • eslint-plugin-security: Detected from package.json devDependencies
  • Dependency audits: pnpm/npm/yarn audit for Node.js, pip-audit for Python, cargo audit for Rust
  • Windows handling: Docker commands get MSYS_NO_PATHCONV=1 to prevent Git Bash path conversion errors

Layer 3: GitHub Actions CI Enforcement

The final layer catches anything that slipped past local scanning. Here's the workflow I use:

# .github/workflows/security.yml
name: Security Scanning

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  semgrep:
    name: Semgrep SAST
    runs-on: ubuntu-latest
    container:
      image: semgrep/semgrep
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run Semgrep
        run: semgrep ci
        env:
          SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}

  # Add additional jobs as needed:
  # - dependency-audit (npm audit, pip-audit, etc.)
  # - secret-scanning
  # - license-compliance

If you use Semgrep's cloud platform, semgrep ci uploads findings for tracking. For open-source repos, use semgrep scan --config auto instead.

Blocking PRs on Security Findings

Set up branch protection to require the security check:

  1. Settings > Branches > Branch protection rules
  2. Add rule for main
  3. Enable "Require status checks to pass before merging"
  4. Select "Semgrep SAST"

PRs with security findings get blocked until you fix them.

Real-World Application: AccessHawk

I use this setup on AccessHawk, my accessibility scanner SaaS. It handles user auth, subscription billing, and scan data. Security problems would be bad.

Having Semgrep at every layer means issues get caught during development instead of code review or production. I ship faster because I'm not worried about what I might have missed.

The Workflow in Practice

A typical feature implementation:

  1. /workflow-plan-phases to create the plan
  2. /workflow-implement-phases with Semgrep MCP catching issues in real-time
  3. /workflow-preflight before committing
  4. Push to GitHub, CI runs security workflow
  5. PR blocked if findings exist

Most issues get caught at step 2 or 3. CI rarely finds anything new because the earlier layers already caught it.

Why This Matters

AI tools are getting better. Context windows are expanding. We're writing more code than ever. That's good. But "security review before release" doesn't work when you can implement features in minutes.

Security needs to run continuously, not as a gate at the end. The MCP server lets the LLM check its own work. Combined with preflight and CI, you get coverage without slowing down.

Set This Up

I run Semgrep on every project now, even without AI involvement. But if you're using Claude Code or Cursor, integrated security scanning isn't optional. You're writing code faster than ever. Your security tooling needs to keep up.

Real-time MCP scanning catches issues as code is generated. Preflight verifies nothing slipped through before commits. CI blocks the main branch from anything that got past both. The layers reinforce each other.

The setup is straightforward: install the MCP server, add the ai-workflow plugin for preflight, drop in the GitHub Actions workflow. You can have all three layers running in 15 minutes.

Contact

Drop me a line. I read everything and reply within a day.

Required fields are marked “(required)”.