CI/CD Integration Guide

OF-CICD-001 v0.4.0

OpenForge integrates with all major CI/CD platforms to automate cryptographic hardware verification as part of your development workflow. This guide covers setup for GitHub Actions, GitLab CI, Jenkins, and Azure DevOps — including pre-merge gates, nightly crypto verification, SARIF security reporting, and webhook-driven notifications.

Overview #

A typical OpenForge CI/CD pipeline uses a tiered verification strategy that balances thoroughness against execution time:

TriggerStageChecksDurationPurpose
Every pushQuickLint + smoke simulation~2 minCatch syntax errors, basic regressions
Pull requestStandardFull simulation + formal + basic crypto~15 minPre-merge quality gate
NightlyFullAll crypto engines + exhaustive NTT + SCA~2 hrComprehensive security verification
Release tagCompleteFull + FIPS compliance report + timing~4 hrRelease qualification

OpenForge provides a scaffolding command that generates CI configuration files for your platform:

# Generate CI configuration for your platform
$ openforge ci setup --github          # → .github/workflows/openforge.yml
$ openforge ci setup --gitlab          # → .gitlab-ci.yml
$ openforge ci setup --jenkins         # → Jenkinsfile
$ openforge ci setup --azure           # → azure-pipelines.yml

# Generate with all tiers (push + PR + nightly + release)
$ openforge ci setup --github --tiers all

GitHub Actions #

The recommended GitHub Actions workflow uses OpenForge's official Docker image and supports parallel job execution, artifact caching, and SARIF upload to GitHub Advanced Security.

Complete Workflow

# .github/workflows/openforge.yml
name: OpenForge Verification

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'           # Nightly at 2 AM UTC
  workflow_dispatch:
    inputs:
      verification_level:
        type: choice
        options: [quick, standard, full, release]

env:
  OPENFORGE_IMAGE: ghcr.io/dyber/openforge:0.4.0
  OPENFORGE_LICENSE: ${{ secrets.OPENFORGE_LICENSE_KEY }}

jobs:
  # ── Quick checks (every push) ──
  lint-and-smoke:
    runs-on: ubuntu-latest
    container:
      image: ${{ env.OPENFORGE_IMAGE }}
    steps:
      - uses: actions/checkout@v4

      - name: Restore tool cache
        uses: actions/cache@v4
        with:
          path: ~/.openforge/cache
          key: openforge-${{ hashFiles('openforge.yaml') }}

      - name: Lint
        run: openforge verify --lint --report junit --output results/lint.xml

      - name: Smoke simulation
        run: openforge verify --sim --quick --report junit --output results/sim.xml

      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: lint-results
          path: results/

  # ── Standard verification (pull requests) ──
  verification:
    if: github.event_name == 'pull_request'
    needs: lint-and-smoke
    runs-on: ubuntu-latest
    container:
      image: ${{ env.OPENFORGE_IMAGE }}
    strategy:
      matrix:
        stage: [simulation, formal, crypto-basic]
    steps:
      - uses: actions/checkout@v4

      - name: Run ${{ matrix.stage }}
        run: |
          case "${{ matrix.stage }}" in
            simulation)
              openforge verify --sim --coverage \
                --report junit --output results/sim.xml
              ;;
            formal)
              openforge verify --formal --timeout 3600 \
                --report junit --output results/formal.xml
              ;;
            crypto-basic)
              openforge verify --crypto --constant-time --ntt-validation \
                --report sarif --output results/crypto.sarif
              ;;
          esac

      - name: Upload SARIF
        if: matrix.stage == 'crypto-basic' && always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results/crypto.sarif
          category: openforge-crypto

      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.stage }}-results
          path: results/

  # ── Nightly full crypto verification ──
  nightly-crypto:
    if: github.event_name == 'schedule'
    runs-on: ubuntu-latest
    container:
      image: ${{ env.OPENFORGE_IMAGE }}
      options: --cpus 8 --memory 32g
    timeout-minutes: 180
    steps:
      - uses: actions/checkout@v4

      - name: Full crypto verification
        run: |
          openforge verify --all \
            --crypto --all-engines \
            --ntt-exhaustive \
            --sca-traces 50000 \
            --report sarif --output results/full_crypto.sarif \
            --report html --output results/full_report.html \
            --report json --output results/full_results.json

      - name: Upload security results
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results/full_crypto.sarif
          category: openforge-nightly

      - name: Notify on failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {"text": "🔴 Nightly crypto verification FAILED — ${{ github.repository }}"}

Required Secrets

SecretRequiredDescription
OPENFORGE_LICENSE_KEYYes (commercial)License key for crypto verification engines
SLACK_WEBHOOK_URLOptionalSlack notification endpoint for failures
OPENFORGE_API_KEYOptionalAPI key for OpenForge Cloud dashboard

GitLab CI/CD #

# .gitlab-ci.yml
image: registry.gitlab.com/dyber/openforge:0.4.0

variables:
  OPENFORGE_LICENSE: $OPENFORGE_LICENSE_KEY

stages:
  - lint
  - verify
  - crypto
  - report

cache:
  key: openforge-${CI_COMMIT_REF_SLUG}
  paths:
    - .openforge/cache/

# ── Quick lint (every push) ──
lint:
  stage: lint
  script:
    - openforge verify --lint --report junit --output results/lint.xml
  artifacts:
    reports:
      junit: results/lint.xml

# ── Simulation + formal (merge requests) ──
simulation:
  stage: verify
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - openforge verify --sim --coverage --report junit --output results/sim.xml
  artifacts:
    reports:
      junit: results/sim.xml

formal:
  stage: verify
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - openforge verify --formal --timeout 3600 --report junit --output results/formal.xml
  artifacts:
    reports:
      junit: results/formal.xml

# ── Crypto verification (nightly + merge requests) ──
crypto-verification:
  stage: crypto
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual
      allow_failure: true
  script:
    - |
      openforge verify --crypto --all-engines \
        --report sarif --output results/crypto.sarif \
        --report html --output results/crypto_report.html
  artifacts:
    reports:
      sast: results/crypto.sarif
    paths:
      - results/crypto_report.html
  tags:
    - high-memory      # Requires runner with ≥16 GB RAM

Jenkins #

// Jenkinsfile
pipeline {
    agent {
        docker {
            image 'ghcr.io/dyber/openforge:0.4.0'
            args '--cpus 8 --memory 16g'
        }
    }

    environment {
        OPENFORGE_LICENSE = credentials('openforge-license-key')
    }

    stages {
        stage('Lint') {
            steps {
                sh 'openforge verify --lint --report junit --output results/lint.xml'
            }
            post {
                always { junit 'results/lint.xml' }
            }
        }

        stage('Verify') {
            parallel {
                stage('Simulation') {
                    steps {
                        sh 'openforge verify --sim --coverage --report junit --output results/sim.xml'
                    }
                }
                stage('Formal') {
                    steps {
                        sh 'openforge verify --formal --timeout 3600 --report junit --output results/formal.xml'
                    }
                }
            }
            post {
                always { junit 'results/*.xml' }
            }
        }

        stage('Crypto Verification') {
            when {
                anyOf {
                    triggeredBy 'TimerTrigger'     // Nightly
                    changeRequest()                 // Pull request
                }
            }
            steps {
                sh '''
                    openforge verify --crypto --all-engines \
                        --report sarif --output results/crypto.sarif \
                        --report html --output results/crypto_report.html
                '''
            }
            post {
                always {
                    archiveArtifacts artifacts: 'results/**'
                    recordIssues tool: sarif(pattern: 'results/crypto.sarif')
                }
            }
        }
    }

    triggers {
        cron('H 2 * * *')   // Nightly at ~2 AM
    }
}

Azure DevOps #

# azure-pipelines.yml
trigger:
  branches:
    include: [main, develop]

pr:
  branches:
    include: [main]

schedules:
  - cron: '0 2 * * *'
    displayName: Nightly verification
    branches:
      include: [main]

pool:
  vmImage: ubuntu-latest

container: ghcr.io/dyber/openforge:0.4.0

variables:
  - group: openforge-secrets

stages:
  - stage: QuickCheck
    jobs:
      - job: LintAndSmoke
        steps:
          - script: openforge verify --lint --sim --quick --report junit --output $(Build.ArtifactStagingDirectory)/results.xml
          - task: PublishTestResults@2
            inputs:
              testResultsFormat: JUnit
              testResultsFiles: '$(Build.ArtifactStagingDirectory)/*.xml'

  - stage: Verification
    condition: eq(variables['Build.Reason'], 'PullRequest')
    jobs:
      - job: FullVerification
        timeoutInMinutes: 60
        steps:
          - script: |
              openforge verify --all \
                --report sarif --output $(Build.ArtifactStagingDirectory)/crypto.sarif \
                --report junit --output $(Build.ArtifactStagingDirectory)/verify.xml
          - task: PublishTestResults@2
            inputs:
              testResultsFiles: '$(Build.ArtifactStagingDirectory)/*.xml'

Pre-Merge Gates #

OpenForge can enforce verification gates that block merges when critical checks fail. Configure these as required status checks in your repository settings.

Recommended Gate Policy

CheckGate LevelBlock Merge?Rationale
Lint (zero errors)RequiredYesPrevents broken syntax from entering main branch
Simulation passRequiredYesBasic functional correctness is non-negotiable
Formal (bounded)RequiredYesCritical properties must hold within bounded depth
Constant-timeRequiredYesTiming leaks are security-critical bugs
NTT validationRequiredYesAlgorithm correctness is non-negotiable
TVLA (side-channel)AdvisoryNoLong-running; nightly results inform decisions
FIPS complianceAdvisoryNoInformational until certification process begins
Fault injectionAdvisoryNoVery long-running; nightly only
# openforge.yaml — CI gate configuration
ci_integration:
  merge_gates:
    required:
      - check: lint
        fail_on: error
      - check: simulation
        fail_on: error
        coverage_threshold: 80    # Minimum line coverage %
      - check: formal
        timeout: 1800
      - check: constant_time
      - check: ntt_validation
    advisory:
      - side_channel
      - fips_compliance
      - fault_injection

Nightly Builds #

Nightly builds run the full crypto verification suite including computationally expensive checks that are impractical for every PR. Results are stored historically for trend analysis and regression detection.

# Run full nightly verification via CLI
$ openforge ci run --stage nightly

# Equivalent to:
$ openforge verify --all \
    --crypto --all-engines \
    --sca-traces 50000 \
    --ntt-exhaustive \
    --fault-injection --campaigns 10000 \
    --fips-compliance \
    --report sarif --output nightly/crypto.sarif \
    --report html --output nightly/report.html \
    --report json --output nightly/results.json

Configure notification channels for nightly failures:

ci_integration:
  notifications:
    on_failure:
      - type: slack
        webhook: ${SLACK_WEBHOOK_URL}
        channel: "#hw-verification"
      - type: email
        to: ["hw-team@dyber.com"]
    on_regression:
      - type: webhook
        url: ${PAGERDUTY_WEBHOOK}

SARIF Security Reporting #

OpenForge exports crypto verification findings in SARIF (Static Analysis Results Interchange Format) v2.1.0 for integration with GitHub Advanced Security, GitLab SAST, Azure DevOps, and other platforms that support the standard.

Each finding includes:

SARIF FieldOpenForge Mapping
ruleIdEngine-specific check ID (e.g., openforge-ct/tainted-control-flow)
levelMapped from severity: Critical → error, High → warning, Medium → note
messageHuman-readable description with remediation guidance
locationsRTL source file, line number, and signal name
relatedLocationsTaint source, dataflow path through design
fixesSuggested countermeasure (when applicable)
# Generate SARIF report
$ openforge verify --crypto --all --report sarif --output crypto.sarif

# Validate SARIF output (useful for custom integrations)
$ openforge report validate --format sarif crypto.sarif

# Convert SARIF to other formats
$ openforge report convert --from sarif --to html crypto.sarif -o report.html
$ openforge report convert --from sarif --to csv crypto.sarif -o findings.csv

Webhook Integration #

OpenForge can send real-time webhook notifications during verification runs. Configure endpoints in openforge.yaml or via the REST API:

ci_integration:
  webhooks:
    - url: https://hooks.slack.com/services/T.../B.../xxx
      events: [job.completed, job.failed]
      secret: ${WEBHOOK_SECRET}
    - url: https://api.pagerduty.com/v2/enqueue
      events: [crypto.critical_finding]
      headers:
        Authorization: Token ${PAGERDUTY_TOKEN}

Available webhook event types:

EventTrigger
job.startedVerification job begins execution
job.completedVerification job finishes (pass or fail)
job.failedVerification job encounters an error or failure
stage.completedIndividual verification stage completes
crypto.findingAny crypto verification finding (any severity)
crypto.critical_findingCritical severity crypto finding detected
coverage.regressionCode coverage drops below previous run

PR Status Comments #

OpenForge can post formatted verification summary comments directly on pull requests. This requires a GitHub App or personal access token with pull_request:write permission.

# GitHub Actions step — post PR comment
- name: Post verification summary
  if: github.event_name == 'pull_request' && always()
  run: |
    openforge report comment \
      --format github \
      --results results/ \
      --pr ${{ github.event.pull_request.number }} \
      --repo ${{ github.repository }}
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The posted comment includes a collapsible summary with pass/fail status per engine, coverage delta, timing summary, and links to full report artifacts.

Caching & Performance #

OpenForge supports incremental verification and result caching to minimize CI execution time. When source files haven't changed, previously verified results are reused.

# openforge.yaml — caching configuration
ci_integration:
  caching:
    enabled: true
    cache_dir: .openforge/cache
    strategy: content-hash        # content-hash | timestamp | none
    max_age: 7d                  # Invalidate cache entries older than 7 days
    incremental: true             # Only re-verify changed modules

Performance Tips

TechniqueImpactTrade-off
Incremental verificationSkip unchanged modulesRequires cache persistence between runs
Parallel matrix jobsRun sim/formal/crypto in parallelHigher runner cost
Reduced SCA tracesUse 10K traces for PR, 50K for nightlyLower confidence in PR results
Quick formal modeBMC depth 20 instead of 100May miss deep bugs
Selective crypto checksRun only CT + NTT for PRsMissing SCA/FIPS/FI coverage on PR

Air-Gapped CI #

For defense and government customers operating in air-gapped environments, OpenForge supports fully offline CI/CD deployment with pre-loaded Docker images and local NIST test vector caches.

# Pre-load everything for air-gapped deployment

# 1. Export Docker image (on internet-connected machine)
$ docker save ghcr.io/dyber/openforge:0.4.0 | gzip > openforge-0.4.0.tar.gz

# 2. Pre-download NIST test vectors
$ openforge vectors download --all --output nist-vectors/

# 3. Transfer to air-gapped network (USB, one-way diode, etc.)

# 4. Load on air-gapped Jenkins/GitLab runner
$ docker load < openforge-0.4.0.tar.gz

# 5. Configure offline mode
$ openforge config set --global offline_mode true
$ openforge config set --global nist_vector_cache /opt/nist-vectors/
Note: Air-gapped installations require an offline license file rather than network-validated license key. Contact sales@dyber.com for air-gapped licensing.