Todas as contribuições
Engenhariavibe-codingsecuritysemgrep

Vibe coding seguro: pipeline de QA para código gerado com Cursor, Aider e Cline

Como auditar código IA antes da produção: Semgrep, Bearer, Trivy, SonarQube, DSPy e Promptfoo.

Numoru EngineeringPublicado em 20 de julho de 202614 min de leitura
Compartilhar
Proposta de implementaçãogithub.com/numoru-ia/secure-ai-codegen-template

TL;DR

"Vibe coding" — describing in natural language and letting Cursor, Aider, Cline or Claude Code write the code — is now the norm for many teams. The problem: generated code passes through less-careful human review, accumulates vulnerabilities (OWASP top ten), unchecked dependencies, and patterns a human would never write by default. This article assembles a QA pipeline that runs on every commit: Semgrep with rules specific to AI-generated code, Bearer for PII and secret detection, Trivy for container CVEs, SonarQube Community with custom rules, DSPy to auto-generate tests from the diff, Promptfoo to validate prompts before merge and Gitleaks for credentials. Pre-commit, CI and runtime. The team keeps vibing; the system stops the risk.

8-12%
AI-generated LOC with issues
OWASP top-10, secrets, missing auth
55%
Teams shipping AI code unchecked
GitHub dev survey 2024
$6-18k
Pipeline install ticket
Per engineering org onboarded
~$45
Monthly scan infra cost
Semgrep + SonarQube + Trivy (OSS)

The concrete problem

In a typical project with intensive vibe coding we observe 1,000 LOC/week generated. Of those, 8-12% have at least one of:

  • SQL injectable via string concatenation.
  • eval()/exec() over unvalidated input.
  • Secrets hardcoded "temporarily" that ship to prod.
  • New libraries without vetting (supply-chain risk).
  • Prompts with inadvertent injection (embedded instructions the model honors).
  • Silent error handling (except: pass).
  • Endpoints without auth because "we'll fix it later."

The human reviewer misses them because the PR is 400 lines and the "made with Cursor" stamp creates false implicit trust.

Pipeline architecture

  Developer → Cursor / Aider / Cline / Claude Code
       │
       ▼
  ┌──────────────────────────────────────────────┐
  │ pre-commit                                   │
  │  ├─ gitleaks (secrets)                       │
  │  ├─ semgrep (quick rules)                    │
  │  ├─ bearer (PII/sensitive data)              │
  │  └─ black/ruff/eslint                        │
  └──────────────────────────────────────────────┘
       │
       ▼
  ┌──────────────────────────────────────────────┐
  │ CI (GitHub Actions)                          │
  │  ├─ semgrep full (OWASP + AI-specific)       │
  │  ├─ sonarqube community                      │
  │  ├─ trivy fs/image                           │
  │  ├─ bearer full scan                         │
  │  ├─ dspy: generate + run tests               │
  │  ├─ promptfoo: prompt validation             │
  │  └─ license check (supply chain)             │
  └──────────────────────────────────────────────┘
       │
       ▼
  ┌──────────────────────────────────────────────┐
  │ runtime                                      │
  │  ├─ dependency-track (SBOM monitoring)       │
  │  ├─ falco / tetragon (syscall anomalies)     │
  │  └─ traces to Langfuse (if there are LLMs)   │
  └──────────────────────────────────────────────┘

Pre-commit: fast and local

.pre-commit-config.yaml:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks

  - repo: https://github.com/returntocorp/semgrep
    rev: v1.95.0
    hooks:
      - id: semgrep
        args:
          - --config=p/owasp-top-ten
          - --config=p/secrets
          - --config=./.semgrep/ai-gen-rules.yml
          - --error
          - --skip-unknown-extensions

  - repo: https://github.com/Bearer/bearer
    rev: v1.49.0
    hooks:
      - id: bearer
        args: ["scan", ".", "--severity=critical,high,medium", "--fail-on-severity=critical,high"]

  - repo: local
    hooks:
      - id: forbidden-patterns
        name: "AI gen anti-patterns"
        entry: scripts/forbidden_patterns.sh
        language: script

scripts/forbidden_patterns.sh:

#!/usr/bin/env bash
set -e
# common patterns Cursor/GPT generate that should not reach prod
grep -nR --include=*.py --include=*.ts --include=*.go \
  -E '(except:\s*pass|# FIXME|# TODO:|console\.log\("DEBUG|print\("DEBUG)' \
  -- "$@" && {
    echo "❌ anti-patterns found"
    exit 1
  } || true

Semgrep: rules specific to AI

File .semgrep/ai-gen-rules.yml:

rules:
  - id: hardcoded-api-key
    pattern-either:
      - pattern: api_key = "sk-..."
      - pattern: $KEY = "pk_live_..."
      - pattern: ANTHROPIC_API_KEY = "sk-ant-..."
    message: "Secret hardcoded. Use env vars."
    severity: ERROR
    languages: [python, javascript, typescript, go]

  - id: llm-without-timeout
    pattern-either:
      - pattern: openai.ChatCompletion.create(...)
      - pattern: anthropic.messages.create(...)
    pattern-not-inside: |
      $CTX.with_timeout(...)
    message: "LLM call without timeout; can hang the process."
    severity: WARNING
    languages: [python]

  - id: prompt-concat-user-input
    patterns:
      - pattern-either:
          - pattern: |
              $PROMPT = f"...{$USER_INPUT}..."
              ...
              $LLM.create(messages=[{"role": "system", "content": $PROMPT}, ...])
      - pattern: |
              $PROMPT = "..." + $USER_INPUT + "..."
              ...
              $LLM.create(...)
    message: "Possible prompt injection: user input concatenated to system prompt."
    severity: ERROR
    languages: [python, typescript]

  - id: sql-string-concat
    pattern-either:
      - pattern: 'cursor.execute(f"SELECT ... {$VAR} ...")'
      - pattern: 'conn.Exec("SELECT ... " + $VAR + " ...")'
    message: "SQL via concatenation. Use parameters."
    severity: ERROR
    languages: [python, go]

  - id: agent-tool-no-idempotency
    patterns:
      - pattern: |
          @tool
          def $FN(...):
              ...
      - pattern-not-inside: |
          def $FN(..., operation_id: str, ...):
              ...
    message: "Agent-side tool without operation_id; missing idempotency."
    severity: WARNING
    languages: [python]

These rules catch what classical SAST doesn't: AI-code-specific patterns (prompt injection, tools without idempotency, LLM without timeout).

SonarQube Community Edition

Generic SonarQube is complemented with specific rules. File .sonar/rules.xml applied by sonar-scanner:

  • Cyclomatic complexity >10 per function.
  • Functions with >60 lines (will be misunderstood by humans).
  • Lack of tests for functions with side effects.
  • Code duplication >3%.

For generated code, lower thresholds temporarily (e.g. complexity <8) and ratchet up — the model gets used to writing cleaner code when feedback is clear.

DSPy: tests generated from the diff

The idea: take the PR's diff, feed it to a DSPy model that produces test cases based on evident intent, and run them.

import dspy

class GenerateTestFromDiff(dspy.Signature):
    """Generate pytest tests for modified functions. Use descriptive names, cover happy path and at least 2 edge cases."""
    diff: str = dspy.InputField()
    file_path: str = dspy.InputField()
    tests: str = dspy.OutputField()

lm = dspy.LM("anthropic/claude-sonnet-4-6", api_base="https://api.numoru.com/v1", api_key=os.environ["LITELLM_MASTER_KEY"])
dspy.configure(lm=lm)

generator = dspy.ChainOfThought(GenerateTestFromDiff)

def generate_and_run(diff: str, file_path: str):
    out = generator(diff=diff, file_path=file_path)
    tests_file = write_to_tmp(out.tests)
    result = subprocess.run(["pytest", tests_file, "-v"], capture_output=True)
    return result

If the auto-generated tests fail, the PR doesn't pass. This catches regressions the human forgot to cover.

Promptfoo: validate prompts before merge

When the PR modifies a prompts/*.txt or *.md file, Promptfoo runs a suite against the new prompt and compares against the previous one. Details in agent evals in CI/CD.

Trivy: CVEs in containers and dependencies

# .github/workflows/security.yml
- name: Trivy fs scan
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: fs
    severity: CRITICAL,HIGH
    ignore-unfixed: true
    exit-code: "1"

- name: Trivy image scan
  if: github.event_name == 'pull_request'
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ghcr.io/numoru/app:${{ github.sha }}
    severity: CRITICAL,HIGH

For AI-generated code, package.json and requirements.txt tend to grow with model-suggested dependencies — CVEs appear quickly.

Supply chain: blocking new dependencies

Problem: Cursor suggests import superlib without warning. The PR adds a new package nobody vetted.

Solution: pre-commit hook that detects new lines in package.json/requirements.txt and requires justification in the commit message.

#!/usr/bin/env bash
set -e
DIFF=$(git diff --cached --unified=0 -- 'package.json' 'requirements.txt' 'go.mod')
if echo "$DIFF" | grep -q '^+[^+-]'; then
    if ! git log --format=%B -n 1 HEAD | grep -q 'new-dep:'; then
        echo "❌ new dependencies detected. Add 'new-dep: <reason>' to the commit message"
        exit 1
    fi
fi

Full GitHub Actions

name: secure-ai-gen
on: [pull_request]

jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: returntocorp/semgrep-action@v1
        with:
          config: >
            p/owasp-top-ten
            p/secrets
            p/ci
            ./.semgrep/ai-gen-rules.yml

  bearer:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: bearer/bearer-action@v2
        with:
          scan-command: "scan . --report=security --severity=critical,high"

  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: SonarSource/sonarqube-scan-action@v3
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: https://sonar.numoru.com

  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/trivy-action@master
        with: { scan-type: fs, severity: "CRITICAL,HIGH", exit-code: "1" }

  dspy-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install dspy-ai pytest
      - name: Generate + run tests from diff
        env:
          LITELLM_MASTER_KEY: ${{ secrets.LITELLM_MASTER_KEY }}
        run: python scripts/gen_tests_from_diff.py

  promptfoo:
    if: contains(github.event.pull_request.changed_files, 'prompts/')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm i -g promptfoo
      - run: promptfoo eval -c promptfoo.yml

Additional rules for Aider/Cline/Cursor

File .cursorrules (also works with Cline and Windsurf):

# Numoru rules — AI-generated code must:

1. Never hardcode API keys; use env vars via pydantic settings / viper / dotenv.
2. Every LLM call must have a timeout (<30s) and retry with backoff.
3. Prompts built with user input must use clear delimiters (<input>...).
4. Functions with external side effects must accept operation_id: str for idempotency.
5. SQL must use parameters; never f-string or concat.
6. Tests must exist for every public function. Minimum 70% coverage.
7. In Python, never except: pass. In Go, never ignore err.
8. Logs must not include plaintext PII; hash email and phone.

This biases the model during generation, not just after.

Runtime: post-deploy detection

Two tools worth their weight:

  • Falco / Tetragon — eBPF-based runtime security. Alerts when a container execs to /bin/sh, opens unexpected ports or writes to sensitive paths.
  • Dependency-Track — periodically ingests SBOMs and warns when a dependency in use today gets a recent CVE.

Minimum Falco policy for AI applications:

- macro: llm_service
  condition: (proc.name in (uvicorn, gunicorn, node, go-binary))

- rule: LLM service spawning shell
  desc: "Should never spawn a shell"
  condition: spawned_process and proc.pname in (uvicorn, gunicorn) and proc.name in (bash, sh, dash, zsh)
  output: "LLM service spawned shell (command=%proc.cmdline)"
  priority: CRITICAL
  tags: [ai, runtime]

Metrics to track

  • Critical findings per 1,000 LOC (target: <0.5 in 90 days).
  • Mean time between commit and merge (the pipeline adds ~5-8 min; ok).
  • PR rejection rate (if it crosses 30%, the pipeline is mis-tuned or the team needs training).
  • CVE-exposure window (days from CVE publication to detection in prod).
Critical findings per 1,000 LOC — before vs after the pipeline

Same team and AI tooling; only difference is whether the QA pipeline runs pre-commit + CI. Observed over 90 days across 4 Numoru client teams.

0.02.04.06.08.0Secrets in diffsSQL injectionpatternsMissing auth onendpointsPrompt injectionriskOutdated /vulnerable deps
  • Before pipeline
  • After pipeline (90 days)

Numoru engagement telemetry, 2024-2026.

Business & commercial impact

Business & commercial impact

Why buy this as a service

Teams know they need it but rarely build it on their own because each piece (Semgrep rules, SonarQube tuning, DSPy test-gen, Falco runtime) is a rabbit hole. Numoru delivers it as a 2-3 week install plus an optional hardening audit. The sale is essentially insurance priced in engineering hours against the asymmetric cost of a shipped vulnerability.

Industries and ticket ranges

Pipeline install + audit ticket by buyer (Numoru, USD)

SaaS using Cursor / Copilot heavily
Put guardrails on vibe-coding without slowing the team.
$6,000 – 14,000
One-time + $450 / mo
Fintech / healthtech
Meet regulator expectations on AI-assisted code.
$18,000 – 40,000
One-time + $900 / mo
Agencies delivering client code
Liability-safe delivery pipeline.
$8,000 – 22,000
One-time + $500 / mo
Enterprise DevSecOps teams
Extend existing security stack with AI-specific rules.
$25,000 – 60,000
One-time + $1,600 / mo
Government / defense contractors
Supply-chain attestation + dependency provenance.
$55,000 – 150,000
Per engagement

Public benchmarks

Public case studyDeveloper platform · Global · 2023-2024

GitHub — Copilot & AI productivity research

Challenge
Quantify productivity uplift and code-quality impact of AI pair-programming.
Solution
GitHub published studies on Copilot adoption, including Dohmke's blog and university collaborations on time-to-PR and perceived code quality.
Results
Devs using AI code completion
~92%
Of surveyed respondents
Perceived productivity gain
+55%
Self-reported
Code accepted on first pass
~30%
Baseline rate
Public case studyApp security · Global · 2024

Snyk — State of open source security 2024

Challenge
Benchmark vulnerability rate in AI-generated vs human-written code.
Solution
Snyk scanned thousands of repos and published the 2024 State of Open Source Security report focusing on AI-assisted codebases.
Results
Vulnerability rate (AI-assisted)
+43%
Vs purely human-written baselines
Missing auth patterns
2.3× more common
In AI-generated endpoints
Hardcoded secrets
+28%
Year over year

Illustrative case — 40-engineer SaaS adopting pipeline

Illustrative caseFintech · 40 eng · $16M ARR · Mexico + Chile

Growth-stage LATAM fintech with 40 engineers heavy users of Cursor + Claude Code

Baseline
3 near-miss incidents in 6 months (exposed .env in PR, leaked client data in logs, SSRF in internal admin tool). Ad-hoc review. Pre-deploy SonarQube but no AI-specific rules.
Intervention
3-week Numoru install: Semgrep + Bearer + Trivy + SonarQube + DSPy + Promptfoo + Gitleaks + Falco runtime. Monthly 1-hour team training. Rule tuning retainer.
Projected outcome (12 mo)
Critical findings / 1k LOC
2.4 → 0.3
-88%
Near-miss incidents
3 / 6 mo → 0
Across 6 mo post-install
PR rejection rate
12% → 22%
Healthy, mostly dep upgrades
Mean CI time added
+6 min
Acceptable
Install + retainer cost
$24,500
One-time + $900 / mo × 12
Breach cost avoided
$450K+
Median fintech breach impact (IBM 2024)
Breach-cost avoidance anchored to IBM Cost of a Data Breach 2024 ($4.88M global average; fintech ~$5.9M). Incident deltas from Numoru engagement data. Synthetic case.

ROI calculator — securing an AI-heavy engineering org

40-engineer fintech adopting pipeline (12 months)

Payback: 3 months
Assumptions
LOC / week AI-generated4,500
Pre-pipeline critical issue rate2.4 / 1k LOC
Post-pipeline issue rate0.3 / 1k LOC
Probability of incident in 12 mo40% → 6%
Expected incident cost (fintech)$450,000
Install cost$18,500
Retainer$900 / mo
Eng time for CI increment6 min / PR × 120 PRs / mo
Install (one-time)−$18,500
Retainer (12 mo × $900)−$10,800
Infra (Sonar + Semgrep Pro trial)−$720
Additional CI time (144 h × $92)−$13,248
Probability-weighted incident avoided+$153,000
Compliance readiness value+$35,000
Net year-1 contribution+$144,732

Pricing tiers Numoru sells

Starter
$6,500one-time
2 weeks. Pre-commit + basic CI.
  • Semgrep + Gitleaks + Trivy
  • Basic SonarQube Community setup
  • Pre-commit hook + GitHub Actions
  • Short team walkthrough
  • 30-day warranty
Full pipeline
$14,500one-time + $650 / mo
3-4 weeks. Full stack + runtime.
  • Everything in Starter
  • DSPy test-gen + Promptfoo
  • Bearer (PII) + supply-chain
  • Falco runtime rules
  • Rule tuning retainer
  • Quarterly audit
DevSecOps enterprise
$45,000+one-time + $1,600 / mo
6-10 weeks. Multi-repo + compliance.
  • Multi-repo / monorepo scope
  • SOC 2 / ISO 27001 alignment
  • Custom Semgrep rules catalog
  • Airflow + Notify.io integration
  • Dedicated hardening engineer
  • Annual red-team engagement

Human anti-patterns

  1. Disabling rules "temporarily". It stays. Better: a PR fixing the false positive in the rules.
  2. Always accepting --skip-unfixed. Makes the scan cosmetic.
  3. Not training the team. If the dev doesn't understand why SonarQube fails, they turn off the check. Short monthly training.
  4. Only running in CI. Pre-commit saves CI cycles and gives instant feedback.
  5. Trusting that "the model will learn". It doesn't learn between PRs; rules must be explicit.

FAQ

Does it become unbearably slow?Pre-commit stays under 20s with well-filtered rules. Full CI 5-12 min. Better than a prod incident.

Does it work for small teams?Yes. Minimum: gitleaks + semgrep + trivy + pre-commit. ~30 min of setup.

Does it detect runtime prompt injection?Partially. SAST catches dangerous concat; runtime guardrails (NeMo, Rebuff) catch what slips. They are complementary.

What do I do when a PR has 40 Semgrep findings?Prioritize by severity: ERROR blocks, WARNING stays as tagged tech debt. The first week generates noise; it calibrates to 2-5 findings per PR.

Is Sonar Community sufficient, or do I need Enterprise? Community is enough for teams <15 people. Enterprise adds decent branch analysis and cross-repo, useful in monorepo.

Next steps

Complete pipeline published at github.com/numoru-ia/secure-ai-codegen-template. Includes pre-commit, GitHub Actions, .cursorrules, custom Semgrep rules and Falco policies. Fork + make bootstrap. Next piece in the series: how to wire these metrics to the Langfuse + Grafana dashboard for full code-to-runtime visibility.

Quer resultados assim para sua empresa?

Iniciar conversa
Compartilhar