-
Notifications
You must be signed in to change notification settings - Fork 0
jenkins
This guide covers all aspects of integrating Purplemet security analyses into Jenkins pipelines.
- Prerequisites
- Quick Start
- Installation Methods
- Parameters
- Security Gates
- Complete Pipeline Examples
- Results and Exit Codes
- Advanced Usage
- FAQ / Common Errors
Create a token at cloud.purplemet.com.
The token must have the Operator role. The Administrator role is discouraged for CI/CD usage — the CLI will display a warning if an Administrator token is detected.
Add the token as a Secret text credential:
- Go to Manage Jenkins → Credentials
- Select the appropriate scope (global or folder)
- Click Add Credentials
- Kind: Secret text
- Secret: your API token
- ID:
PURPLEMET_API_TOKEN - Click Create
The Jenkins agent must have:
-
curl(for CLI installation) - Outbound HTTPS access to
api.purplemet.com - Docker (if using the Docker-based approach)
@Library('purplemet') _
pipeline {
agent any
stages {
stage('Security Analysis') {
steps {
purplemetAnalyze(
url: 'https://your-app.example.com',
failSeverity: 'high'
)
}
}
}
}Uses the official Jenkins shared library that provides purplemetAnalyze and purplemetInstall steps.
Setup:
- Make sure the Pipeline: Shared Groovy Libraries plugin is installed (Manage Jenkins → Plugins → Available plugins). It is included in the default suggested plugins.
- Go to Manage Jenkins → System and scroll to Global Trusted Pipeline Libraries (not Untrusted — the Purplemet library runs trusted Groovy).
- Click Add and configure the library:
-
Name:
purplemet -
Default version:
main - Retrieval method: Modern SCM
- Source Code Management: Git
-
Project Repository:
https://dev.purplemet.com/purplemet/cli.git(add Git credentials if the repo is private) or the public mirrorhttps://github.com/Purplemet/cli.git(no credentials needed) -
Library Path (optional):
integrations/jenkins
-
Name:
- Check Load implicitly for automatic availability (optional)
- Click Save.
Note: if you see inline HTTP 403 "No valid crumb was included in the request" errors next to the Name / Default version fields, it is a cosmetic UI validation issue — the Save itself still works. If Save also fails with 403, set Jenkins Location → Jenkins URL to match the URL in your browser, hard-reload the page (Cmd/Ctrl+Shift+R) and try again.
Usage:
@Library('purplemet') _
pipeline {
agent any
stages {
stage('Security Analysis') {
steps {
purplemetAnalyze(
url: 'https://your-app.example.com',
failSeverity: 'high'
)
}
}
}
}Uses the official Docker image. Requires Docker on the agent.
pipeline {
agent {
docker {
image 'ppmsupport/purplemet-cli:latest'
args '--entrypoint='
}
}
stages {
stage('Security Analysis') {
steps {
withCredentials([string(credentialsId: 'PURPLEMET_API_TOKEN', variable: 'PURPLEMET_API_TOKEN')]) {
sh 'purplemet-cli analyze https://your-app.com --format json --fail-on-severity high --output-file purplemet-report.json'
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'purplemet-report.json', allowEmptyArchive: true
}
}
}
args '--entrypoint='neutralises the image's built-in entrypoint so Jenkins can run itscatkeep-alive command — without it the container exits immediately withThe container started but didn't run the expected command.Apple Silicon / ARM runners: the
ppmsupport/purplemet-cliimage is currently published forlinux/amd64andlinux/arm64. If your Jenkins controller runs under QEMU emulation and Docker cannot resolve the right variant automatically, pin it with anenvironment { DOCKER_DEFAULT_PLATFORM = 'linux/amd64' }block at the pipeline level and add--platform linux/amd64toargs.
Downloads and installs the CLI binary directly. On agents without sudo and without write access to /usr/local/bin (typical Jenkins containers), the installer falls back to ~/.local/bin — hence the PATH override so subsequent sh steps find the binary.
pipeline {
agent any
environment {
PATH = "${env.HOME}/.local/bin:${env.PATH}"
}
stages {
stage('Security Analysis') {
steps {
sh 'curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh'
withCredentials([string(credentialsId: 'PURPLEMET_API_TOKEN', variable: 'PURPLEMET_API_TOKEN')]) {
sh 'purplemet-cli analyze https://your-app.com --format json --fail-on-severity high --output-file purplemet-report.json'
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'purplemet-report.json', allowEmptyArchive: true
}
}
}Core parameters below; see the full list of gates (severity, CVE, SSL, HTTP, etc.) in the library README.
| Parameter | Required | Default | Description |
|---|---|---|---|
url |
Yes | — | URL of the web application to analyze |
token |
No | PURPLEMET_API_TOKEN |
Jenkins credential ID for the API token |
baseUrl |
No | — | API base URL override (e.g. https://api.dev.purplemet.com) |
failSeverity |
No | high |
Severity threshold: critical, high, medium, low, info
|
timeout |
No | 1800000 |
Polling timeout in milliseconds (30 min, 0 = unlimited) |
format |
No | json |
Output format: json, human, sarif, html
|
version |
No | latest |
CLI version to install |
Important: with the shared library, security gates must be passed as named parameters to
purplemetAnalyze()(e.g.failOnKev: true,failOnCertExpiry: '30'). Settingenvironment { PURPLEMET_* = ... }has no effect — the library builds its own environment from the named arguments and overrides anything set outside.
tokendefaults to the credential IDPURPLEMET_API_TOKEN. Override only when your credential has a different ID:purplemetAnalyze( url: 'https://your-app.example.com', token: 'MY_CUSTOM_CREDENTIAL_ID' )
When using the Docker or binary methods (not the shared library), all standard Purplemet environment variables are supported:
| Variable | Required | Default | Description |
|---|---|---|---|
PURPLEMET_API_TOKEN |
Yes | — | API authentication token |
PURPLEMET_FAIL_SEVERITY |
No | — | Severity threshold |
PURPLEMET_WAIT_TIMEOUT |
No | 0 |
Polling timeout (ms) |
PURPLEMET_BASE_URL |
No | — | API base URL override |
All security gates are exposed as both env vars and CLI flags — use these with the Docker or binary methods. With the shared library, pass them as named parameters instead (see above).
| Variable | Default | Description |
|---|---|---|
PURPLEMET_FAIL_SEVERITY |
— | Severity threshold |
PURPLEMET_FAIL_RATING |
— | Rating threshold: A–F
|
PURPLEMET_FAIL_CVSS |
0 |
CVSS score threshold (e.g. 9.0) |
PURPLEMET_FAIL_ON_EOL |
false |
Block on end-of-life components |
PURPLEMET_FAIL_ON_SSL |
false |
Block on SSL/TLS issues |
PURPLEMET_FAIL_ON_CERT |
false |
Block on certificate issues |
PURPLEMET_FAIL_ON_HEADERS |
false |
Block on HTTP security header issues |
PURPLEMET_FAIL_ON_COOKIES |
false |
Block on insecure cookies |
PURPLEMET_FAIL_ON_UNSAFE |
false |
Block on unsafe components |
PURPLEMET_FAIL_ON_KEV |
false |
Block on CISA Known Exploited Vulnerabilities |
PURPLEMET_FAIL_ON_EPSS |
0 |
EPSS score threshold (e.g. 0.75) |
PURPLEMET_FAIL_ON_ACTIVE_EXPLOITS |
false |
Block on actively exploited vulnerabilities |
PURPLEMET_FAIL_ON_OSSF_SCORE |
0 |
Min OpenSSF Scorecard score |
PURPLEMET_FAIL_ON_CERT_EXPIRY |
0 |
Block if certificate expires within N days |
PURPLEMET_FAIL_ON_ISSUE_COUNT |
0 |
Block if total issues >= threshold |
PURPLEMET_REQUIRE_WAF |
false |
Block if no WAF detected |
PURPLEMET_FAIL_ON_SENSITIVE_SERVICES |
false |
Block if sensitive services exposed |
PURPLEMET_EXCLUDE_TECH |
— | Block if specified technologies detected |
Multiple gates can be combined — the analysis fails (exit code 1) if any gate triggers.
With the shared library, all gates are passed as named parameters to purplemetAnalyze(...). Do not use environment { PURPLEMET_* } blocks — the library builds its own environment from the named parameters and will override anything set outside.
@Library('purplemet') _
pipeline {
agent any
stages {
stage('Security Analysis') {
steps {
purplemetAnalyze(
url: 'https://your-app.example.com',
failSeverity: 'high',
failOnEol: true,
failOnKev: true,
failOnSsl: true,
requireWaf: true,
failOnCertExpiry: '30'
)
}
}
}
}pipeline {
agent any
environment {
PATH = "${env.HOME}/.local/bin:${env.PATH}"
}
stages {
stage('Security Analysis') {
steps {
sh 'curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh'
withCredentials([string(credentialsId: 'PURPLEMET_API_TOKEN', variable: 'PURPLEMET_API_TOKEN')]) {
sh '''
purplemet-cli analyze https://your-app.com \
--format json \
--fail-on-severity high \
--fail-on-cvss 9.0 \
--fail-on-eol \
--fail-on-kev \
--require-waf \
--fail-on-cert-expiry 30 \
--output-file purplemet-report.json
'''
}
}
}
}
}@Library('purplemet') _
pipeline {
agent any
stages {
stage('Security Analysis') {
steps {
purplemetAnalyze(
url: 'https://your-app.example.com',
failSeverity: 'high'
)
}
}
}
}@Library('purplemet') _
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make build'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
stage('Deploy to Staging') {
steps {
sh './deploy.sh staging'
}
}
stage('Security Analysis') {
steps {
purplemetAnalyze(
url: 'https://staging.example.com',
failSeverity: 'high',
timeout: '600000'
)
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
input {
message 'Deploy to production?'
}
steps {
sh './deploy.sh production'
}
}
}
}@Library('purplemet') _
node {
stage('Security Analysis') {
purplemetAnalyze(
url: 'https://your-app.example.com',
failSeverity: 'medium',
timeout: '600000'
)
}
}@Library('purplemet') _
pipeline {
agent any
stages {
stage('Security Analyses') {
parallel {
stage('Analyze App 1') {
steps {
purplemetAnalyze(
url: 'https://app1.example.com',
failSeverity: 'high'
)
}
}
stage('Analyze App 2') {
steps {
purplemetAnalyze(
url: 'https://app2.example.com',
failSeverity: 'high'
)
}
}
}
}
}
}@Library('purplemet') _
pipeline {
agent any
triggers {
cron('H 6 * * 1') // Every Monday at ~6:00
}
stages {
stage('Nightly Analysis') {
steps {
purplemetAnalyze(
url: 'https://production.example.com',
failSeverity: 'medium',
timeout: '600000'
)
}
}
}
}pipeline {
agent any
environment {
PATH = "${env.HOME}/.local/bin:${env.PATH}"
}
stages {
stage('Security Analysis') {
steps {
sh 'curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh'
withCredentials([string(credentialsId: 'PURPLEMET_API_TOKEN', variable: 'PURPLEMET_API_TOKEN')]) {
script {
def exitCode = sh(
script: 'purplemet-cli analyze https://your-app.com --format json --fail-on-severity high --output-file purplemet-report.json',
returnStatus: true
)
if (exitCode == 0) {
echo 'Analysis passed — no issues above threshold'
} else if (exitCode == 1) {
currentBuild.result = 'UNSTABLE'
echo 'WARNING: vulnerabilities found above threshold'
} else {
error "Analysis failed with exit code ${exitCode}"
}
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'purplemet-report.json', allowEmptyArchive: true
}
}
}@Library('purplemet') _
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make build'
}
}
stage('Deploy Staging') {
steps {
sh './deploy.sh staging'
}
}
stage('Security Analysis') {
steps {
purplemetAnalyze(
url: 'https://staging.example.com',
failSeverity: 'high',
timeout: '600000',
failOnEol: true,
failOnKev: true,
failOnSsl: true,
failOnCert: true,
failOnCertExpiry: '30',
requireWaf: true
)
}
}
stage('Deploy Production') {
when { branch 'main' }
input { message 'Deploy to production?' }
steps {
sh './deploy.sh production'
}
}
}
post {
always {
archiveArtifacts artifacts: 'purplemet-report.json', allowEmptyArchive: true
}
}
}| Code | Meaning | Jenkins Build Status |
|---|---|---|
| 0 | No issues above threshold | SUCCESS |
| 1 | Issues found above threshold | UNSTABLE (shared library) or configurable |
| 2 | Analysis error on Purplemet | FAILURE |
| 3 | Timeout exceeded | FAILURE |
| 4 | Network or API error | FAILURE |
| 5 | Usage error (bad arguments) | FAILURE |
| 6 | API contract error | FAILURE |
Ratings (A–F) and severity levels (CRITICAL/HIGH/MEDIUM/LOW/INFO) are computed and defined by the Purplemet platform. See the official Purplemet documentation for authoritative definitions.
The analysis report is saved as purplemet-report.json and archived as a Jenkins build artifact. Download it from the build page.
purplemetAnalyze(
url: 'https://your-app.example.com',
version: 'v1.2.0'
)pipeline {
agent {
docker {
image 'ppmsupport/purplemet-cli:latest'
args '--entrypoint= -e PURPLEMET_API_TOKEN'
}
}
stages {
stage('Analysis') {
steps {
withCredentials([string(credentialsId: 'PURPLEMET_API_TOKEN', variable: 'PURPLEMET_API_TOKEN')]) {
sh 'purplemet-cli analyze https://your-app.com --format json --output-file purplemet-report.json'
}
}
}
}
}The shared library auto-derives the report filename from format (.html here) and archives it — the report is available from the build's Artifacts section.
@Library('purplemet') _
pipeline {
agent any
stages {
stage('Security Analysis') {
steps {
purplemetAnalyze(
url: 'https://your-app.com',
format: 'html',
failSeverity: 'high'
)
}
}
}
}To expose the report as a clickable link in the build sidebar (instead of a download), install the HTML Publisher Plugin and add a post block:
post {
always {
publishHTML(target: [
allowMissing: true,
reportDir: '.',
reportFiles: 'purplemet-report.html',
reportName: 'Purplemet Security Report'
])
}
}The Purplemet HTML report bundles all styling inline. Jenkins ships with a strict Content Security Policy that strips inline CSS/JS from any user-served HTML — including reports shown through HTML Publisher. The result: the report renders, but looks plain/unstyled.
You have three ways to allow the report's styles through, ordered from least to most permissive:
Add a Java system property on Jenkins startup. Edit your Jenkins service/launcher and add:
-Dhudson.model.DirectoryBrowserSupport.CSP="sandbox allow-scripts; default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"
Depending on how Jenkins is installed:
-
systemd (
/etc/systemd/system/jenkins.service.d/override.conf):Then:[Service] Environment="JAVA_OPTS=-Dhudson.model.DirectoryBrowserSupport.CSP=sandbox allow-scripts; default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"sudo systemctl daemon-reload && sudo systemctl restart jenkins -
Docker (
jenkins/jenkins:lts): add theJAVA_OPTSenv var when launching the container. -
/etc/default/jenkins(debian-style installs): append to theJAVA_ARGSvariable.
Go to Manage Jenkins → Script Console and run:
System.setProperty("hudson.model.DirectoryBrowserSupport.CSP",
"sandbox allow-scripts; default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;")This is the right option for quick testing. For production use option 1 so the setting survives restarts.
System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")This exposes Jenkins to CVE-2017-2608 — use only on isolated/dev Jenkins instances.
After reload, open the published report: colors, rating chart, and severity badges should now render. If not, check your browser console for blocked CSP sources and add the corresponding directive. The archived .html artifact downloaded from the build page always renders correctly because it's served from the user's filesystem, not through Jenkins.
The credential is missing or inaccessible.
Fix: Add a Secret text credential:
Manage Jenkins → Credentials → Add Credentials → Secret text → ID: PURPLEMET_API_TOKEN.
The API token is invalid or expired.
Fix:
- Verify:
purplemet-cli auth check - Create a new token at cloud.purplemet.com
- Update the Jenkins credential
The Jenkins agent doesn't have curl installed.
Fix: Either:
- Install curl on the agent:
apt-get install -y curlorapk add curl - Use a Docker agent with curl pre-installed
- Use the Docker image method (no curl needed)
Fix: Increase the timeout parameter:
purplemetAnalyze(
url: 'https://your-app.com',
timeout: '600000' // 10 minutes
)The shared library already marks exit code 1 as UNSTABLE by default. If using the CLI directly:
script {
def exitCode = sh(script: 'purplemet-cli analyze ... --format json', returnStatus: true)
if (exitCode == 1) {
currentBuild.result = 'UNSTABLE'
} else if (exitCode > 1) {
error "Analysis failed (exit code ${exitCode})"
}
}The shared library is not configured.
Fix: Add the library in Manage Jenkins → System → Global Trusted Pipeline Libraries. See Installation Methods.
Use parallel stages (see Multi-Site Analysis example).
- Console output: Summary in the build log
-
Build artifacts: Download
purplemet-report.json - Build status: SUCCESS / UNSTABLE / FAILURE reflects analysis results
- Purplemet dashboard: cloud.purplemet.com for detailed results