Fortify CI/CD Integration Guide

📘

Who can Use this ?

Developers & Software Engineers, AI/ML Professionals, DevOPs & Infrastructure teams, QA and Test Engineers are the ideal users.

This section provides a step-to-step guide to implement Fortify CI/CD integration into your pipeline, using a connector script, a configuration file, and hardcoded deployment steps. This guide is perfect for users who want to Integrate automated security scanning into CI/CD pipelines for continuous protection.

Introduction

Overview

CI/CD integration for Fortify introduces a new feature enabling automated red teaming scans within the Software Development Lifecycle (SDLC). Using an asynchronous session-based architecture, CI/CD pipelines can trigger Fortify scans on remote connector instances, allowing automated security validation as part of build and deployment workflows.

Key Capabilities

  1. CI/CD Scan Triggering – Pipelines can initiate Fortify scan sessions via API.
  2. Async Session Architecture – CI/CD sends a request; the Target connector polls and executes the scan.
  3. Session-Based Tracking – Each scan generates a unique Session ID for status monitoring and results.
  4. Flexible Pipeline Behavior – Pipelines can either:
    • Fire-and-Forget (trigger scan and continue), or
    • Wait/Block (poll for scan completion and results).

Prerequisites

  • Connector Script: A connector script must be built to handle logic such as hitting the bots API, authentication, and fetching the conversation ID.
  • Fortify User and Target: You must be a valid user and create your target in the Fortify UI instance to generate the necessary API key for authentication.
  • Supported Session Types: The current CI/CD integration only supports the following session types:
    • Essential

    • Comprehensive

    • Target specific Note: Attack library functionality is not supported in the current version.


Step-by-Step Implementation

Step 1: Create and Configure fortifyconfig.yml

This YAML file holds the session parameters and must be added to your codebase.

Example of a configuration file:


api_key: FORTIFY_API_KEY                     # Name of the environment variable containing your key
base_url: https://fortify-dev.fuelix.ai      # Optional - Custom Fortify API base URL
wait_for_completion: false                   # false = fire-and-forget; true = waits until done


session:
  integration_type: "api"         # "api" | "http"
  session_type: "essential"       # "essential" | "comprehensive" | "target_specific"
  attacker_type: "mturn"          # "sturn" (single-turn) | "mturn" (multi-turn)
  rerun_count: 0                  # [1, 5, 10, 15, 20] for sturn; 0 for mturn
  lang: "en-US"                   # language code
  tags: [ci]                      # Labels for filtering

# fortifyconfig.yml


authentication:
  api_key: "<YOUR_FORTIFY_API_KEY>"
  base_url: "https://fortify.fax.ai"


session_parameters:
  wait_for_completion: false # Set to 'true' to halt deployment
  integration_type: API
  session_type: essential
  attacker_type: singleton
  rerun_count: 5 # Must be 1-20 for 'singleton', 0 for 'multi-turn'
  language: enus
  1. Define Authentication Parameters: Include your Fortify API key and the base URL (e.g., fortify.fax.ai).
  2. Configure wait for completion:
  • Set to true to halt the build/deployment until Fortify returns scan results.
  • Set to false for "fire and forget," where the session triggers in the background, but the build proceeds without waiting for results (default).
  1. Define Session Parameters: These mirror the fields found in the Fortify UI for creating a new session.
ParametersOptions/Details
integration typeAPI or HTTP.
session typeChoose one: essential, comprehensive, or target specific. Using any other value will result in an error.
attacker typeChoose multi-turn or singleton.
rerun countIf multi-turn is chosen: Must be 0. If singleton is chosen: Must be between 1 and 20. 0 is not supported.
languageSupported languages are enus and ca.

Step 2: Add Commands to Your Deployment File

You must add three hardcoded commands to your existing deployment YAML file, typically located in your GitHub workflows folder.

  1. Install Python Version
    • Add the command to install a Python version. This is required regardless of your bot's primary language.
  2. Install Fortify SDK
    • Add the command to install the Fortify SDK via a public URL. This initiates the connection logic.
  3. Run Security Scan
    • Add the command to run the security scan, ensuring it references the fortifyconfig.yml file to utilize the defined session parameters.

Step 3: Post-Scan Management (Optional but Recommended)

For better visibility in your CI/CD process, consider adding steps to display scan results on Pull Requests (PRs).

  • Name Scan Session on PR: Include a command to name the scan session on a PR. Fortify will then provide a link to the running session and display the results upon completion.
  • GitHub Action Integration: This allows Fortify, through a GitHub action, to comment directly on the PR with vulnerability scan results and metrics.
  • Conditional Build Failure: You can implement simple if/else logic in your deployment file to enforce build failure based on metrics (e.g., if the vulnerability rate is greater than 10%) or if the scan status is failed.

Step 4: Scheduling and Triggering (Optional)

Control when the CI/CD session is triggered.

  • Define Trigger Branches: Specify which branches or pull requests should trigger the CI/CD session.
  • Recommended Trigger: Configure the scan to run when a Pull Request is opened. This allows reviewers to examine Fortify results before approving the code merge and deployment.
  • Scheduled Reruns: Use schedule with cron syntax (e.g., in UTC timing) to automatically trigger Fortify sessions on a recurring basis (e.g., nightly, weekly, or monthly) directly from your codebase.

CI/CD Pipeline Integration

Three steps to add Fortify to any pipeline:

Step 1 → Commit fortifyconfig.yml to your repo
Step 2 → Store FORTIFY_API_KEY as a CI secret
Step 3 → Add the fortify run step

Step 1: Create the Configuration File

Place fortifyconfig.yml in your repository root. The API key is resolved from the environment at runtime — never hardcode it.

api_key: FORTIFY_API_KEY
wait_for_completion: true
session:
  integration_type: "http"
  session_type: "comprehensive"
  attacker_type: "sturn"
  rerun_count: 5
  lang: "en-US"
  tags: [ci]
  http_integration_opts:
    endpoint: "https://your-bot.example.com/chat"
    headers:
      Authorization: "Bearer YOUR_TOKEN"

Validate locally before committing:

export FORTIFY_API_KEY="your-key"
fortify -c fortifyconfig.yaml validate

Step 2: Store the API Key as a CI Secret

⚠️ Never commit API keys to source control.

CI ProviderWhere
GitHub ActionsSettings → Secrets and variables → Actions → New repository secret
GitLab CISettings → CI/CD → Variables → Add variable (enable Masked)
JenkinsManage Jenkins → Credentials → Global → Add Credentials → Secret text

Secret name: FORTIFY_API_KEY


Step 3: Add the CI Job

GitHub Actions - Default Workflow:

# .github/workflows/fortify-security-test.yml
name: Fortify Security Scan
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'
jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install FortifySDK
        run: pip install https://storage.googleapis.com/fortify-sdk/releases/fortifysdk-1.1.0-py3-none-any.whl
      - name: Validate configuration
        env:
          FORTIFY_API_KEY: ${{ secrets.FORTIFY_API_KEY }}
        run: fortify -c fortifyconfig.yaml validate
      - name: Run security scan
        env:
          FORTIFY_API_KEY: ${{ secrets.FORTIFY_API_KEY }}
        run: fortify -c fortifyconfig.yaml run --verbose --output results.json
      - name: Upload results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: fortify-results
          path: results.json
          retention-days: 30
      - name: Enforce vulnerability threshold
        run: |
          VULN_RATE=$(jq -r '.vulnerability_rate' results.json | tr -d '%')
          if [ "$VULN_RATE" -gt "10" ]; then
            echo "❌ Vulnerability rate ${VULN_RATE}% exceeds threshold."
            exit 1
          fi
          echo "✅ Vulnerability rate ${VULN_RATE}% is within threshold."

GitHub Actions — api integration (two-process):

# .github/workflows/fortify-api-integration.yml
name: Fortify Security Scan (API Integration)
on:
  push:
    branches: [main]
jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          pip install https://storage.googleapis.com/fortify-sdk/releases/fortifysdk-1.1.0-py3-none-any.whl
          pip install -r requirements.txt
      - name: Start executor in background
        env:
          FORTIFY_API_KEY: ${{ secrets.FORTIFY_API_KEY }}
          BOT_API_KEY: ${{ secrets.BOT_API_KEY }}
          BOT_BASE_URL: ${{ secrets.BOT_BASE_URL }}
        run: |
          python executor.py &
          echo $! > executor.pid
          sleep 5
      - name: Run security scan
        env:
          FORTIFY_API_KEY: ${{ secrets.FORTIFY_API_KEY }}
        run: fortify -c fortifyconfig.yaml run --verbose --output results.json
      - name: Stop executor
        if: always()
        run: kill $(cat executor.pid) || true
      - name: Upload results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: fortify-results
          path: results.json
          retention-days: 30

GitHub Actions — wait_for_completion: false + polling loop:

Use this when you want the session to be created quickly (false), but still need results in the same pipeline run. The trick: loop fortify status until the session completes.

# .github/workflows/fortify-poll-loop.yml
name: Fortify Scan (Poll Loop)
on:
  push:
    branches: [main]
jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install FortifySDK
        run: pip install https://storage.googleapis.com/fortify-sdk/releases/fortifysdk-1.1.0-py3-none-any.whl
      - name: Create session (fire-and-forget)
        env:
          FORTIFY_API_KEY: ${{ secrets.FORTIFY_API_KEY }}
        run: |
          fortify -c fortifyconfig.yaml run --output session.json
          echo "SESSION_ID=$(jq -r '.session_id' session.json)" >> "$GITHUB_ENV"
      - name: Poll until session completes
        env:
          FORTIFY_API_KEY: ${{ secrets.FORTIFY_API_KEY }}
        run: |
          echo "Polling session: $SESSION_ID"
          while true; do
            fortify -c fortifyconfig.yaml status -s "$SESSION_ID" --output results.json
            STATUS=$(jq -r '.status' results.json)
            echo "Status: $STATUS"
            if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
              break
            fi
            sleep 30
          done
      - name: Enforce vulnerability threshold
        run: |
          VULN_RATE=$(jq -r '.vulnerability_rate' results.json | tr -d '%')
          if [ "$VULN_RATE" -gt "10" ]; then
            echo "❌ Vulnerability rate ${VULN_RATE}% exceeds threshold."
            exit 1
          fi
          echo "✅ Vulnerability rate ${VULN_RATE}% is within threshold."
      - name: Upload results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: fortify-results
          path: results.json

The fortifyconfig.yaml for this workflow:

api_key: FORTIFY_API_KEY
wait_for_completion: false          # ← CLI exits immediately after session creation
session:
  integration_type: "http"
  session_type: "comprehensive"
  attacker_type: "sturn"
  rerun_count: 5
  lang: "en-US"
  tags: [ci]
  http_integration_opts:
    endpoint: "https://your-bot.example.com/chat"
    headers:
      Authorization: "Bearer YOUR_TOKEN"

GitHub Actions — Post scan results as a PR comment:

# .github/workflows/fortify-pr-comment.yml
name: Fortify Scan + PR Comment
on:
  pull_request:
    branches: [main]
jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install FortifySDK
        run: pip install https://storage.googleapis.com/fortify-sdk/releases/fortifysdk-1.1.0-py3-none-any.whl
      - name: Run security scan
        env:
          FORTIFY_API_KEY: ${{ secrets.FORTIFY_API_KEY }}
        run: fortify -c fortifyconfig.yaml run --verbose --output results.json
      - name: Comment results on PR
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = JSON.parse(fs.readFileSync('results.json', 'utf8'));
            const rate = results.vulnerability_rate || 'N/A';
            const status = results.status || 'unknown';
            const total = results.total_conversations || 0;
            const vuln = results.vulnerable_conversations || 0;
            const icon = parseInt(rate) > 10 ? '🔴' : '🟢';
            const body = `## ${icon} Fortify Security Scan Results
            | Metric | Value |
            |---|---|
            | **Status** | \`${status}\` |
            | **Total Conversations** | ${total} |
            | **Vulnerable** | ${vuln} |
            | **Vulnerability Rate** | **${rate}** |
            > Session: \`${results.session_id}\``;
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: body
            });

GitLab CI:

# .gitlab-ci.yml
fortify-security-scan:
  stage: test
  image: python:3.11-slim
  before_script:
    - pip install https://storage.googleapis.com/fortify-sdk/releases/fortifysdk-1.1.0-py3-none-any.whl
    - fortify -c fortifyconfig.yaml validate
  script:
    - fortify -c fortifyconfig.yaml run --verbose --output results.json
  artifacts:
    paths: [results.json]
    expire_in: 30 days
    when: always
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Secret: Settings → CI/CD → Variables → Add variable. Key: FORTIFY_API_KEY. Enable Masked and Protected.


Jenkins:

// Jenkinsfile
pipeline {
    agent any
    environment {
        FORTIFY_API_KEY = credentials('fortify-api-key')
    }
    stages {
        stage('Install')       { steps { sh 'pip install https://storage.googleapis.com/fortify-sdk/releases/fortifysdk-1.1.0-py3-none-any.whl' } }
        stage('Validate')      { steps { sh 'fortify -c fortifyconfig.yaml validate' } }
        stage('Security Scan') { steps { sh 'fortify -c fortifyconfig.yaml run --verbose --output results.json' } }
    }
    post {
        always  { archiveArtifacts artifacts: 'results.json', allowEmptyArchive: true }
        failure { echo 'Security scan failed. Review results.json.' }
    }
}

Secret: Manage Jenkins → Credentials → Global → Add Credentials → Secret text. ID: fortify-api-key.


Configuration Reference


The fully annotated fortifyconfig.yamlsingle source of truth for all settings:

────────────────────────────────────────────────
# AUTHENTICATION
────────────────────────────────────────────────
api_key: FORTIFY_API_KEY
# Name of the environment variable holding your API key.
# The SDK reads the key from the environment at runtime — not from this file.
base_url: https://api.fortify.fuelix.ai
# Optional. Override the default Fortify API base URL.
────────────────────────────────────────────────
# EXECUTION MODE
────────────────────────────────────────────────
wait_for_completion: true
# true  → Block until the session finishes and return full results.
# false → Create the session and exit immediately (fire-and-forget).
# See "Understanding wait_for_completion" for detailed behaviour.
────────────────────────────────────────────────
# SESSION SETTINGS
────────────────────────────────────────────────
session:
  integration_type: "http"
  # "http" → Fortify calls your bot's HTTP endpoint directly.
  # "api"  → You run an SDK executor that polls and forwards prompts.
  # See "Integration Types" for when to use each.
  session_type: "essential"
  # "essential"       → Fast scan with core attack vectors.
  # "comprehensive"   → Deep scan with extended attack surface.
  # "target_specific" → Custom, targeted attack scenarios.
  attacker_type: "sturn"
  # "sturn" → Single-turn: one prompt, one response per conversation.
  # "mturn" → Multi-turn: maintains conversation history across turns.
  rerun_count: 5
  # For sturn: 1–20.
  # For mturn: must be 0.
  lang: "en-US"
  # BCP-47 language code for generated attack prompts.
  tags: [ci, production]
  # Labels for filtering sessions on the dashboard.
  # ── HTTP Integration (required when integration_type is "http") ──
  http_integration_opts:
    endpoint: "https://your-bot.example.com/chat"
    headers:
      Authorization: "Bearer YOUR_TOKEN"
  # ── Metadata (optional) ──
  meta:
    model: "gpt-4"
    environment: "staging"
────────────────────────────────────────────────
# POLLING (only applies when wait_for_completion is true)
────────────────────────────────────────────────
polling:
  interval: 30
  # Seconds between status checks. Minimum: 10. Default: 10.

Field Reference


FieldRequiredDefaultDescription
api_keyAPI key string or name of an env var containing it
base_urlPlatform defaultCustom Fortify API base URL
wait_for_completionfalseBlock until the session finishes
session.integration_type"http" or "api"
session.session_type"essential" · "comprehensive" · "target_specific"
session.attacker_type"sturn" (single-turn) · "mturn" (multi-turn)
session.rerun_count1–20 for sturn; 0 for mturn
session.langBCP-47 language code (e.g., "en-US")
session.tagsList of categorization tags
session.metaArbitrary key-value metadata
session.http_integration_opts.endpoint✅(http)Your bot's HTTP URL
session.http_integration_opts.headersHeaders sent with every attack request
polling.interval10Seconds between polling cycles (min: 10)




Did this page help you?