tutorial

Monitoring Semgrep in CI/CD Pipelines with Vigilmon

Track Semgrep SAST scan health, rule performance, and CI integration metrics using Vigilmon — catch failing scans, slow rules, and pipeline regressions before they reach production.

Static analysis is only useful if it actually runs. Semgrep can catch injection vulnerabilities, insecure deserialization, and hundreds of OWASP Top 10 patterns in seconds — but when CI pipelines break, rules regress, or scan times balloon out of control, those checks silently disappear from your security posture. You only notice when a vulnerability slips through.

Most teams treat Semgrep as a set-and-forget CI step. A failed scan gets retried, the flaky pipeline gets ignored, and gradually the SAST layer becomes decorative. Monitoring changes that equation: uptime checks on the Semgrep API, alerting on scan duration spikes, and heartbeat monitors that confirm scans are actually running keep the tool honest.

This tutorial shows how to wire Vigilmon into a Semgrep-powered CI/CD pipeline to monitor scan availability, rule health, and integration continuity.

What You'll Build

  • An HTTP monitor for the Semgrep API endpoint
  • A heartbeat monitor that fires after every successful scan run
  • A webhook integration that reports scan status to Vigilmon
  • Alerts for scan failures and performance regressions

Prerequisites

  • A Vigilmon account at vigilmon.online
  • A Vigilmon API key (Settings → API Keys)
  • Semgrep CLI installed (pip install semgrep) or Semgrep Cloud access
  • A CI/CD system (GitHub Actions, GitLab CI, or similar)

Step 1: Monitor the Semgrep API Availability

If you use Semgrep Cloud or Semgrep's managed infrastructure for rule syncing, the API is a dependency. Monitor it directly.

In the Vigilmon dashboard, go to Monitors → New Monitor and create an HTTP monitor:

  • Name: Semgrep API
  • URL: https://semgrep.dev/api/check-alive
  • Interval: 5 minutes
  • Expected status: 200
  • Alert: Slack or email channel of your choice

For teams using Semgrep's registry to pull rules, also add:

  • Name: Semgrep Registry
  • URL: https://semgrep.dev/c/p/default
  • Interval: 30 minutes
  • Expected status: 200

This catches registry outages that would cause your CI jobs to fail when fetching rule sets.


Step 2: Add a Heartbeat Monitor for Scan Continuity

A heartbeat monitor is the most direct way to confirm Semgrep is actually running. Create one in Vigilmon, then ping it at the end of every scan job. If the ping doesn't arrive within the expected window, Vigilmon alerts you.

In Vigilmon, create a Heartbeat Monitor:

  • Name: Semgrep CI Scan - main branch
  • Expected interval: 24 hours (or match your scan frequency)
  • Grace period: 2 hours

Vigilmon gives you a unique heartbeat URL. Add the ping to your CI step:

# .github/workflows/semgrep.yml
name: Semgrep Scan

on:
  push:
    branches: [main]
  pull_request:

jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Semgrep
        id: semgrep
        run: |
          pip install semgrep
          semgrep scan \
            --config=auto \
            --json \
            --output=semgrep-results.json \
            .

      - name: Ping Vigilmon heartbeat
        if: success()
        run: |
          curl -s -X POST "${{ secrets.VIGILMON_HEARTBEAT_URL }}" \
            -H "Content-Type: application/json" \
            -d '{"status": "ok", "source": "semgrep-ci"}'

Store the heartbeat URL as a CI secret (VIGILMON_HEARTBEAT_URL). The if: success() condition ensures the heartbeat only fires when the scan completes without errors — not when it's skipped or the job errors out.


Step 3: Report Scan Metrics via Vigilmon Webhook

Semgrep's --json output contains everything you need to build rich metrics: rule count, finding count, scan duration, files scanned, and errors. Parse these and send them to Vigilmon's webhook endpoint so your dashboard reflects scan health over time.

#!/bin/bash
# report-semgrep-metrics.sh

RESULTS_FILE="semgrep-results.json"
VIGILMON_WEBHOOK="$1"

if [ ! -f "$RESULTS_FILE" ]; then
  echo "No results file found"
  exit 1
fi

# Extract key metrics from Semgrep JSON output
FINDINGS=$(jq '.results | length' "$RESULTS_FILE")
ERRORS=$(jq '.errors | length' "$RESULTS_FILE")
FILES_SCANNED=$(jq '.stats.files_scanned // 0' "$RESULTS_FILE")
RULES_RAN=$(jq '.stats.total_time // 0' "$RESULTS_FILE")
SCAN_DURATION=$(jq '.stats.total_time // 0' "$RESULTS_FILE")

STATUS="healthy"
if [ "$ERRORS" -gt 0 ]; then
  STATUS="degraded"
fi

curl -s -X POST "$VIGILMON_WEBHOOK" \
  -H "Content-Type: application/json" \
  -d "{
    \"status\": \"$STATUS\",
    \"message\": \"Semgrep scan complete: $FINDINGS findings, $ERRORS errors, $FILES_SCANNED files\",
    \"metadata\": {
      \"findings\": $FINDINGS,
      \"errors\": $ERRORS,
      \"files_scanned\": $FILES_SCANNED,
      \"scan_duration_s\": $SCAN_DURATION
    }
  }"

Wire this into your CI workflow after the scan step:

      - name: Report metrics to Vigilmon
        if: always()
        run: |
          chmod +x ./scripts/report-semgrep-metrics.sh
          ./scripts/report-semgrep-metrics.sh "${{ secrets.VIGILMON_WEBHOOK_URL }}"

Using if: always() ensures metrics are reported even on failed scans — which is when you most need the data.


Step 4: Monitor Rule Performance

Slow rules are a hidden CI tax. A rule that takes 30 seconds to scan a medium-sized repo can cause pipeline queues to back up across the team. Semgrep provides per-rule timing data in profiling mode.

Enable profiling in your scan:

semgrep scan \
  --config=auto \
  --json \
  --time \
  --output=semgrep-results.json \
  .

The output now includes a time block with per-rule timing. Extract the slowest rules and alert when any rule exceeds a threshold:

#!/bin/bash
# check-rule-performance.sh

MAX_RULE_DURATION_MS=5000  # Alert if any rule takes more than 5 seconds

SLOW_RULES=$(jq --argjson max "$MAX_RULE_DURATION_MS" '
  .time.rules
  | to_entries
  | map(select(.value.match_time * 1000 > $max))
  | map({rule: .key, duration_ms: (.value.match_time * 1000 | floor)})
' semgrep-results.json)

SLOW_COUNT=$(echo "$SLOW_RULES" | jq 'length')

if [ "$SLOW_COUNT" -gt 0 ]; then
  echo "WARNING: $SLOW_COUNT rules exceeded ${MAX_RULE_DURATION_MS}ms threshold"
  echo "$SLOW_RULES" | jq .
  
  # Send alert to Vigilmon
  curl -s -X POST "$VIGILMON_WEBHOOK_URL" \
    -H "Content-Type: application/json" \
    -d "{
      \"status\": \"degraded\",
      \"message\": \"$SLOW_COUNT Semgrep rules are slow (>${MAX_RULE_DURATION_MS}ms)\",
      \"metadata\": {\"slow_rules\": $SLOW_RULES}
    }"
fi

Add this check as a non-blocking CI step so slow rules generate alerts without failing the pipeline.


Step 5: Track Finding Trends with Periodic Scans

Scheduled scans of your main branch give you a baseline to detect finding count regressions — when the number of issues spikes, it often means a risky refactor landed without review, or a new code pattern bypassed existing suppressions.

# .github/workflows/semgrep-scheduled.yml
name: Semgrep Scheduled Baseline Scan

on:
  schedule:
    - cron: '0 6 * * *'  # Daily at 6am UTC

jobs:
  baseline-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: main

      - name: Run baseline scan
        run: |
          pip install semgrep
          semgrep scan \
            --config=auto \
            --json \
            --output=semgrep-baseline.json \
            .

      - name: Check finding delta and alert
        env:
          VIGILMON_API_KEY: ${{ secrets.VIGILMON_API_KEY }}
        run: |
          CURRENT=$(jq '.results | length' semgrep-baseline.json)
          
          # Compare against stored baseline (you can persist this in a cache or artifact)
          PREVIOUS=${SEMGREP_BASELINE_COUNT:-0}
          DELTA=$((CURRENT - PREVIOUS))
          
          STATUS="healthy"
          MESSAGE="Semgrep baseline: $CURRENT findings (delta: $DELTA)"
          
          if [ "$DELTA" -gt 10 ]; then
            STATUS="degraded"
            MESSAGE="Semgrep finding spike: +$DELTA new findings on main"
          fi
          
          curl -s -X POST "https://vigilmon.online/api/webhook/${{ secrets.VIGILMON_HEARTBEAT_ID }}" \
            -H "Authorization: Bearer $VIGILMON_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"status\": \"$STATUS\", \"message\": \"$MESSAGE\"}"

Step 6: Set Up Alerting Channels

Route scan alerts to the right people. In Vigilmon, go to Alerts → Channels and configure:

  • Slack: Post to your #security or #devsecops channel for findings spikes
  • Email: Notify the security lead when scan errors exceed threshold
  • PagerDuty or webhook: Escalate if scans haven't run in 48 hours (heartbeat miss)

Separate alert channels by severity:

  • Heartbeat miss → PagerDuty (scans not running is a critical gap)
  • Finding spike → Slack security channel (needs investigation, not immediate action)
  • Slow rules → Slack engineering channel (performance issue, low urgency)

Monitoring Coverage Summary

| What to monitor | Monitor type | Interval | Alert on | |---|---|---|---| | Semgrep API availability | HTTP | 5 min | Non-200 response | | Rule registry reachability | HTTP | 30 min | Non-200 response | | Scan running on schedule | Heartbeat | 24h | Missed ping | | Scan finding count trend | Webhook | Per scan | Delta > threshold | | Scan error count | Webhook | Per scan | Errors > 0 | | Slow rule detection | Script + webhook | Per scan | Rule time > 5s |


Semgrep's value depends entirely on it running consistently and correctly. A single week of broken scans can undo months of security posture improvements. Vigilmon gives you the observability layer that ensures your SAST pipeline is actually doing its job — not just appearing in a CI status badge.

Start monitoring your security pipeline free at vigilmon.online


Tags: #semgrep #sast #security #devsecops #monitoring

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →