CI/CD Integration

Integrate TurboPentest into your CI/CD pipeline to automatically run a pentest on every deployment (or on a schedule). This is the highest-value use case - continuous security coverage with zero manual effort.

Prerequisites

  • An API key (create one in Dashboard > API Keys)
  • Your domain verified in TurboPentest
  • Pentest credits available (use a subscription for continuous coverage)

GitHub Actions

.github/workflows/pentest.yml
name: Security Pentest
on:
  push:
    branches: [main]

jobs:
  pentest:
    runs-on: ubuntu-latest
    steps:
      - name: Start TurboPentest
        run: |
          RESPONSE=$(curl -sf -X POST https://turbopentest.com/api/pentests \
            -H "X-API-Key: ${{ secrets.TURBOPENTEST_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"targetUrl": "${{ vars.TARGET_URL }}"}')
          SCAN_ID=$(echo $RESPONSE | jq -r '.id')
          echo "SCAN_ID=$SCAN_ID" >> $GITHUB_ENV
          echo "Pentest started: $SCAN_ID"

      - name: Wait for results
        run: |
          for i in $(seq 1 120); do
            STATUS=$(curl -sf -H "X-API-Key: ${{ secrets.TURBOPENTEST_API_KEY }}" \
              https://turbopentest.com/api/pentests/$SCAN_ID | jq -r '.status')
            if [ "$STATUS" = "complete" ]; then
              echo "Pentest complete"
              break
            fi
            if [ "$STATUS" = "failed" ]; then
              echo "Pentest failed"
              exit 1
            fi
            echo "Status: $STATUS - waiting 60s..."
            sleep 60
          done

      - name: Check for critical findings
        run: |
          CRITICALS=$(curl -sf -H "X-API-Key: ${{ secrets.TURBOPENTEST_API_KEY }}" \
            https://turbopentest.com/api/pentests/$SCAN_ID | \
            jq '[.findings[] | select(.severity == "critical")] | length')
          if [ "$CRITICALS" -gt 0 ]; then
            echo "Found $CRITICALS critical findings - failing build"
            exit 1
          fi
          echo "No critical findings"

Setup: Add TURBOPENTEST_API_KEY as a repository secret and TARGET_URL as a repository variable.

GitLab CI/CD

.gitlab-ci.yml
pentest:
  stage: test
  image: alpine:latest
  before_script:
    - apk add --no-cache curl jq
  script:
    - |
      RESPONSE=$(curl -sf -X POST https://turbopentest.com/api/pentests \
        -H "X-API-Key: $TURBOPENTEST_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"targetUrl\": \"$TARGET_URL\"}")
      SCAN_ID=$(echo $RESPONSE | jq -r '.id')
      echo "Pentest started: $SCAN_ID"

      for i in $(seq 1 120); do
        STATUS=$(curl -sf -H "X-API-Key: $TURBOPENTEST_API_KEY" \
          https://turbopentest.com/api/pentests/$SCAN_ID | jq -r '.status')
        if [ "$STATUS" = "complete" ]; then break; fi
        if [ "$STATUS" = "failed" ]; then exit 1; fi
        sleep 60
      done

      CRITICALS=$(curl -sf -H "X-API-Key: $TURBOPENTEST_API_KEY" \
        https://turbopentest.com/api/pentests/$SCAN_ID | \
        jq '[.findings[] | select(.severity == "critical")] | length')
      if [ "$CRITICALS" -gt 0 ]; then exit 1; fi
  only:
    - main

Setup: Add TURBOPENTEST_API_KEY and TARGET_URL as CI/CD variables.

Jenkins

Jenkinsfile
pipeline {
    agent any
    environment {
        TURBOPENTEST_API_KEY = credentials('turbopentest-api-key')
        TARGET_URL = 'https://app.example.com'
    }
    stages {
        stage('Pentest') {
            steps {
                sh '''
                    RESPONSE=$(curl -sf -X POST https://turbopentest.com/api/pentests \
                        -H "X-API-Key: $TURBOPENTEST_API_KEY" \
                        -H "Content-Type: application/json" \
                        -d "{\\"targetUrl\\": \\"$TARGET_URL\\"}")
                    SCAN_ID=$(echo $RESPONSE | jq -r '.id')
                    echo "Pentest started: $SCAN_ID"

                    for i in $(seq 1 120); do
                        STATUS=$(curl -sf -H "X-API-Key: $TURBOPENTEST_API_KEY" \
                            https://turbopentest.com/api/pentests/$SCAN_ID | jq -r '.status')
                        if [ "$STATUS" = "complete" ]; then break; fi
                        if [ "$STATUS" = "failed" ]; then exit 1; fi
                        sleep 60
                    done

                    CRITICALS=$(curl -sf -H "X-API-Key: $TURBOPENTEST_API_KEY" \
                        https://turbopentest.com/api/pentests/$SCAN_ID | \
                        jq '[.findings[] | select(.severity == "critical")] | length')
                    if [ "$CRITICALS" -gt 0 ]; then exit 1; fi
                '''
            }
        }
    }
}

CircleCI

.circleci/config.yml
version: 2.1
jobs:
  pentest:
    docker:
      - image: cimg/base:current
    steps:
      - run:
          name: Run TurboPentest
          command: |
            RESPONSE=$(curl -sf -X POST https://turbopentest.com/api/pentests \
              -H "X-API-Key: $TURBOPENTEST_API_KEY" \
              -H "Content-Type: application/json" \
              -d "{\"targetUrl\": \"$TARGET_URL\"}")
            SCAN_ID=$(echo $RESPONSE | jq -r '.id')

            for i in $(seq 1 120); do
              STATUS=$(curl -sf -H "X-API-Key: $TURBOPENTEST_API_KEY" \
                https://turbopentest.com/api/pentests/$SCAN_ID | jq -r '.status')
              if [ "$STATUS" = "complete" ]; then break; fi
              if [ "$STATUS" = "failed" ]; then exit 1; fi
              sleep 60
            done

            CRITICALS=$(curl -sf -H "X-API-Key: $TURBOPENTEST_API_KEY" \
              https://turbopentest.com/api/pentests/$SCAN_ID | \
              jq '[.findings[] | select(.severity == "critical")] | length')
            if [ "$CRITICALS" -gt 0 ]; then exit 1; fi

workflows:
  deploy-and-test:
    jobs:
      - pentest:
          filters:
            branches:
              only: main

Azure DevOps

azure-pipelines.yml
trigger:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - script: |
      RESPONSE=$(curl -sf -X POST https://turbopentest.com/api/pentests \
        -H "X-API-Key: $(TURBOPENTEST_API_KEY)" \
        -H "Content-Type: application/json" \
        -d "{\"targetUrl\": \"$(TARGET_URL)\"}")
      SCAN_ID=$(echo $RESPONSE | jq -r '.id')

      for i in $(seq 1 120); do
        STATUS=$(curl -sf -H "X-API-Key: $(TURBOPENTEST_API_KEY)" \
          https://turbopentest.com/api/pentests/$SCAN_ID | jq -r '.status')
        if [ "$STATUS" = "complete" ]; then break; fi
        if [ "$STATUS" = "failed" ]; then exit 1; fi
        sleep 60
      done

      CRITICALS=$(curl -sf -H "X-API-Key: $(TURBOPENTEST_API_KEY)" \
        https://turbopentest.com/api/pentests/$SCAN_ID | \
        jq '[.findings[] | select(.severity == "critical")] | length')
      if [ "$CRITICALS" -gt 0 ]; then exit 1; fi
    displayName: 'Run TurboPentest'

Common patterns

Fail on critical or high findings

Replace the critical check with:

SERIOUS=$(curl -sf -H "X-API-Key: $TURBOPENTEST_API_KEY" \
  https://turbopentest.com/api/pentests/$SCAN_ID | \
  jq '[.findings[] | select(.severity == "critical" or .severity == "high")] | length')
if [ "$SERIOUS" -gt 0 ]; then exit 1; fi

Schedule pentests (not on every commit)

Use your CI/CD platform's cron/schedule feature instead of on: push. For example in GitHub Actions:

on:
  schedule:
    - cron: '0 2 * * 1'  # Every Monday at 2am

White box pentests in CI/CD

Add repoUrl to the request body:

-d '{
  "targetUrl": "'"$TARGET_URL"'",
  "repoUrl": "https://github.com/'"$GITHUB_REPOSITORY"'"
}'

On this page