Skip to main content
surfbot.

CI/CD Integration

Trigger Surfbot scans from your CI/CD pipeline and gate deployments on security results.

Overview

Integrate Surfbot into your deployment pipeline to automatically scan after every deploy. This catches new vulnerabilities introduced by infrastructure changes before they're exploited.

GitHub Actions

.github/workflows/surfbot-scan.yml
name: Post-Deploy Security Scan
 
on:
  workflow_run:
    workflows: ["Deploy"]
    types: [completed]
 
jobs:
  scan:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - name: Trigger Surfbot Scan
        run: |
          RESPONSE=$(curl -s -X POST \
            "https://api.surfbot.io/v1/domains/${{ secrets.SURFBOT_DOMAIN_ID }}/scans" \
            -H "Authorization: Bearer ${{ secrets.SURFBOT_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"scan_type": "quick"}')
          
          SCAN_ID=$(echo $RESPONSE | jq -r '.data.id')
          echo "scan_id=$SCAN_ID" >> $GITHUB_OUTPUT
          echo "Triggered scan: $SCAN_ID"
 
      - name: Wait for Scan
        run: |
          for i in $(seq 1 60); do
            STATUS=$(curl -s \
              "https://api.surfbot.io/v1/scans/${{ steps.scan.outputs.scan_id }}" \
              -H "Authorization: Bearer ${{ secrets.SURFBOT_API_KEY }}" \
              | jq -r '.data.status')
            
            if [ "$STATUS" = "completed" ]; then
              echo "Scan completed"
              break
            elif [ "$STATUS" = "failed" ]; then
              echo "Scan failed"
              exit 1
            fi
            
            echo "Scan status: $STATUS (attempt $i/60)"
            sleep 15
          done
 
      - name: Check Results
        run: |
          DIFF=$(curl -s \
            "https://api.surfbot.io/v1/scans/${{ steps.scan.outputs.scan_id }}/diff" \
            -H "Authorization: Bearer ${{ secrets.SURFBOT_API_KEY }}")
          
          NEW_CRITICAL=$(echo $DIFF | jq '[.data.changes[] | select(.severity == "critical" and .change == "new")] | length')
          NEW_HIGH=$(echo $DIFF | jq '[.data.changes[] | select(.severity == "high" and .change == "new")] | length')
          
          echo "New critical findings: $NEW_CRITICAL"
          echo "New high findings: $NEW_HIGH"
          
          if [ "$NEW_CRITICAL" -gt 0 ]; then
            echo "::error::New critical vulnerabilities found after deploy!"
            exit 1
          fi

GitLab CI

.gitlab-ci.yml
surfbot_scan:
  stage: post-deploy
  image: curlimages/curl:latest
  script:
    - |
      RESPONSE=$(curl -s -X POST \
        "https://api.surfbot.io/v1/domains/${SURFBOT_DOMAIN_ID}/scans" \
        -H "Authorization: Bearer ${SURFBOT_API_KEY}" \
        -H "Content-Type: application/json" \
        -d '{"scan_type": "quick"}')
      echo "Scan triggered: $(echo $RESPONSE | jq -r '.data.id')"
  only:
    - main

Generic Script

For any CI system, use this shell script:

scripts/surfbot-scan.sh
#!/bin/bash
set -e
 
API_KEY="${SURFBOT_API_KEY}"
DOMAIN_ID="${SURFBOT_DOMAIN_ID}"
BASE_URL="https://api.surfbot.io/v1"
 
# Trigger scan
echo "Triggering Surfbot scan..."
SCAN=$(curl -sf -X POST "${BASE_URL}/domains/${DOMAIN_ID}/scans" \
  -H "Authorization: Bearer ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"scan_type": "quick"}')
 
SCAN_ID=$(echo "$SCAN" | jq -r '.data.id')
echo "Scan started: ${SCAN_ID}"
 
# Poll until complete (timeout: 15 min)
TIMEOUT=900
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
  STATUS=$(curl -sf "${BASE_URL}/scans/${SCAN_ID}" \
    -H "Authorization: Bearer ${API_KEY}" \
    | jq -r '.data.status')
  
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && echo "Scan failed" && exit 1
  
  sleep 10
  ELAPSED=$((ELAPSED + 10))
done
 
echo "Scan complete. Checking for new critical findings..."
 
# Check for new critical vulns
CRITICAL=$(curl -sf "${BASE_URL}/scans/${SCAN_ID}/diff" \
  -H "Authorization: Bearer ${API_KEY}" \
  | jq '[.data.changes[] | select(.severity == "critical" and .change == "new")] | length')
 
if [ "$CRITICAL" -gt 0 ]; then
  echo "⚠️  ${CRITICAL} new critical vulnerabilities found!"
  exit 1
fi
 
echo "✅ No new critical vulnerabilities"

Best Practices

  • Use quick scans in CI (faster, focuses on web-layer changes)
  • Run full scans on a daily schedule separately
  • Only gate on new critical findings — don't block deploys on pre-existing issues
  • Store your API key as a CI/CD secret, never in code
  • Set a reasonable timeout (15 minutes) to avoid blocking pipelines indefinitely

On this page