Skip to content

jenkins

Purplemet CI edited this page Apr 28, 2026 · 2 revisions

Jenkins Integration

This guide covers all aspects of integrating Purplemet security analyses into Jenkins pipelines.

Table of Contents

Prerequisites

1. Purplemet API Token

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.

2. Jenkins Credential

Add the token as a Secret text credential:

  1. Go to Manage JenkinsCredentials
  2. Select the appropriate scope (global or folder)
  3. Click Add Credentials
  4. Kind: Secret text
  5. Secret: your API token
  6. ID: PURPLEMET_API_TOKEN
  7. Click Create

3. Agent Requirements

The Jenkins agent must have:

  • curl (for CLI installation)
  • Outbound HTTPS access to api.purplemet.com
  • Docker (if using the Docker-based approach)

Quick Start

@Library('purplemet') _

pipeline {
    agent any
    stages {
        stage('Security Analysis') {
            steps {
                purplemetAnalyze(
                    url: 'https://your-app.example.com',
                    failSeverity: 'high'
                )
            }
        }
    }
}

Installation Methods

Method 1: Shared Library (recommended)

Uses the official Jenkins shared library that provides purplemetAnalyze and purplemetInstall steps.

Setup:

  1. Make sure the Pipeline: Shared Groovy Libraries plugin is installed (Manage JenkinsPluginsAvailable plugins). It is included in the default suggested plugins.
  2. Go to Manage JenkinsSystem and scroll to Global Trusted Pipeline Libraries (not Untrusted — the Purplemet library runs trusted Groovy).
  3. 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 mirror https://github.com/Purplemet/cli.git (no credentials needed)
    • Library Path (optional): integrations/jenkins
  4. Check Load implicitly for automatic availability (optional)
  5. 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'
                )
            }
        }
    }
}

Method 2: Docker Image

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 its cat keep-alive command — without it the container exits immediately with The container started but didn't run the expected command.

Apple Silicon / ARM runners: the ppmsupport/purplemet-cli image is currently published for linux/amd64 and linux/arm64. If your Jenkins controller runs under QEMU emulation and Docker cannot resolve the right variant automatically, pin it with an environment { DOCKER_DEFAULT_PLATFORM = 'linux/amd64' } block at the pipeline level and add --platform linux/amd64 to args.

Method 3: Binary Installation

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
        }
    }
}

Parameters

Shared Library Parameters (purplemetAnalyze)

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'). Setting environment { PURPLEMET_* = ... } has no effect — the library builds its own environment from the named arguments and overrides anything set outside.

token defaults to the credential ID PURPLEMET_API_TOKEN. Override only when your credential has a different ID:

purplemetAnalyze(
    url: 'https://your-app.example.com',
    token: 'MY_CUSTOM_CREDENTIAL_ID'
)

Environment Variables

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

Security Gate Variables

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: AF
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

Security Gates

Multiple gates can be combined — the analysis fails (exit code 1) if any gate triggers.

Example: Strict Policy with Shared Library

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'
                )
            }
        }
    }
}

Example: Advanced Gates with CLI Flags

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
                    '''
                }
            }
        }
    }
}

Complete Pipeline Examples

Basic: Analysis on Every Build

@Library('purplemet') _

pipeline {
    agent any
    stages {
        stage('Security Analysis') {
            steps {
                purplemetAnalyze(
                    url: 'https://your-app.example.com',
                    failSeverity: 'high'
                )
            }
        }
    }
}

Build → Deploy → Analysis Pipeline

@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'
            }
        }
    }
}

Scripted Pipeline

@Library('purplemet') _

node {
    stage('Security Analysis') {
        purplemetAnalyze(
            url: 'https://your-app.example.com',
            failSeverity: 'medium',
            timeout: '600000'
        )
    }
}

Multi-Site Analysis (Parallel)

@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'
                        )
                    }
                }
            }
        }
    }
}

Scheduled Nightly Analysis

@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'
                )
            }
        }
    }
}

Custom Exit Code Handling

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
        }
    }
}

Full Pipeline with Strict Gates

@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
        }
    }
}

Results and Exit Codes

Exit Codes

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

Security Rating and Severity Levels

Ratings (AF) and severity levels (CRITICAL/HIGH/MEDIUM/LOW/INFO) are computed and defined by the Purplemet platform. See the official Purplemet documentation for authoritative definitions.

Report Artifact

The analysis report is saved as purplemet-report.json and archived as a Jenkins build artifact. Download it from the build page.

Advanced Usage

Pin a Specific CLI Version

purplemetAnalyze(
    url: 'https://your-app.example.com',
    version: 'v1.2.0'
)

Use Docker Agent for Isolation

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'
                }
            }
        }
    }
}

Generate HTML Report

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'
                )
            }
        }
    }
}

Optional: render the report inline in the Jenkins UI

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'
            ])
        }
    }

Enabling CSS in the Jenkins-rendered 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:

1. Relax the policy permanently (recommended)

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):
    [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:;"
    
    Then: sudo systemctl daemon-reload && sudo systemctl restart jenkins
  • Docker (jenkins/jenkins:lts): add the JAVA_OPTS env var when launching the container.
  • /etc/default/jenkins (debian-style installs): append to the JAVA_ARGS variable.
2. Relax the policy at runtime (no restart, but lost on Jenkins restart)

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.

3. Disable the policy entirely (not recommended)
System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

This exposes Jenkins to CVE-2017-2608 — use only on isolated/dev Jenkins instances.

Verifying the change

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.

FAQ / Common Errors

PURPLEMET_API_TOKEN is not set

The credential is missing or inaccessible.

Fix: Add a Secret text credential: Manage Jenkins → Credentials → Add Credentials → Secret text → ID: PURPLEMET_API_TOKEN.


"Access is not authorized without a valid session"

The API token is invalid or expired.

Fix:

  1. Verify: purplemet-cli auth check
  2. Create a new token at cloud.purplemet.com
  3. Update the Jenkins credential

curl: command not found

The Jenkins agent doesn't have curl installed.

Fix: Either:

  • Install curl on the agent: apt-get install -y curl or apk add curl
  • Use a Docker agent with curl pre-installed
  • Use the Docker image method (no curl needed)

Analysis times out (exit code 3)

Fix: Increase the timeout parameter:

purplemetAnalyze(
    url: 'https://your-app.com',
    timeout: '600000'  // 10 minutes
)

Build fails with exit code 1 but I want UNSTABLE

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})"
    }
}

"Library 'purplemet' not found"

The shared library is not configured.

Fix: Add the library in Manage Jenkins → System → Global Trusted Pipeline Libraries. See Installation Methods.


How do I analyze multiple sites?

Use parallel stages (see Multi-Site Analysis example).


Where can I see the results?

  1. Console output: Summary in the build log
  2. Build artifacts: Download purplemet-report.json
  3. Build status: SUCCESS / UNSTABLE / FAILURE reflects analysis results
  4. Purplemet dashboard: cloud.purplemet.com for detailed results

Clone this wiki locally