Ci Security Gates

Automate security scanning in your CI/CD pipeline. --- Security gates enforce minimum security standards: - Block merges with critical vulnerabilities -...

Last updated: January 14, 2026

CI Security Gates

Automate security scanning in your CI/CD pipeline.


Overview

Security gates enforce minimum security standards:

  • Block merges with critical vulnerabilities
  • Require security review for high findings
  • Track security metrics over time
  • Create audit trail

GitHub Actions

Basic Setup

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install BlockSecOps CLI
        run: npm install -g @blocksecops/cli

      - name: Run Security Scan
        env:
          BLOCKSECOPS_API_KEY: ${{ secrets.BLOCKSECOPS_API_KEY }}
        run: |
          blocksecops scan ./contracts --preset standard --fail-on critical

With Build Step

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive  # For Foundry

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1

      - name: Build
        run: forge build

      - name: Security Scan
        env:
          BLOCKSECOPS_API_KEY: ${{ secrets.BLOCKSECOPS_API_KEY }}
        run: |
          blocksecops scan ./src --preset standard --fail-on critical

PR Comments

- name: Security Scan
  id: scan
  env:
    BLOCKSECOPS_API_KEY: ${{ secrets.BLOCKSECOPS_API_KEY }}
  run: |
    OUTPUT=$(blocksecops scan ./contracts --preset standard --format json)
    echo "results<<EOF" >> $GITHUB_OUTPUT
    echo "$OUTPUT" >> $GITHUB_OUTPUT
    echo "EOF" >> $GITHUB_OUTPUT
  continue-on-error: true

- name: Comment on PR
  if: github.event_name == 'pull_request'
  uses: actions/github-script@v7
  with:
    script: |
      const results = JSON.parse(`${{ steps.scan.outputs.results }}`);
      const body = `## Security Scan Results

      | Severity | Count |
      |----------|-------|
      | Critical | ${results.summary.critical} |
      | High | ${results.summary.high} |
      | Medium | ${results.summary.medium} |
      | Low | ${results.summary.low} |

      View Details`;

      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: body
      });

GitLab CI

Basic Setup

# .gitlab-ci.yml
stages:
  - build
  - security

security-scan:
  stage: security
  image: node:18
  before_script:
    - npm install -g @blocksecops/cli
  script:
    - blocksecops scan ./contracts --preset standard --fail-on critical
  variables:
    BLOCKSECOPS_API_KEY: $BLOCKSECOPS_API_KEY
  only:
    changes:
      - contracts/**/*.sol

With Artifacts

security-scan:
  stage: security
  script:
    - blocksecops scan ./contracts --preset standard --format json > security-report.json
    - blocksecops scan ./contracts --preset standard --fail-on critical
  artifacts:
    paths:
      - security-report.json
    expire_in: 30 days

Different Presets by Context

Quick for PRs, Standard for Main

# GitHub Actions
jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Determine Preset
        id: preset
        run: |
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            echo "preset=quick" >> $GITHUB_OUTPUT
            echo "threshold=critical" >> $GITHUB_OUTPUT
          else
            echo "preset=standard" >> $GITHUB_OUTPUT
            echo "threshold=high" >> $GITHUB_OUTPUT
          fi

      - name: Security Scan
        run: |
          blocksecops scan ./contracts \
            --preset ${{ steps.preset.outputs.preset }} \
            --fail-on ${{ steps.preset.outputs.threshold }}

Deep for Releases

release-scan:
  if: startsWith(github.ref, 'refs/tags/')
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - name: Deep Security Scan
      run: |
        blocksecops scan ./contracts --preset deep --fail-on high

Required Status Checks

GitHub Branch Protection

  1. Go to Settings → Branches
  2. Add rule for main
  3. Enable "Require status checks"
  4. Search and select "security-scan"
  5. Save

Now PRs can't merge without passing security scan.

GitLab Protected Branches

# gitlab-ci.yml
security-scan:
  stage: security
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - blocksecops scan ./contracts --preset standard --fail-on critical
  allow_failure: false  # Block MR if fails

Handling Failures

Allow High with Review

security-scan:
  runs-on: ubuntu-latest
  steps:
    - name: Security Scan
      id: scan
      run: |
        blocksecops scan ./contracts --preset standard --fail-on critical
      continue-on-error: true

    - name: Check for High Findings
      if: steps.scan.outcome == 'failure'
      run: |
        echo "::warning::Security scan found critical issues. Review required."

    - name: Require Security Review
      if: steps.scan.outcome == 'failure'
      run: |
        gh pr edit ${{ github.event.number }} --add-label "security-review-required"

Notify on Failure

- name: Notify Slack on Failure
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "Security scan failed for ${{ github.repository }}"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Caching

Cache Dependencies

- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}

- name: Cache Foundry
  uses: actions/cache@v4
  with:
    path: |
      ~/.foundry
      lib
    key: ${{ runner.os }}-foundry-${{ hashFiles('foundry.toml') }}

Skip Unchanged

- name: Check for changes
  uses: dorny/paths-filter@v2
  id: changes
  with:
    filters: |
      contracts:
        - 'contracts/**/*.sol'
        - 'src/**/*.sol'

- name: Security Scan
  if: steps.changes.outputs.contracts == 'true'
  run: blocksecops scan ./contracts --preset standard

Matrix Builds

Multiple Projects

strategy:
  matrix:
    project: [token, vault, governance]

steps:
  - name: Scan ${{ matrix.project }}
    run: |
      blocksecops scan ./packages/${{ matrix.project }}/contracts \
        --preset standard --fail-on critical

Multiple Presets

strategy:
  matrix:
    include:
      - preset: quick
        threshold: critical
      - preset: standard
        threshold: high

steps:
  - name: Scan (${{ matrix.preset }})
    run: |
      blocksecops scan ./contracts \
        --preset ${{ matrix.preset }} \
        --fail-on ${{ matrix.threshold }}

Metrics and Reporting

Save Results as Artifact

- name: Export Report
  run: |
    blocksecops scan ./contracts --preset standard --format json > results.json

- name: Upload Report
  uses: actions/upload-artifact@v4
  with:
    name: security-report
    path: results.json

Track Over Time

- name: Record Metrics
  run: |
    RESULTS=$(blocksecops scan ./contracts --format json)
    CRITICAL=$(echo $RESULTS | jq '.summary.critical')
    HIGH=$(echo $RESULTS | jq '.summary.high')

    # Send to metrics system
    curl -X POST "$METRICS_URL" \
      -d "critical=$CRITICAL&high=$HIGH&repo=$GITHUB_REPOSITORY"

Quality Gates (API-Based)

Quality Gates provide project-level security thresholds configured in the BlockSecOps dashboard. Unlike CLI-based --fail-on flags, Quality Gates persist configuration across all CI/CD integrations.

Configuration

Configure in Project Settings → Quality Gate:

Setting Description Default
Block on Critical Fail if ANY critical Enabled
Block on High Fail if ANY high Disabled
Max Critical Max allowed (0 = none) 0
Max High Max allowed (-1 = unlimited) -1
Max Medium Max allowed (-1 = unlimited) -1
Max Low Max allowed (-1 = unlimited) -1

API Integration

# GitHub Actions with Quality Gate API
- name: Evaluate Quality Gate
  run: |
    RESULT=$(curl -s -X POST "$BLOCKSECOPS_API_URL/quality-gates/projects/$PROJECT_ID/evaluate" \
      -H "Authorization: Bearer ${{ secrets.BLOCKSECOPS_API_KEY }}" \
      -H "Content-Type: application/json" \
      -d '{
        "scan_id": "'$SCAN_ID'",
        "triggered_by": "ci",
        "ci_context": {
          "branch": "'${{ github.ref_name }}'",
          "commit": "'${{ github.sha }}'",
          "pr": ${{ github.event.pull_request.number || 'null' }}
        }
      }')

    PASSED=$(echo $RESULT | jq -r '.passed')
    if [ "$PASSED" != "true" ]; then
      echo "Quality Gate FAILED"
      echo "$RESULT" | jq '.violations'
      exit 1
    fi

Build Status Endpoint

Check current quality gate status:

curl -s "$BLOCKSECOPS_API_URL/quality-gates/projects/$PROJECT_ID/build-status" \
  -H "Authorization: Bearer $API_KEY"

Response:

{
  "status": "passing",
  "quality_gate_name": "Production Gate",
  "critical_count": 0,
  "high_count": 2,
  "violations": [],
  "badge_url": "/api/v1/quality-gates/projects/{id}/badge.svg"
}

README Badge

Add a security status badge to your README:

[![Security](https://api.blocksecops.com/api/v1/quality-gates/projects/YOUR_PROJECT_ID/badge.svg)](https://app.blocksecops.com/projects/YOUR_PROJECT_ID)

The badge updates automatically based on latest scan results (cached 5 minutes).

Tier Requirement

Quality Gates require Developer tier or higher. Free tier users should use CLI --fail-on flags instead.


Best Practices

1. Fail Early

Use Quick preset for PRs - get feedback fast.

2. Strict for Main

Use Standard preset and --fail-on high for main branch.

3. Deep for Releases

Run Deep scan before any release or deployment.

4. Don't Skip

Avoid continue-on-error: true for security jobs.

5. Review, Don't Bypass

If scan fails, fix the issue rather than skipping.


Next Steps