-
Notifications
You must be signed in to change notification settings - Fork 0
gitlab ci
This guide covers all aspects of integrating Purplemet security analyses into GitLab CI/CD pipelines.
- Prerequisites
- Quick Start
- Installation Methods
- Parameters
- Outputs & Artifacts
- 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 masked CI/CD variable:
- Go to your project on GitLab
- Navigate to Settings → CI/CD → Variables
- Click Add variable
- Key:
PURPLEMET_API_TOKEN - Value: your API token
- Check Mask variable — hides the value in job logs
- Check Protect variable — only available on protected branches/tags (optional)
- Click Add variable
Group-level variable: For multi-project usage, add the variable at the group level (Group → Settings → CI/CD → Variables).
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.
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
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"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 daysDownloads 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| 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 |
All security gates are configured via environment variables:
| Variable | Default | Description |
|---|---|---|
PURPLEMET_FAIL_SEVERITY |
high |
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 (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) |
Two template files are shipped, one per runner executor type:
-
purplemet-analyze.gitlab-ci.yml— for Docker-executor runners (default in GitLab SaaS). Uses theppmsupport/purplemet-cliimage. -
purplemet-analyze-shell.gitlab-ci.yml— for shell / SSH / VM-executor runners. Installs the binary from GitHub at job start. Requirescurl+bashon 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"| 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) |
| 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"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.
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"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"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"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"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:
- deployinclude:
- 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/CD → Schedules → New schedule.
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).
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"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"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"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| 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 |
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.
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'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"pages:
stage: deploy
needs:
- purplemet
script:
- mkdir -p public
- cp purplemet-report.json public/
artifacts:
paths:
- publicpurplemet-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: alwaysThe 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.
The target URL variable is missing.
Fix: Set PURPLEMET_TARGET_URL in the variables: block of your job definition.
The API token is invalid or expired.
Fix:
- Verify:
purplemet-cli auth check - Create a new token at cloud.purplemet.com
- Update the CI/CD variable
Fix: Increase PURPLEMET_WAIT_TIMEOUT (default is 1800000 / 30 min):
variables:
PURPLEMET_WAIT_TIMEOUT: "3600000" # 60 minutesOr set to 0 for no limit.
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: trueFix: Either:
- Run the pipeline on a protected branch/tag
- Uncheck Protect variable on the CI/CD variable
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_PROXYin the runner config - If using a custom API endpoint, set
PURPLEMET_BASE_URL
Rate limit reached. The CLI automatically retries with the Retry-After delay.
Fix: If persistent, reduce analysis frequency or contact Purplemet support.
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'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"- Job log: Human-readable summary in the CI job output
-
Artifacts: Download
purplemet-report.jsonfrom the job page -
Downstream jobs: Access
PURPLEMET_RATING,PURPLEMET_ISSUESvia dotenv variables - Security Dashboard (SARIF template + GitLab Ultimate): Findings in the security dashboard
- Purplemet dashboard: cloud.purplemet.com for detailed results