Skip to content

gitlab ci

Purplemet CI edited this page Jun 2, 2026 · 4 revisions

GitLab CI/CD Integration

This guide covers all aspects of integrating Purplemet security analyses into GitLab CI/CD 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. CI/CD Variable

Add the token as a masked CI/CD variable:

  1. Go to your project on GitLab
  2. Navigate to SettingsCI/CDVariables
  3. Click Add variable
  4. Key: PURPLEMET_API_TOKEN
  5. Value: your API token
  6. Check Mask variable — hides the value in job logs
  7. Check Protect variable — only available on protected branches/tags (optional)
  8. Click Add variable

Group-level variable: For multi-project usage, add the variable at the group level (Group → Settings → CI/CD → Variables).

3. Network Access

The GitLab Runner must be able to reach:

  • api.purplemet.com (HTTPS, port 443) — Purplemet API
  • registry.hub.docker.com — Docker Hub (for the CLI image)

If behind a proxy, set HTTP_PROXY / HTTPS_PROXY in your runner configuration.

Quick Start

Add to your .gitlab-ci.yml:

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

purplemet:
  extends: .purplemet-analyze
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"

This:

  • Includes the official Purplemet template
  • Runs a security analysis with JSON output
  • Fails on high or critical issues (default threshold)
  • Saves the report as an artifact

Installation Methods

Method 1: Include Template (recommended)

Uses the official reusable template. Handles installation, execution, and artifact management.

From project (private GitLab instances):

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1.0.0'
    file: '/purplemet-analyze.gitlab-ci.yml'

purplemet:
  extends: .purplemet-analyze
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"

From remote URL:

include:
  - remote: 'https://raw.githubusercontent.com/Purplemet/cli/v1.0.0/integrations/gitlab/purplemet-analyze.gitlab-ci.yml'

purplemet:
  extends: .purplemet-analyze
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"

Method 2: Docker Image (no template)

Uses the official Docker image directly. Provides more control over execution.

purplemet-analysis:
  image: ppmsupport/purplemet-cli:latest
  stage: test
  script:
    - purplemet-cli analyze "$PURPLEMET_TARGET_URL"
      --json --fail-on-severity high
      | tee purplemet-report.json
  artifacts:
    paths:
      - purplemet-report.json
    when: always
    expire_in: 30 days

Method 3: Binary Installation

Downloads the CLI binary. Useful with custom images or when Docker-in-Docker is not available.

purplemet-analysis:
  image: alpine:3.19
  stage: test
  before_script:
    - apk add --no-cache curl
    - curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh
  script:
    - purplemet-cli analyze "$PURPLEMET_TARGET_URL"
      --json --fail-on-severity high
      | tee purplemet-report.json
  artifacts:
    paths:
      - purplemet-report.json
    when: always

Parameters

CI/CD Variables

Variable Required Default Description
PURPLEMET_API_TOKEN Yes API authentication token (masked CI/CD variable)
PURPLEMET_TARGET_URL Yes URL of the web application to analyze
PURPLEMET_FAIL_SEVERITY No high Severity threshold: critical, high, medium, low, info
PURPLEMET_WAIT_TIMEOUT No 1800000 Polling timeout in milliseconds (30 min, 0 = unlimited)
PURPLEMET_CLI_VERSION No latest CLI Docker image tag (e.g. v1.0.0)
PURPLEMET_FORMAT No json Output format: json, human, sarif, html
PURPLEMET_NO_CREATE No false Do not auto-create the site if the URL is not found
PURPLEMET_BASE_URL No API base URL override

Security Gate Variables

All security gates are configured via environment variables:

Variable Default Description
PURPLEMET_FAIL_SEVERITY high 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 (e.g. 5.0)
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 are exposed
PURPLEMET_EXCLUDE_TECH Block if specified technologies detected (comma-separated)

Available Templates

Two template files are shipped, one per runner executor type:

  • purplemet-analyze.gitlab-ci.yml — for Docker-executor runners (default in GitLab SaaS). Uses the ppmsupport/purplemet-cli image.
  • purplemet-analyze-shell.gitlab-ci.yml — for shell / SSH / VM-executor runners. Installs the binary from GitHub at job start. Requires curl + bash on the runner.
Template File Executor Behavior on exit code 1
.purplemet-analyze purplemet-analyze.gitlab-ci.yml docker Warning (pipeline continues)
.purplemet-analyze-blocking purplemet-analyze.gitlab-ci.yml docker Blocks pipeline
.purplemet-analyze-sarif purplemet-analyze.gitlab-ci.yml docker Warning — JSON + SARIF reports
.purplemet-analyze-shell purplemet-analyze-shell.gitlab-ci.yml shell / ssh Warning
.purplemet-analyze-shell-blocking purplemet-analyze-shell.gitlab-ci.yml shell / ssh Blocks pipeline

Example — shell runner:

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1.0.0'
    file: '/purplemet-analyze-shell.gitlab-ci.yml'

security_scan:
  extends: .purplemet-analyze-shell
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"
    PURPLEMET_FAIL_SEVERITY: "high"

Outputs & Artifacts

Artifacts

File Description
purplemet-report.json Full analysis results in JSON
purplemet-report.sarif SARIF 2.1.0 report (.purplemet-analyze-sarif template only)
purplemet-report.env Dotenv artifact with key metrics
purplemet-stderr.log CLI stderr output (warnings/errors)

Dotenv Variables (available in downstream jobs)

Variable Description
PURPLEMET_EXIT_CODE CLI exit code
PURPLEMET_RATING Security rating (A–F)
PURPLEMET_ISSUES Total number of issues
PURPLEMET_TARGET Analyzed URL

Use these in downstream jobs:

notify:
  stage: deploy
  needs:
    - purplemet
  script:
    - echo "Rating: $PURPLEMET_RATING"
    - echo "Issues: $PURPLEMET_ISSUES"

Security Gates

Gates let you define precise pass/fail criteria for your pipeline. Multiple gates can be combined — the analysis fails (exit code 1) if any gate triggers.

Example: Strict Security Policy

purplemet:
  extends: .purplemet-analyze-blocking
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"
    PURPLEMET_FAIL_SEVERITY: "high"
    PURPLEMET_FAIL_ON_EOL: "true"
    PURPLEMET_FAIL_ON_KEV: "true"
    PURPLEMET_FAIL_ON_SSL: "true"
    PURPLEMET_FAIL_ON_CERT: "true"
    PURPLEMET_REQUIRE_WAF: "true"
    PURPLEMET_FAIL_ON_CERT_EXPIRY: "30"

Example: CVSS + EPSS Thresholds

purplemet:
  extends: .purplemet-analyze-blocking
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"
    PURPLEMET_FAIL_CVSS: "9.0"
    PURPLEMET_FAIL_ON_EPSS: "0.75"
    PURPLEMET_FAIL_ON_ACTIVE_EXPLOITS: "true"

Example: Technology Policy

purplemet:
  extends: .purplemet-analyze-blocking
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"
    PURPLEMET_FAIL_ON_EOL: "true"
    PURPLEMET_EXCLUDE_TECH: "php,java"
    PURPLEMET_FAIL_ON_OSSF_SCORE: "5.0"

Complete Pipeline Examples

Basic: Analysis on Every Push

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

purplemet:
  extends: .purplemet-analyze
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"
    PURPLEMET_FAIL_SEVERITY: "high"

Build → Deploy → Analysis Pipeline

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

stages:
  - build
  - deploy
  - test

build:
  stage: build
  script:
    - docker build -t my-app .

deploy:
  stage: deploy
  script:
    - ./deploy.sh staging
  environment:
    name: staging

purplemet:
  extends: .purplemet-analyze-blocking
  stage: test
  variables:
    PURPLEMET_TARGET_URL: "https://staging.example.com"
    PURPLEMET_FAIL_SEVERITY: "high"
  needs:
    - deploy

Scheduled Nightly Analysis

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

purplemet:nightly:
  extends: .purplemet-analyze
  variables:
    PURPLEMET_TARGET_URL: "https://production.example.com"
    PURPLEMET_FAIL_SEVERITY: "medium"
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"

Configure the schedule in CI/CDSchedulesNew schedule.

With GitLab Security Dashboard (SARIF)

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

purplemet:
  extends: .purplemet-analyze-sarif
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"

The SARIF report is registered as a DAST artifact and appears in the GitLab Security Dashboard (requires GitLab Ultimate).

Multi-Environment Analysis

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

.purplemet-base:
  extends: .purplemet-analyze-blocking
  variables:
    PURPLEMET_FAIL_SEVERITY: "high"
    PURPLEMET_FAIL_ON_KEV: "true"

purplemet:staging:
  extends: .purplemet-base
  variables:
    PURPLEMET_TARGET_URL: "https://staging.example.com"
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

purplemet:production:
  extends: .purplemet-base
  variables:
    PURPLEMET_TARGET_URL: "https://app.example.com"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Use Analysis Results in Downstream Jobs

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

stages:
  - test
  - notify

purplemet:
  extends: .purplemet-analyze
  stage: test
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"

notify:
  stage: notify
  needs:
    - purplemet
  script:
    - echo "Analysis rating: $PURPLEMET_RATING"
    - echo "Issues found: $PURPLEMET_ISSUES"
    - |
      if [ "$PURPLEMET_EXIT_CODE" = "1" ]; then
        echo "Security issues detected — review purplemet-report.json"
      fi
  rules:
    - if: $CI_PIPELINE_SOURCE != "schedule"

Warning Mode (Non-blocking)

The default .purplemet-analyze template already treats exit code 1 as a warning. For fully non-blocking:

purplemet:
  extends: .purplemet-analyze
  allow_failure: true
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"

Full Pipeline with Strict Gates

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'
    file: '/purplemet-analyze.gitlab-ci.yml'

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - docker build -t my-app .

purplemet:
  extends: .purplemet-analyze-blocking
  stage: test
  variables:
    PURPLEMET_TARGET_URL: "https://staging.example.com"
    PURPLEMET_FAIL_SEVERITY: "high"
    PURPLEMET_FAIL_ON_EOL: "true"
    PURPLEMET_FAIL_ON_KEV: "true"
    PURPLEMET_FAIL_ON_SSL: "true"
    PURPLEMET_FAIL_ON_CERT_EXPIRY: "30"
    PURPLEMET_REQUIRE_WAF: "true"
  needs:
    - build

deploy:
  stage: deploy
  script:
    - ./deploy.sh production
  needs:
    - purplemet
  environment:
    name: production
  when: manual

Results and Exit Codes

Exit Codes

Code Meaning Pipeline Behavior
0 No issues above threshold Pipeline passes
1 Issues found above threshold Warning (.purplemet-analyze) or Fail (.purplemet-analyze-blocking)
2 Analysis error on Purplemet Pipeline fails
3 Timeout exceeded Pipeline fails
4 Network or API error Pipeline fails
5 Usage error (bad config) Pipeline fails
6 API contract error Pipeline fails

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.

Advanced Usage

Pin a Specific Template Version

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1.0.0'                # exact version
    file: '/purplemet-analyze.gitlab-ci.yml'

Or use a major version tag for automatic updates:

include:
  - project: 'purplemet/integrations/gitlab-ci-templates'
    ref: 'v1'                    # latest v1.x.x
    file: '/purplemet-analyze.gitlab-ci.yml'

Custom Stage

By default, the template runs in the test stage. Override:

purplemet:
  extends: .purplemet-analyze
  stage: security
  variables:
    PURPLEMET_TARGET_URL: "https://your-app.example.com"

Save Report as Pages Artifact

pages:
  stage: deploy
  needs:
    - purplemet
  script:
    - mkdir -p public
    - cp purplemet-report.json public/
  artifacts:
    paths:
      - public

Generate HTML Report

purplemet-html:
  image: ppmsupport/purplemet-cli:latest
  stage: test
  script:
    - purplemet-cli analyze "$PURPLEMET_TARGET_URL"
      --format html --output-file purplemet-report.html
      --fail-on-severity high || true
  artifacts:
    paths:
      - purplemet-report.html
    when: always

FAQ / Common Errors

PURPLEMET_API_TOKEN is not set

The CI/CD variable is missing or inaccessible.

Fix: Add the token as a masked CI/CD variable: Settings → CI/CD → Variables → Add variable → PURPLEMET_API_TOKEN.

For protected variables, ensure the pipeline runs on a protected branch or tag.


PURPLEMET_TARGET_URL is not set

The target URL variable is missing.

Fix: Set PURPLEMET_TARGET_URL in the variables: block of your job definition.


"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 CI/CD variable

Analysis times out (exit code 3)

Fix: Increase PURPLEMET_WAIT_TIMEOUT (default is 1800000 / 30 min):

variables:
  PURPLEMET_WAIT_TIMEOUT: "3600000"  # 60 minutes

Or set to 0 for no limit.


Pipeline fails with exit code 1 but I want it to continue

Exit code 1 means vulnerabilities were found above the threshold.

Option 1: Use .purplemet-analyze template (default behavior — exit code 1 is a warning).

Option 2: Add allow_failure:

purplemet:
  extends: .purplemet-analyze-blocking
  allow_failure:
    exit_codes: [1]

Option 3: Full non-blocking:

purplemet:
  extends: .purplemet-analyze
  allow_failure: true

Protected variable not available in pipeline

Fix: Either:

  • Run the pipeline on a protected branch/tag
  • Uncheck Protect variable on the CI/CD variable

Network error (exit code 4)

The runner cannot reach the Purplemet API.

Fix:

  • Check that the runner has outbound HTTPS access to api.purplemet.com
  • If behind a proxy, configure HTTP_PROXY / HTTPS_PROXY in the runner config
  • If using a custom API endpoint, set PURPLEMET_BASE_URL

"api http 429: Too Many Requests"

Rate limit reached. The CLI automatically retries with the Retry-After delay.

Fix: If persistent, reduce analysis frequency or contact Purplemet support.


How do I use the template from a different GitLab instance?

Use include:remote with the raw URL:

include:
  - remote: 'https://raw.githubusercontent.com/Purplemet/cli/v1.0.0/integrations/gitlab/purplemet-analyze.gitlab-ci.yml'

How do I analyze multiple sites?

Create multiple jobs extending the template:

purplemet:app1:
  extends: .purplemet-analyze
  variables:
    PURPLEMET_TARGET_URL: "https://app1.example.com"

purplemet:app2:
  extends: .purplemet-analyze
  variables:
    PURPLEMET_TARGET_URL: "https://app2.example.com"

Where can I see the results?

  1. Job log: Human-readable summary in the CI job output
  2. Artifacts: Download purplemet-report.json from the job page
  3. Downstream jobs: Access PURPLEMET_RATING, PURPLEMET_ISSUES via dotenv variables
  4. Security Dashboard (SARIF template + GitLab Ultimate): Findings in the security dashboard
  5. Purplemet dashboard: cloud.purplemet.com for detailed results

Clone this wiki locally