Skip to main content

Overview

Esprit supports non-interactive mode for seamless integration with CI/CD pipelines. Run automated security scans on every commit, pull request, or deployment.

Exit Codes

Esprit uses standard exit codes for CI/CD integration:
  • 0 - Scan completed successfully with no vulnerabilities found
  • 2 - Vulnerabilities detected
  • Non-zero - Error during scan execution

Basic CI/CD Usage

esprit scan https://staging.example.com --non-interactive
The --non-interactive flag disables the TUI and outputs machine-readable results suitable for CI/CD environments.

GitHub Actions

Basic Workflow

Create .github/workflows/security-scan.yml:
name: Security Scan

on:
  pull_request:
  push:
    branches: [main, develop]
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM

jobs:
  security-scan:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      
      - name: Install Esprit
        run: |
          curl -fsSL https://raw.githubusercontent.com/esprit-cli/Esprit/main/scripts/install.sh | bash
          echo "$HOME/.esprit/bin" >> $GITHUB_PATH
      
      - name: Configure Esprit Cloud
        env:
          ESPRIT_TOKEN: ${{ secrets.ESPRIT_TOKEN }}
        run: |
          echo "$ESPRIT_TOKEN" | esprit provider login esprit --token-stdin
      
      - name: Run Security Scan
        run: |
          esprit scan https://staging.example.com \
            --non-interactive \
            --mode deep
      
      - name: Upload Report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: esprit-report
          path: ~/.esprit/scans/*/report.json

Docker-Based Workflow

For local runtime with your own LLM provider:
name: Security Scan (Docker)

on:
  pull_request:
  push:
    branches: [main]

jobs:
  security-scan:
    runs-on: ubuntu-latest
    
    services:
      docker:
        image: docker:dind
        options: --privileged
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Install Esprit
        run: |
          curl -fsSL https://raw.githubusercontent.com/esprit-cli/Esprit/main/scripts/install.sh | bash
          echo "$HOME/.esprit/bin" >> $GITHUB_PATH
      
      - name: Pull Esprit Sandbox Image
        run: docker pull improdead/esprit-sandbox:latest
      
      - name: Configure LLM Provider
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          export ESPRIT_LLM="anthropic/claude-sonnet-4-5-20250514"
          export LLM_API_KEY="$ANTHROPIC_API_KEY"
      
      - name: Run Security Scan
        env:
          ESPRIT_LLM: "anthropic/claude-sonnet-4-5-20250514"
          LLM_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          esprit scan ./src \
            --non-interactive \
            --mode standard \
            --instruction "Focus on authentication and authorization vulnerabilities"
      
      - name: Upload Scan Report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: security-report
          path: ~/.esprit/scans/*/report.json

Repository Code Scanning

name: Code Security Scan

on:
  pull_request:
    paths:
      - 'src/**'
      - 'api/**'

jobs:
  code-scan:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Esprit
        run: pip install esprit-cli
      
      - name: Scan Repository
        env:
          ESPRIT_LLM: "esprit/default"
          ESPRIT_TOKEN: ${{ secrets.ESPRIT_TOKEN }}
        run: |
          esprit scan . \
            --non-interactive \
            --mode quick \
            --instruction-file .esprit/scan-instructions.txt

GitLab CI/CD

Create .gitlab-ci.yml:
stages:
  - security

security-scan:
  stage: security
  image: python:3.12-slim
  
  before_script:
    - apt-get update && apt-get install -y curl git docker.io
    - curl -fsSL https://raw.githubusercontent.com/esprit-cli/Esprit/main/scripts/install.sh | bash
    - export PATH="$HOME/.esprit/bin:$PATH"
  
  script:
    - |
      esprit provider login esprit --token "$ESPRIT_TOKEN"
      esprit scan $CI_ENVIRONMENT_URL --non-interactive --mode deep
  
  artifacts:
    when: always
    paths:
      - ~/.esprit/scans/*/report.json
    expire_in: 30 days
  
  only:
    - merge_requests
    - main
    - develop

Jenkins Pipeline

pipeline {
    agent any
    
    environment {
        ESPRIT_LLM = 'esprit/default'
        ESPRIT_TOKEN = credentials('esprit-cloud-token')
    }
    
    stages {
        stage('Install Esprit') {
            steps {
                sh '''
                    curl -fsSL https://raw.githubusercontent.com/esprit-cli/Esprit/main/scripts/install.sh | bash
                    export PATH="$HOME/.esprit/bin:$PATH"
                '''
            }
        }
        
        stage('Security Scan') {
            steps {
                sh '''
                    export PATH="$HOME/.esprit/bin:$PATH"
                    esprit scan https://staging.example.com \
                        --non-interactive \
                        --mode standard
                '''
            }
        }
        
        stage('Archive Results') {
            steps {
                archiveArtifacts artifacts: '**/.esprit/scans/*/report.json', allowEmptyArchive: true
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
    }
}

CircleCI

Create .circleci/config.yml:
version: 2.1

jobs:
  security-scan:
    docker:
      - image: cimg/python:3.12
    
    steps:
      - checkout
      
      - setup_remote_docker:
          version: 20.10.24
      
      - run:
          name: Install Esprit
          command: |
            curl -fsSL https://raw.githubusercontent.com/esprit-cli/Esprit/main/scripts/install.sh | bash
            echo 'export PATH="$HOME/.esprit/bin:$PATH"' >> $BASH_ENV
      
      - run:
          name: Configure Provider
          command: |
            echo "$ESPRIT_TOKEN" | esprit provider login esprit --token-stdin
      
      - run:
          name: Run Security Scan
          command: |
            esprit scan $STAGING_URL \
              --non-interactive \
              --mode deep
      
      - store_artifacts:
          path: ~/.esprit/scans/
          destination: security-reports

workflows:
  version: 2
  security:
    jobs:
      - security-scan:
          context: security-testing
          filters:
            branches:
              only:
                - main
                - develop

Azure Pipelines

Create azure-pipelines.yml:
trigger:
  - main
  - develop

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: '3.12'
      addToPath: true
  
  - script: |
      curl -fsSL https://raw.githubusercontent.com/esprit-cli/Esprit/main/scripts/install.sh | bash
      echo "##vso[task.prependpath]$HOME/.esprit/bin"
    displayName: 'Install Esprit'
  
  - script: |
      esprit provider login esprit --token "$(ESPRIT_TOKEN)"
    displayName: 'Configure Esprit Cloud'
    env:
      ESPRIT_TOKEN: $(ESPRIT_TOKEN)
  
  - script: |
      esprit scan $(stagingUrl) --non-interactive --mode standard
    displayName: 'Run Security Scan'
  
  - task: PublishBuildArtifacts@1
    inputs:
      pathToPublish: '$(HOME)/.esprit/scans/'
      artifactName: 'security-reports'
    condition: always()

Docker Container Integration

Standalone Container Scan

FROM python:3.12-slim

RUN apt-get update && apt-get install -y \
    curl \
    git \
    docker.io \
    && rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://raw.githubusercontent.com/esprit-cli/Esprit/main/scripts/install.sh | bash
ENV PATH="/root/.esprit/bin:$PATH"

WORKDIR /workspace

COPY . .

CMD ["esprit", "scan", ".", "--non-interactive"]

Multi-Stage Build with Scan

# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Security scan stage
FROM python:3.12-slim AS security
RUN pip install esprit-cli
COPY --from=builder /app /scan
WORKDIR /scan
RUN esprit scan . --non-interactive || true

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --production
EXPOSE 3000
CMD ["node", "dist/server.js"]

Environment Variables for CI/CD

Esprit Cloud

ESPRIT_TOKEN=your-cloud-token
ESPRIT_LLM=esprit/default

Local Runtime with Providers

# Anthropic
ESPRIT_LLM=anthropic/claude-sonnet-4-5-20250514
LLM_API_KEY=sk-ant-...

# OpenAI
ESPRIT_LLM=openai/gpt-5-3-codex
LLM_API_KEY=sk-...

# Custom Base URL (Ollama, etc.)
ESPRIT_LLM=ollama/llama3
LLM_API_BASE=http://localhost:11434

Runtime Configuration

# Docker image override
ESPRIT_IMAGE=improdead/esprit-sandbox:latest

# Timeouts (seconds)
ESPRIT_SANDBOX_TIMEOUT=60
ESPRIT_SANDBOX_EXECUTION_TIMEOUT=120

# Platform override (for ARM builders)
ESPRIT_DOCKER_PLATFORM=linux/amd64

Advanced Patterns

Conditional Scanning

# Only scan on specific file changes
on:
  pull_request:
    paths:
      - 'src/**'
      - 'api/**'
      - 'package.json'
      - 'requirements.txt'

Parallel Scanning

jobs:
  scan-web:
    runs-on: ubuntu-latest
    steps:
      - name: Scan Web Application
        run: esprit scan https://web.example.com --non-interactive
  
  scan-api:
    runs-on: ubuntu-latest
    steps:
      - name: Scan API
        run: esprit scan https://api.example.com --non-interactive
  
  scan-code:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Scan Repository
        run: esprit scan . --non-interactive

Scheduled Scans

on:
  schedule:
    # Daily at 2 AM UTC
    - cron: '0 2 * * *'
    # Weekly on Sunday at midnight
    - cron: '0 0 * * 0'

Report Processing

Parse JSON Report

#!/bin/bash
REPORT=$(find ~/.esprit/scans -name "report.json" -type f -print -quit)

if [ -f "$REPORT" ]; then
  # Extract vulnerability count
  VULN_COUNT=$(jq '.vulnerabilities | length' "$REPORT")
  
  # Check severity levels
  CRITICAL=$(jq '[.vulnerabilities[] | select(.severity=="CRITICAL")] | length' "$REPORT")
  HIGH=$(jq '[.vulnerabilities[] | select(.severity=="HIGH")] | length' "$REPORT")
  
  echo "Found $VULN_COUNT vulnerabilities ($CRITICAL critical, $HIGH high)"
  
  # Fail if critical vulnerabilities found
  if [ "$CRITICAL" -gt 0 ]; then
    echo "CRITICAL vulnerabilities detected - failing build"
    exit 1
  fi
fi

Upload to Security Dashboard

- name: Upload to Security Dashboard
  run: |
    REPORT=$(find ~/.esprit/scans -name "report.json" -type f -print -quit)
    curl -X POST https://security-dashboard.example.com/api/reports \
      -H "Authorization: Bearer $DASHBOARD_TOKEN" \
      -H "Content-Type: application/json" \
      -d @"$REPORT"

Troubleshooting

Docker Socket Access

If using local runtime in CI/CD, ensure Docker socket access:
services:
  docker:
    image: docker:dind
    options: --privileged

env:
  DOCKER_HOST: tcp://docker:2376
  DOCKER_TLS_CERTDIR: /certs
  DOCKER_TLS_VERIFY: 1
  DOCKER_CERT_PATH: /certs/client

Rate Limiting

For high-frequency CI/CD runs, use Esprit Cloud or configure rate limits:
# Use quick mode for faster scans
esprit scan $URL --mode quick --non-interactive

# Add delay between scans
sleep 60

Caching

Cache Esprit installation for faster builds:
- name: Cache Esprit
  uses: actions/cache@v4
  with:
    path: ~/.esprit
    key: esprit-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}

Best Practices

  1. Use Non-Interactive Mode - Always include --non-interactive for CI/CD
  2. Set Appropriate Timeouts - Configure ESPRIT_SANDBOX_TIMEOUT based on your environment
  3. Secure Secrets - Store API keys and tokens in CI/CD secrets, never commit them
  4. Archive Reports - Always upload reports as artifacts for later review
  5. Fail Fast - Configure builds to fail on critical vulnerabilities
  6. Scan Strategy - Use quick mode for PRs, deep mode for releases
  7. Resource Limits - Set appropriate Docker memory/CPU limits in CI environments

See Also

Build docs developers (and LLMs) love