AI / ML ·

Securing AI-Generated Code in Production: Static Analysis, Vulnerability Scanning, and Governance Pipelines for Vibe-Coded Applications

40-62% of AI-generated code contains security vulnerabilities. This guide covers building a CI/CD security pipeline with Semgrep, secret detection, dependency scanning, runtime monitoring, and a practical governance framework for teams using Copilot, Cursor, or Claude Code.

Securing AI-Generated Code in Production: Static Analysis, Vulnerability Scanning, and Governance Pipelines for Vibe-Coded Applications

The numbers are not a warning anymore

Veracode’s 2025 research found that between 40% and 62% of AI-generated code contains at least one security vulnerability. That range is wide because the number depends on context: prompt quality, model, developer experience with review. But even the low end of that range is alarming for any team shipping code to production.

In March 2026, Escape.tech published the results of scanning 5,600 applications built with AI coding tools. They found over 2,000 vulnerabilities, more than 400 exposed secrets, and 175 instances of personally identifiable information leaking through API endpoints. Georgia Tech’s Vibe Security Radar tracked 35 new CVEs in March 2026 that were directly attributable to AI-generated code. That is a 6x increase from the 6 CVEs tracked in January 2026.

The incidents have names now. Moltbook exposed 1.5 million API keys because AI-generated Supabase code was missing Row Level Security. CVE-2025-48757 affected 170 production Lovable-built applications with inverted access control logic. A Replit AI agent wiped a production database during an explicit code freeze.

This article is not about whether you should use AI coding tools. 92% of US developers are already using them daily. The question is what security infrastructure your team needs to run on top of them.


Vulnerability patterns specific to AI-generated code

AI models generate plausible-looking code that satisfies the immediate requirement. They are not optimized for the security properties that are invisible to the immediate requirement: authentication state, data ownership, secret management, input sanitization.

The four patterns that show up repeatedly in audits:

Missing authorization checks. The model generates a working CRUD endpoint. The check for “does this user own this resource” is absent because it was not in the prompt. The endpoint functions correctly in testing, where every request is sent by an authenticated user with the right data. It fails in production when a user modifies the ID parameter.

SQL injection and ORM misuse. AI models default to string interpolation in examples. They also generate raw query fallbacks when the ORM pattern is not obvious from context. Both introduce injection surfaces.

Hardcoded secrets. Models generate working connection strings, API keys, and credentials directly in code because that is what the training data contains. They frequently go into the repository without a .gitignore entry.

Inverted access control. The model generates the check backwards. Instead of if (!hasPermission) return 403, it generates if (hasPermission) return 403. This pattern passes unit tests if the test only checks the success path. CVE-2025-48757 is this exact bug at scale.

The underlying cause of all four is the same: the model produces code that satisfies functional requirements. Security requirements are rarely explicit in the prompt.


Building the CI/CD security pipeline

The pipeline has four layers. Each layer catches a different class of problem and has a different false positive profile.

Layer 1: Secret detection at commit time

Secret detection needs to run before code reaches the repository, not after. Pre-commit hooks with detect-secrets or gitleaks stop the most common failure mode before it becomes a git history problem.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args:
          - --baseline
          - .secrets.baseline
          - --exclude-files
          - package-lock.json
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

The baseline file (detect-secrets scan > .secrets.baseline) lets you acknowledge false positives without suppressing future real findings. Maintain it in version control. Review the diff when it changes in a PR.

In CI, run secret scanning again as a gate. Pre-commit hooks are bypassed with --no-verify. The CI gate is the enforceable check.

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

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

jobs:
  secrets:
    name: Secret Detection
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Run Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Layer 2: Static analysis with Semgrep

Semgrep runs pattern-based analysis against the AST. It catches the vulnerability patterns described above faster and with lower false positive rates than generic linters because you write rules that target your specific stack.

# .github/workflows/security.yml (continued)
  sast:
    name: Static Analysis
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Semgrep Scan
        uses: semgrep/semgrep-action@v1
        with:
          config: >-
            p/typescript
            p/nodejs
            p/owasp-top-ten
            p/jwt
            p/sql-injection
          auditOn: push
        env:
          SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}

The rulesets above cover most of the AI-generated code failure patterns. The p/jwt ruleset catches algorithms set to none, missing verification, and secret fallbacks. The p/sql-injection ruleset catches string interpolation in query builders.

Write custom rules for your specific patterns. If your codebase uses a particular ORM or auth library, a custom rule that flags the misuse pattern you have already seen is worth the fifteen minutes it takes to write.

# semgrep-rules/missing-auth-check.yaml
rules:
  - id: express-route-missing-auth-middleware
    patterns:
      - pattern: |
          $APP.$METHOD($PATH, $HANDLER)
      - pattern-not: |
          $APP.$METHOD($PATH, ..., requireAuth, ..., $HANDLER)
      - pattern-not: |
          $APP.$METHOD($PATH, ..., authenticate, ..., $HANDLER)
      - metavariable-regex:
          metavariable: $METHOD
          regex: ^(get|post|put|patch|delete)$
      - metavariable-regex:
          metavariable: $PATH
          regex: ^['"]\/api\/
    message: >
      API route $PATH is missing an auth middleware. AI-generated routes
      frequently omit authorization. Verify this is intentional.
    severity: WARNING
    languages: [javascript, typescript]
    metadata:
      category: security
      cwe: "CWE-862: Missing Authorization"

The rule above will produce false positives for intentionally public endpoints. That is acceptable. The goal is to require an explicit decision, not to automate the decision itself.

Layer 3: Dependency vulnerability scanning

AI models suggest packages they have seen frequently in training data. They do not check whether those packages have known CVEs at the version pinned. They also generate npm install x without checking whether a maintained alternative exists.

# .github/workflows/security.yml (continued)
  dependencies:
    name: Dependency Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - name: Install dependencies
        run: npm ci
      - name: npm audit
        run: npm audit --audit-level=high
      - name: Snyk vulnerability scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high --fail-on=all

Set --audit-level=high initially. Starting at critical is tempting but it misses the class of vulnerabilities that AI code introduces via package churn. Once you have addressed the backlog, tighten to moderate.

Layer 4: A complete pipeline configuration

# .github/workflows/security.yml (full)
name: Security Pipeline

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

jobs:
  secrets:
    name: Secret Detection
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  sast:
    name: Static Analysis
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: semgrep/semgrep-action@v1
        with:
          config: >-
            p/typescript
            p/nodejs
            p/owasp-top-ten
            p/jwt
            p/sql-injection
        env:
          SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}

  dependencies:
    name: Dependency Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci
      - run: npm audit --audit-level=high
      - uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high

  security-gate:
    name: Security Gate
    needs: [secrets, sast, dependencies]
    runs-on: ubuntu-latest
    steps:
      - name: All security checks passed
        run: echo "Security pipeline passed"

Runtime security monitoring

Static analysis catches what it can see in the source. It does not catch SSRF triggered by user-supplied URLs, authentication bypass via unexpected request shapes, or data exfiltration through AI-generated logging statements that include more than intended.

The minimum viable runtime layer:

Structured logging with PII tagging. AI-generated log statements frequently include entire request objects. Implement a logging wrapper that redacts fields by pattern before write.

// lib/logger.ts
const PII_FIELDS = new Set([
  "password",
  "token",
  "secret",
  "apiKey",
  "api_key",
  "authorization",
  "ssn",
  "creditCard",
  "credit_card",
]);

function redactObject(obj: unknown, depth = 0): unknown {
  if (depth > 5) return "[truncated]";
  if (obj === null || typeof obj !== "object") return obj;
  if (Array.isArray(obj)) return obj.map((item) => redactObject(item, depth + 1));

  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
    if (PII_FIELDS.has(key.toLowerCase())) {
      result[key] = "[redacted]";
    } else {
      result[key] = redactObject(value, depth + 1);
    }
  }
  return result;
}

export function log(level: "info" | "warn" | "error", message: string, context?: unknown) {
  const entry = {
    timestamp: new Date().toISOString(),
    level,
    message,
    context: context ? redactObject(context) : undefined,
  };
  console.log(JSON.stringify(entry));
}

Anomaly alerting on authorization failures. A spike in 403 responses often means an access control check is functioning but being probed, or that an inverted check is intermittently triggering. Both are worth an alert.

Rate limiting on all AI-generated endpoints. AI models do not add rate limiting unless asked. Add it as middleware at the route level, not as an afterthought.


Code review strategies for AI-generated code

Reviewing AI-generated code requires a different mental model than reviewing code written by a developer. When a developer writes code, the reviewer is checking their reasoning. When AI writes code, the reviewer needs to supply the reasoning the model did not have.

The questions to ask on every AI-generated PR:

  1. Does every state-mutating endpoint verify that the authenticated user owns the resource being mutated?
  2. Are there any direct database queries that interpolate user input?
  3. Are there any new environment variable references? Are those variables in the secret manager, not in .env committed to the repository?
  4. Does the error handler expose stack traces or internal state in the response body?
  5. Are there any new dependencies? Why were they chosen over existing utilities?

A practical rule: require PR authors to annotate which portions of a PR were AI-generated. This is not about penalizing AI use. It tells reviewers where to concentrate attention.

// Example: the kind of PR comment that adds value
// AI-generated this handler. Verified: ownership check on line 23,
// no string interpolation in the query on line 31, rate limit
// middleware added on line 8. Not verified: the pagination logic
// on lines 40-55 needs a second look for off-by-one.

The annotation does not need to be formal. It needs to make the reviewer’s job explicit.


Governance framework for teams using AI coding tools

A governance framework answers three questions: what is allowed, how is it verified, and what happens when something slips through.

Tradeoffs: security approaches for AI-generated code

ApproachThreat coverageMaintenance costFalse positive riskWhen to use
Pre-commit hooks onlyLow (bypassed easily)LowLowNever sufficient alone
CI SAST gateMedium (syntax-visible bugs)MediumMediumRequired baseline
Custom Semgrep rulesHigh (stack-specific patterns)HighLow when tunedAfter first audit
Manual AI-code annotationHigh (context the model lacks)HighNoneAll PRs with AI content
Runtime anomaly detectionHigh (behavioral, not static)MediumMediumPost-baseline
Dependency scanningMedium (known CVEs only)LowLowRequired baseline

The policy document

A governance policy does not need to be long. It needs to be specific enough that a new engineer can follow it without ambiguity.

# AI Code Governance Policy

## What requires review
Any code generated by Copilot, Cursor, Claude Code, or any AI assistant
must pass the automated security pipeline AND receive explicit human review
before merging to main.

## What the automated pipeline checks
- Secrets in code and git history (gitleaks)
- OWASP Top 10 patterns (Semgrep)
- Known CVEs in dependencies (npm audit + Snyk)

## What the automated pipeline does not check
- Authorization logic correctness
- Business rule enforcement
- Context-dependent access control

These require human review. See the PR checklist.

## PR checklist for AI-generated code
- [ ] Annotate which sections were AI-generated
- [ ] Verify resource ownership checks on all state-mutating endpoints
- [ ] Verify no raw query string interpolation
- [ ] Verify no secrets in code (pipeline catches most; human check for logic)
- [ ] Verify error responses do not expose internal state

## When the pipeline fails
Fix the finding before merging. Do not bypass with `--no-verify` or
workflow file edits without a documented exception approved by a senior engineer.

## Exception process
Document the exception in the PR description with: (1) what was found,
(2) why it is not a risk in this specific context, (3) who approved the exception.

Tracking over time

The pipeline produces signal you should measure. Track per sprint:

  • Number of secrets caught pre-merge vs post-merge (post-merge means the pre-commit hook was bypassed)
  • Number of SAST findings by category (a spike in a category often correlates with a specific AI tool or workflow change)
  • Number of CVEs introduced via new dependencies
  • Number of manual review findings not caught by automation (these are candidates for new Semgrep rules)

The ratio of automated catches to manual catches tells you whether your rules are keeping up with the code patterns your team is generating.


Production notes

Four things that cause governance pipelines to fail in practice, not in principle:

Rule fatigue. A pipeline that produces 50 findings per PR trains engineers to ignore it. Start with high-severity rules only. Add rules incrementally as the team builds trust in the signal.

No exception path. If bypassing the pipeline requires heroic effort, engineers will find creative ways around it. A documented exception process with human approval is better than a policy that gets ignored.

Governance without context. Semgrep flags a missing auth middleware on a route that serves a public health check. The engineer dismisses it, and the habit of dismissing findings grows. Custom rules scoped to the patterns that actually matter in your codebase have lower noise floors than generic rulesets.

Treating the pipeline as a destination. The pipeline is infrastructure. It needs maintenance. Model upgrades change the code patterns AI tools generate. A Semgrep rule that caught everything in 2025 may miss a new pattern in 2026 if no one reviews the miss log.


AI coding tools are not going away. The 40-62% vulnerability rate in AI-generated code is not a reason to stop using them. It is a reason to treat AI-generated code as you would treat code from any source you do not fully control: with automated checks, explicit review requirements, and a governance process that can adapt as the patterns change. The infrastructure described here is not exotic. It is the same security pipeline any production team should have. The difference is that without it, AI tooling scales the vulnerability surface faster than manual review can track.

More in AI / ML

How Mixture of Experts Works: Sparse Gating, Expert Routing, and the Architecture Behind Efficient Large Language Models
AI / ML ·

How Mixture of Experts Works: Sparse Gating, Expert Routing, and the Architecture Behind Efficient Large Language Models

A deep dive into MoE architecture: how the gating network routes tokens to experts, top-k selection, load balancing losses, capacity factor, token dropping, expert parallelism for serving, and the real production tradeoffs between dense transformers and sparse MoE models.

AI Agent Frameworks Compared: CrewAI, LangGraph, AutoGen, and Mastra for Production Systems
AI / ML ·

AI Agent Frameworks Compared: CrewAI, LangGraph, AutoGen, and Mastra for Production Systems

A practical comparison of CrewAI, LangGraph, AutoGen, and Mastra for building production AI agent systems. Covers architecture philosophy, state management, tool integration, observability, and deployment patterns with TypeScript code examples.

Google's Agent2Agent Protocol: How A2A Enables Cross-Framework Agent Communication in Production Systems
AI / ML ·

Google's Agent2Agent Protocol: How A2A Enables Cross-Framework Agent Communication in Production Systems

A deep dive into Google's Agent2Agent (A2A) protocol covering agent cards, task lifecycle, message parts, streaming via SSE, push notifications, and how A2A complements MCP. Includes TypeScript implementation examples, comparison with MCP and direct API integration, and production deployment patterns for multi-vendor agent ecosystems.

How Transformer Models Work: Self-Attention, Positional Encoding, and the Architecture Behind Modern LLMs
AI / ML ·

How Transformer Models Work: Self-Attention, Positional Encoding, and the Architecture Behind Modern LLMs

A technical deep dive into the Transformer architecture: tokenization, positional encoding, self-attention with Q/K/V matrices, multi-head attention, the encoder-decoder split, training dynamics, and what it all means for engineers building on top of LLMs.