CI/CD Pipelines

Other CI platforms

The generic VulnCheck CI recipe, with examples for Bitbucket Pipelines, CircleCI, Drone, Woodpecker and Buildkite.

Every VulnCheck CI integration is the same five steps; install a pinned CLI, expose VC_TOKEN, scan, gate, publish. So any platform that can run a shell script in a container can run it. VulnCheck in CI/CD explains each step; this page applies them to platforms without a guide of their own.

The generic recipe

# 1. Install a pinned CLI
VC_CLI_VERSION=1.1.0
ARCH="$(uname -m)"
case "$ARCH" in x86_64) ARCH=amd64 ;; aarch64) ARCH=arm64 ;; esac
curl -sSL "https://github.com/vulncheck-oss/cli/releases/download/v${VC_CLI_VERSION}/vulncheck_${VC_CLI_VERSION}_linux_${ARCH}.tar.gz" | tar -xz -C /tmp
install -m 0755 "/tmp/vulncheck_${VC_CLI_VERSION}_linux_${ARCH}/bin/vulncheck" /usr/local/bin/vulncheck

# 2. VC_TOKEN comes from the platform's secret store — never from the repository

# 3. Scan
vulncheck scan . --json > scan.json
jq -r '"\((.vulnerabilities // []) | length) vulnerabilities found"' scan.json

# 4. Gate
jq -e --argjson max 7.0 '
  [ (.vulnerabilities // [])[]
    | select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
' scan.json > /dev/null || {
  echo "VulnCheck policy breach:"
  jq -r --argjson max 7.0 '
    (.vulnerabilities // [])[]
    | select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev)
    | "  \(.cve)  \(.name)@\(.version)  CVSS \(.cvss_base_score)  KEV \(.in_kev)  fixed in \(.fixed_versions // "n/a")"
  ' scan.json
  exit 1
}

# 5. Publish scan.json as a build artifact

Three things trip people up on every platform:

  • vulncheck scan exits 0 even when it finds critical vulnerabilities, so step 4 is what fails your build. Without it the job goes green with findings in the log.
  • A clean scan omits the vulnerabilities key entirely, so (.vulnerabilities // [])[] is required — iterating it unguarded makes jq exit 5 on a repository with nothing wrong with it. .cvss_base_score // 0 | tonumber? // 0 guards the other end: a finding without a CVSS score would otherwise abort the whole program on tonumber, with the same exit 5, before .in_kev is evaluated.
  • The install.sh script is glibc-only and treats --sudo as available even in a root container, where it silently installs nothing. The tarball above works everywhere, Alpine included.

Bitbucket Pipelines

Add VC_TOKEN under Repository settings > Repository variables with Secured ticked.

image: debian:bookworm-slim

definitions:
  steps:
    - step: &vulncheck-scan
        name: Scan with VulnCheck
        script:
          - apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates curl jq
          - export VC_CLI_VERSION=1.1.0
          - curl -sSL "https://github.com/vulncheck-oss/cli/releases/download/v${VC_CLI_VERSION}/vulncheck_${VC_CLI_VERSION}_linux_amd64.tar.gz" | tar -xz -C /tmp
          - install -m 0755 "/tmp/vulncheck_${VC_CLI_VERSION}_linux_amd64/bin/vulncheck" /usr/local/bin/vulncheck
          - vulncheck scan . --json > scan.json
          - jq -r '"\((.vulnerabilities // []) | length) vulnerabilities found"' scan.json
          - |
            jq -e --argjson max 7.0 '
              [ (.vulnerabilities // [])[]
                | select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
            ' scan.json > /dev/null || { echo "VulnCheck policy breach"; exit 1; }
        artifacts:
          - scan.json

pipelines:
  pull-requests:
    '**':
      - step: *vulncheck-scan
  branches:
    main:
      - step: *vulncheck-scan

CircleCI

Add VC_TOKEN as a project environment variable, or to a context shared across projects.

version: 2.1

jobs:
  vulncheck-scan:
    docker:
      - image: cimg/base:current
    environment:
      VC_CLI_VERSION: 1.1.0
      VC_CVSS_THRESHOLD: '7.0'
    steps:
      - checkout
      - run:
          name: Install the VulnCheck CLI
          command: |
            curl -sSL "https://github.com/vulncheck-oss/cli/releases/download/v${VC_CLI_VERSION}/vulncheck_${VC_CLI_VERSION}_linux_amd64.tar.gz" | tar -xz -C /tmp
            sudo install -m 0755 "/tmp/vulncheck_${VC_CLI_VERSION}_linux_amd64/bin/vulncheck" /usr/local/bin/vulncheck
            vulncheck version
      - run:
          name: Scan
          command: |
            vulncheck scan . --json > scan.json
            jq -r '"\((.vulnerabilities // []) | length) vulnerabilities found"' scan.json
      - run:
          name: Gate on findings
          command: |
            jq -e --argjson max "$VC_CVSS_THRESHOLD" '
              [ (.vulnerabilities // [])[]
                | select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
            ' scan.json > /dev/null || { echo "VulnCheck policy breach"; exit 1; }
      - store_artifacts:
          path: scan.json

workflows:
  security:
    jobs:
      - vulncheck-scan

Drone and Woodpecker

Add vc_token as a repository secret. The example below is Drone; Woodpecker takes the same steps with the top-level kind, type and name keys removed.

kind: pipeline
type: docker
name: security

steps:
  - name: vulncheck-scan
    image: debian:bookworm-slim
    environment:
      VC_TOKEN:
        from_secret: vc_token
      VC_CLI_VERSION: 1.1.0
    commands:
      - apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates curl jq
      - curl -sSL "https://github.com/vulncheck-oss/cli/releases/download/v${VC_CLI_VERSION}/vulncheck_${VC_CLI_VERSION}_linux_amd64.tar.gz" | tar -xz -C /tmp
      - install -m 0755 "/tmp/vulncheck_${VC_CLI_VERSION}_linux_amd64/bin/vulncheck" /usr/local/bin/vulncheck
      - vulncheck scan . --json > scan.json
      - jq -r '"\((.vulnerabilities // []) | length) vulnerabilities found"' scan.json
      - |
        jq -e --argjson max 7.0 '
          [ (.vulnerabilities // [])[]
            | select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
        ' scan.json > /dev/null || { echo "VulnCheck policy breach"; exit 1; }

Buildkite

Expose VC_TOKEN through your agent's environment hook or secrets helper, then run the scan in a container:

steps:
  - label: 'Scan with VulnCheck'
    plugins:
      - docker#v5.11.0:
          image: debian:bookworm-slim
          propagate-environment: true
          environment:
            - VC_TOKEN
    commands:
      - apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates curl jq
      - export VC_CLI_VERSION=1.1.0
      - curl -sSL "https://github.com/vulncheck-oss/cli/releases/download/v${VC_CLI_VERSION}/vulncheck_${VC_CLI_VERSION}_linux_amd64.tar.gz" | tar -xz -C /tmp
      - install -m 0755 "/tmp/vulncheck_${VC_CLI_VERSION}_linux_amd64/bin/vulncheck" /usr/local/bin/vulncheck
      - vulncheck scan . --json > scan.json
      - |
        jq -e --argjson max 7.0 '
          [ (.vulnerabilities // [])[]
            | select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
        ' scan.json > /dev/null || { echo "VulnCheck policy breach"; exit 1; }
    artifact_paths:
      - scan.json

Anything else

Kubernetes-native runners such as Tekton, Argo Workflows and GitLab's Kubernetes executor need nothing special: run the generic recipe in a step container, mount VC_TOKEN from a Secret, and write scan.json to a shared workspace for a later step to publish.