Skip to main content

Vulnerability Gates

Automated vulnerability scanning and threshold enforcement in CI/CD pipelines

Vulnerability gates automatically scan dependencies, container images, and code for known security vulnerabilities, blocking deployments when critical issues are detected.

Overview

Modern applications depend on hundreds of third-party libraries and base images. Vulnerability gates ensure that:

  • No critical CVEs enter production
  • Dependency versions are tracked and monitored
  • Known vulnerabilities trigger immediate action
  • Security teams have visibility into the attack surface

Real-World Impact

Equifax Breach (2017)

  • Exploited Apache Struts CVE-2017-5638 (CVSS 10.0)
  • Patch available 2 months before breach
  • 147 million records compromised
  • Cost: $1.4 billion in settlements
  • Prevention: Automated vulnerability gates would have blocked the vulnerable version

Pros:

  • Fast, comprehensive scanning (OS packages, language dependencies, IaC, secrets)
  • Easy to integrate, single binary
  • Excellent SBOM support
  • Free and open-source

Cons:

  • Limited false positive management
  • No central dashboard (use with Trivy Operator for Kubernetes)

Grype

Pros:

  • Fast scans with low memory footprint
  • Works with Syft SBOMs
  • Good vulnerability database coverage

Cons:

  • Fewer scan types than Trivy (no IaC/secrets scanning)
  • Less mature ecosystem

Snyk

Pros:

  • Excellent developer experience with fix suggestions
  • IDE integrations
  • Comprehensive language support
  • Remediation advice

Cons:

  • Commercial (free tier limited)
  • Can be slower than Trivy/Grype
  • Requires account/authentication

Severity Thresholds

# .trivy-policy.yaml
severity:
  CRITICAL:
    action: BLOCK
    max_allowed: 0
    notify:
      - security-team@company.com
      - slack:#security-alerts
  
  HIGH:
    action: WARN
    max_allowed: 5
    require_jira_ticket: true
  
  MEDIUM:
    action: WARN
    max_allowed: 20
  
  LOW:
    action: ALLOW
    report_only: true

ignore:
  - CVE-2023-12345  # False positive - not applicable
  - CVE-2023-67890  # Fix scheduled, approved exception

## Trivy Integration

### Basic Container Scanning

```bash
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Scan container image
trivy image --severity CRITICAL,HIGH nginx:latest

# Exit with error code if vulnerabilities found
trivy image --exit-code 1 --severity CRITICAL nginx:latest

# Generate report
trivy image --format json --output report.json nginx:latest

Filesystem Scanning

# Scan project dependencies
trivy fs --severity CRITICAL,HIGH .

# Scan specific package manager files
trivy fs --scanners vuln --severity CRITICAL package.json
trivy fs --scanners vuln --severity CRITICAL requirements.txt
trivy fs --scanners vuln --severity CRITICAL pom.xml

SBOM-Based Scanning

# Generate SBOM with Syft
syft nginx:latest -o cyclonedx-json > sbom.json

# Scan SBOM with Trivy
trivy sbom sbom.json --severity CRITICAL,HIGH

# This approach separates SBOM generation from vulnerability scanning
# Benefits: faster CI/CD, reusable SBOMs, audit trail

GitHub Actions Example

name: Vulnerability Gate

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

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'  # Fail if vulnerabilities found
      
      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'
      
      - name: Scan dependencies
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'table'
          severity: 'CRITICAL'
          exit-code: '1'

GitLab CI Example

vulnerability-gate:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy image --exit-code 1 --severity CRITICAL,HIGH $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - trivy fs --exit-code 1 --severity CRITICAL .
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json
  only:
    - merge_requests
    - main

Grype Integration

Basic Usage

# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

# Scan container image
grype nginx:latest

# Fail on high severity
grype nginx:latest --fail-on high

# Scan from SBOM
syft nginx:latest -o cyclonedx-json | grype

GitHub Actions with Grype

name: Grype Vulnerability Scan

on: [pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build image
        run: docker build -t myapp:test .
      
      - name: Scan with Grype
        uses: anchore/scan-action@v3
        with:
          image: myapp:test
          fail-build: true
          severity-cutoff: high

Advanced: Custom Vulnerability Database

For air-gapped environments or custom CVE tracking:

# Download Trivy vulnerability database
trivy image --download-db-only --cache-dir /tmp/trivy-cache

# Use offline database
trivy image --skip-db-update --cache-dir /tmp/trivy-cache nginx:latest

Handling False Positives

Trivy Ignore File

Create .trivyignore in your repo:

# False positive - vulnerability not applicable to our use case
CVE-2023-12345

# Acknowledged risk - approved by security team (JIRA: SEC-789)
CVE-2023-67890

# Waiting for upstream fix - temporary exception expires 2025-06-01
CVE-2024-11111

Policy-Based Exceptions

# trivy-policy.rego
package trivy

import rego.v1

# Ignore specific CVE in specific package
ignore contains result if {
    input.PkgName == "spring-core"
    input.VulnerabilityID == "CVE-2023-12345"
    result := {"reason": "False positive - not using vulnerable code path"}
}

# Allow medium severity in dev environment
ignore contains result if {
    input.Severity == "MEDIUM"
    os.Getenv("ENVIRONMENT") == "dev"
    result := {"reason": "Medium severity allowed in dev"}
}

Use with Trivy:

trivy image --ignore-policy trivy-policy.rego nginx:latest

Integration with SBOM Gates

Combine SBOM generation with vulnerability scanning:

#!/bin/bash
# combined-gate.sh

set -e

IMAGE="$1"

# Generate SBOM
echo "Generating SBOM..."
syft "$IMAGE" -o cyclonedx-json > sbom.json

# Upload SBOM to artifact repository
echo "Uploading SBOM to registry..."
curl -X POST -H "Content-Type: application/json" \
  --data @sbom.json \
  "https://sbom-registry.company.com/api/upload"

# Scan for vulnerabilities
echo "Scanning for vulnerabilities..."
trivy sbom sbom.json --severity CRITICAL,HIGH --exit-code 1

echo "✓ All gates passed"

Continuous Monitoring

Kubernetes Trivy Operator

For runtime vulnerability monitoring:

# Install Trivy Operator
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm install trivy-operator aqua/trivy-operator \
  --namespace trivy-system --create-namespace

# View vulnerability reports
kubectl get vulnerabilityreports -A
kubectl describe vulnerabilityreport <report-name>

Alerting on New CVEs

# cronjob-vuln-monitor.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: vuln-monitor
spec:
  schedule: "0 */6 * * *"  # Every 6 hours
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: trivy
            image: aquasec/trivy:latest
            command:
            - sh
            - -c
            - |
              trivy image --severity CRITICAL myapp:production > /tmp/report.txt
              if [ -s /tmp/report.txt ]; then
                curl -X POST "$SLACK_WEBHOOK" \
                  -d "{\"text\":\"⚠️ New critical vulnerabilities detected in production\"}"
              fi
            env:
            - name: SLACK_WEBHOOK
              valueFrom:
                secretKeyRef:
                  name: slack-webhook
                  key: url
          restartPolicy: OnFailure

Best Practices

1. Scan at Multiple Stages

Development → Build → Deploy → Runtime
    ↓           ↓        ↓         ↓
  IDE scan   Image    Registry  Operator
             scan      scan      monitoring

2. Progressive Enforcement

# Week 1-2: Monitor only (exit-code 0)
trivy image --severity CRITICAL nginx:latest

# Week 3-4: Block CRITICAL only
trivy image --exit-code 1 --severity CRITICAL nginx:latest

# Week 5+: Block CRITICAL and HIGH
trivy image --exit-code 1 --severity CRITICAL,HIGH nginx:latest

3. Dependency Pinning

Always pin exact versions to ensure reproducible builds:

# Bad: vulnerable to supply chain attacks
FROM node:18

# Good: pinned to specific digest
FROM node:18.19.0@sha256:a6385a6bb2fdcb7c48fc871e35e32af8daaa82c518900be49b76d10c005864c2

4. Automated Remediation

name: Auto-update Dependencies

on:
  schedule:
    - cron: '0 0 * * 1'  # Weekly on Monday

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Update dependencies
        run: |
          npm update
          npm audit fix
      
      - name: Run vulnerability scan
        run: trivy fs --exit-code 1 --severity CRITICAL .
      
      - name: Create pull request
        if: success()
        uses: peter-evans/create-pull-request@v5
        with:
          title: "chore: Update dependencies (weekly)"
          body: "Automated dependency updates passed vulnerability gates"

Metrics to Track

  • Mean Time to Remediate (MTTR): Average time from CVE discovery to fix
  • Vulnerability Debt: Total count of known vulnerabilities by severity
  • Gate Block Rate: Percentage of builds blocked by vulnerability gates
  • False Positive Rate: Percentage of flagged CVEs marked as not applicable

Troubleshooting

"Too Many Vulnerabilities" Error

Problem: Legacy application with hundreds of CVEs blocks all deployments.

Solution: Implement baseline and progressive reduction:

# Generate baseline
trivy image myapp:current --format json > baseline.json

# Only fail on NEW vulnerabilities
trivy image myapp:new --format json > current.json
diff baseline.json current.json || exit 1

Database Update Failures

Problem: Trivy fails to download vulnerability database in CI.

Solution: Cache the database:

- name: Cache Trivy DB
  uses: actions/cache@v3
  with:
    path: ~/.cache/trivy
    key: trivy-db-${{ github.run_id }}
    restore-keys: trivy-db-

Performance Issues

Problem: Scans take too long in CI pipeline.

Solution: Use parallel scanning:

# Scan image and filesystem in parallel
trivy image myapp:test &
trivy fs . &
wait

Next Steps

Found an issue?