VulnCheck runs in Jenkins through the VulnCheck CLI no plugin to install. The pipeline below installs a pinned CLI, scans the workspace, fails the build on findings that breach your policy, and archives the results.
Create a VulnCheck API token from API Tokens, then add it to Jenkins as a Secret text credential:
vulncheck-api-token this is what the pipeline referencesThe pipeline binds that credential to VC_TOKEN with withCredentials, so the token is masked in the build log and never lands in the environment of unrelated steps.
pipeline {
agent {
docker {
image 'debian:bookworm-slim'
args '-u root'
}
}
environment {
VC_CLI_VERSION = '1.1.0'
VC_CVSS_THRESHOLD = '7.0'
}
stages {
stage('Install the VulnCheck CLI') {
steps {
sh '''
apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates curl jq
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
vulncheck version
'''
}
}
stage('Scan') {
steps {
withCredentials([string(credentialsId: 'vulncheck-api-token', variable: 'VC_TOKEN')]) {
sh '''
vulncheck scan . --json > scan.json
jq -r '"\\((.vulnerabilities // []) | length) vulnerabilities found"' scan.json
'''
}
}
}
stage('Gate on findings') {
steps {
sh '''
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 "Findings at or above CVSS ${VC_CVSS_THRESHOLD}, or in VulnCheck KEV:"
jq -r --argjson max "$VC_CVSS_THRESHOLD" '
(.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
}
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'scan.json', allowEmptyArchive: true
}
}
}
jq programs — \\( rather than \(. Jenkins processes escape sequences even inside single-quoted Groovy strings, so an unescaped \( is rejected as an illegal escape character before the shell ever sees it.Other details worth knowing:
vulncheck scan exits 0 even when it finds critical vulnerabilities, so the scan stage always succeeds and the policy decision stays visible as its own stage in the build. See VulnCheck in CI/CD.args '-u root' lets the install step write to /usr/local/bin. Without it, install to a writable location and add it to PATH instead.archiveArtifacts in post { always { … } } keeps scan.json from failed builds, the runs you most want the evidence from.VC_CLI_VERSION. Every build then scans with a known CLI, and you avoid the unauthenticated GitHub API call that install.sh makes to resolve the latest release.To surface findings without blocking the build, mark it unstable rather than failed:
stage('Gate on findings') {
steps {
script {
def breaches = sh(returnStatus: true, script: '''
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
''')
if (breaches != 0) {
unstable('VulnCheck policy breach — see scan.json')
}
}
}
}
A common arrangement is to fail on change requests and only mark the branch build unstable, so a newly published CVE never blocks unrelated work:
if (breaches != 0) {
if (env.CHANGE_ID) {
error('VulnCheck policy breach on this change request')
} else {
unstable('VulnCheck policy breach on the branch build')
}
}
On a plain agent, install the CLI once on the host through your configuration management, or a one-off admin step and drop the install stage entirely:
pipeline {
agent { label 'linux' }
environment {
VC_CVSS_THRESHOLD = '7.0'
}
stages {
stage('Scan') {
steps {
withCredentials([string(credentialsId: 'vulncheck-api-token', variable: 'VC_TOKEN')]) {
sh 'vulncheck version'
sh 'vulncheck scan . --json > scan.json'
}
}
}
}
}
Keep vulncheck version in the build so the log records which CLI produced the results.
| Symptom | Cause |
|---|---|
illegal escape character when the pipeline loads | A jq string interpolation written as \( inside a Groovy string. Use \\(. |
vulncheck: command not found after a successful install stage | install.sh --sudo in a container running as root: sudo does not exist, and the script reports success anyway. Use the tarball install above. |
| Build fails with exit 3 | The credential is missing or the binding did not apply — check that the sh step using vulncheck is inside the withCredentials block. |
| Build fails with exit 5 | Rate limited, usually many executors sharing one token. |
Cannot iterate over null from jq | The vulnerabilities key is absent on a clean scan. Use (.vulnerabilities // [])[], as above. |
null (null) cannot be parsed as a number from jq | A finding with no cvss_base_score. Use .cvss_base_score // 0 | tonumber? // 0, as above. |