-
Notifications
You must be signed in to change notification settings - Fork 0
github actions
This guide covers all aspects of integrating Purplemet security analyses into GitHub Actions workflows.
- Prerequisites
- Quick Start
- Installation Methods
- Parameters
- Outputs
- Complete Pipeline Examples
- SARIF and GitHub Code Scanning
- 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 GitHub Actions secret:
- Go to your repository on GitHub
- Navigate to Settings → Secrets and variables → Actions
- Click New repository secret
- Name:
PURPLEMET_API_TOKEN - Value: your API token
- Click Add secret
Organization-level secret: For multi-repo usage, add the secret at the organization level (Settings → Secrets → Actions) and grant access to the required repositories.
If using GitHub Code Scanning (SARIF upload), your workflow needs the security-events: write permission. GitHub Advanced Security must be enabled for the repository (free for public repos, requires license for private repos).
Create .github/workflows/purplemet.yml:
name: Security Analysis
on:
push:
branches: [main]
pull_request:
jobs:
purplemet:
runs-on: ubuntu-latest
steps:
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
fail-severity: 'high'This workflow:
- Runs on every push to
mainand on pull requests - Fails the pipeline if high or critical vulnerabilities are found
- Generates a visual summary in the Actions tab
Uses the official composite action that handles installation, execution, and reporting automatically.
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'Downloads and installs the CLI binary directly. Useful when you need more control over the execution.
- name: Install Purplemet CLI
run: curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh
- name: Run Security Analysis
env:
PURPLEMET_API_TOKEN: ${{ secrets.PURPLEMET_API_TOKEN }}
run: purplemet-cli analyze https://your-app.com --json --fail-on-severity highUses the official Docker image (~15MB). No installation step needed.
- name: Run Security Analysis
run: |
docker run --rm \
-e PURPLEMET_API_TOKEN=${{ secrets.PURPLEMET_API_TOKEN }} \
ppmsupport/purplemet-cli analyze https://your-app.com --json --fail-on-severity highOr as a Docker-based action:
- uses: docker://ppmsupport/purplemet-cli:latest
with:
args: analyze https://your-app.example.com --json --fail-on-severity high
env:
PURPLEMET_API_TOKEN: ${{ secrets.PURPLEMET_API_TOKEN }}| Input | Required | Default | Description |
|---|---|---|---|
api-token |
Yes | — | Purplemet API token (use ${{ secrets.PURPLEMET_API_TOKEN }}) |
target-url |
Yes | — | URL of the web application to analyze |
fail-severity |
No | high |
Fail threshold: critical, high, medium, low, info
|
timeout |
No | 1800000 |
Wait timeout in milliseconds (30 min, 0 = unlimited) |
version |
No | latest |
CLI version to use (e.g. v1.2.0) |
base-url |
No | — | API base URL override |
sarif-upload |
No | false |
Upload SARIF results to GitHub Code Scanning |
When using the binary or Docker directly (methods 2 and 3), configure via environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
PURPLEMET_API_TOKEN |
Yes | — | API authentication token |
PURPLEMET_BASE_URL |
No | https://api.purplemet.com |
API base URL |
PURPLEMET_FAIL_SEVERITY |
No | — (disabled) | Severity gate threshold |
PURPLEMET_WAIT_TIMEOUT |
No |
0 (unlimited) |
Polling timeout in ms |
When invoking purplemet-cli directly, the following flags are relevant:
| Flag | Description |
|---|---|
--json |
Machine-readable JSON output (recommended for CI) |
--fail-on-severity <level> |
Fail if issues at or above this severity |
--wait-timeout <ms> |
Bound total execution time |
--format sarif |
Output in SARIF 2.1.0 format |
--output-file <path> |
Write output to a file |
--no-create |
Don't auto-create a site for unknown URLs |
The Purplemet action exposes outputs for use in subsequent steps:
| Output | Type | Description | Example |
|---|---|---|---|
exit-code |
int | Exit code of the analysis | 0 |
rating |
string | Security rating (A–F) | B |
issues |
int | Total number of issues found | 12 |
result-json |
string | Full analysis result in JSON | {"analysis": {...}} |
gate-results |
string | JSON with gate pass/fail results | {"severity": {"passed": true}} |
- uses: purplemet/purplemet-action@v1
id: analysis
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
continue-on-error: true
- name: Check Results
run: |
echo "Rating: ${{ steps.analysis.outputs.rating }}"
echo "Issues: ${{ steps.analysis.outputs.issues }}"
echo "Exit code: ${{ steps.analysis.outputs.exit-code }}"
- name: Fail on Critical
if: steps.analysis.outputs.exit-code == '1'
run: |
echo "::error::Security issues found above threshold"
exit 1- name: Parse Results
if: always()
run: |
cat purplemet-report.json | jq '.analysis.issueCnts'Note: The
result-jsonoutput contains the full JSON result, but for shell processing prefer reading thepurplemet-report.jsonfile directly (generated by the action) as it avoids shell escaping issues with multiline content.
name: Security Analysis
on:
push:
branches: [main]
pull_request:
jobs:
purplemet:
name: Purplemet Security Analysis
runs-on: ubuntu-latest
steps:
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
fail-severity: 'high'name: Post-Deploy Security Analysis
on:
workflow_run:
workflows: ['Deploy to Staging']
types: [completed]
branches: [main]
jobs:
security:
name: Security Analysis
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://staging.your-app.example.com'
fail-severity: 'high'
timeout: '600000'name: Weekly Security Analysis
on:
schedule:
- cron: '0 6 * * 1' # Every Monday at 6:00 UTC
jobs:
security:
name: Weekly Security Analysis
runs-on: ubuntu-latest
steps:
- uses: purplemet/purplemet-action@v1
id: analysis
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
fail-severity: 'medium'
timeout: '600000'
continue-on-error: true
- name: Notify on Issues
if: steps.analysis.outputs.exit-code == '1'
run: |
echo "::warning::Weekly analysis found ${{ steps.analysis.outputs.issues }} issue(s) — rating: ${{ steps.analysis.outputs.rating }}"name: Multi-Site Security Analysis
on:
schedule:
- cron: '0 6 * * 1'
jobs:
security:
name: Analyze ${{ matrix.site.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
site:
- { name: 'Production', url: 'https://app.example.com' }
- { name: 'Staging', url: 'https://staging.example.com' }
- { name: 'API', url: 'https://api.example.com' }
steps:
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: ${{ matrix.site.url }}
fail-severity: 'high'
timeout: '600000'The analysis runs and reports results but never fails the pipeline:
name: Security Analysis (Warning Mode)
on: [push]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: purplemet/purplemet-action@v1
id: analysis
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
fail-severity: 'high'
continue-on-error: true
- name: Report
if: always()
run: |
if [ "${{ steps.analysis.outputs.exit-code }}" = "1" ]; then
echo "::warning::Security analysis found issues (rating: ${{ steps.analysis.outputs.rating }}, issues: ${{ steps.analysis.outputs.issues }})"
finame: CI/CD
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Build
run: make build
- name: Test
run: make test
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Deploy to Staging
run: ./deploy.sh staging
security-analysis:
needs: deploy
runs-on: ubuntu-latest
steps:
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://staging.your-app.example.com'
fail-severity: 'high'
timeout: '600000'name: Security Analysis
on: [push]
jobs:
security:
runs-on: ubuntu-latest
steps:
- name: Install Purplemet CLI
run: curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh
- name: Run Analysis
id: analysis
env:
PURPLEMET_API_TOKEN: ${{ secrets.PURPLEMET_API_TOKEN }}
run: |
set +e
RESULT=$(purplemet-cli analyze https://your-app.com \
--json --fail-on-severity high --wait-timeout 300000 2>/dev/null)
EXIT_CODE=$?
set -e
echo "exit-code=${EXIT_CODE}" >> $GITHUB_OUTPUT
# Multiline JSON requires a delimiter for GITHUB_OUTPUT
EOF_DELIM=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "result<<${EOF_DELIM}" >> $GITHUB_OUTPUT
echo "${RESULT}" >> $GITHUB_OUTPUT
echo "${EOF_DELIM}" >> $GITHUB_OUTPUT
case $EXIT_CODE in
0) echo "Analysis passed" ;;
1) echo "::warning::Vulnerabilities found above threshold" ;;
2) echo "::error::Analysis error on Purplemet side" ;;
3) echo "::error::Analysis timed out" ;;
4) echo "::error::Network/API error — check token and connectivity" ;;
*) echo "::error::Unexpected error (code ${EXIT_CODE})" ;;
esac
exit $EXIT_CODESARIF (Static Analysis Results Interchange Format) is a standard format for static analysis results. GitHub Code Scanning uses SARIF to display security findings directly in the Security tab of your repository.
- GitHub Advanced Security must be enabled (free for public repos)
- Workflow must have
security-events: writepermission - The repository must be checked out (
actions/checkout) for SARIF upload
name: Security Analysis with Code Scanning
on:
push:
branches: [main]
pull_request:
jobs:
purplemet:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v5
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
sarif-upload: 'true'
fail-severity: 'high'
continue-on-error: trueResults appear in:
- Security tab → Code scanning alerts
- Pull request Files changed tab as inline annotations
If you prefer manual control:
name: Security Analysis with SARIF
on: [push]
permissions:
security-events: write
contents: read
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Purplemet CLI
run: curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh
- name: Run Analysis
env:
PURPLEMET_API_TOKEN: ${{ secrets.PURPLEMET_API_TOKEN }}
run: |
purplemet-cli analyze https://your-app.com \
--format sarif --output-file purplemet-results.sarif \
--fail-on-severity high || true
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: purplemet-results.sarif
category: purplemet| Code | Meaning | Pipeline Behavior |
|---|---|---|
| 0 | No issues above threshold | Pipeline passes |
| 1 | Issues found above severity threshold | Pipeline fails (use continue-on-error: true for warning) |
| 2 | Analysis error on Purplemet platform | Pipeline fails |
| 3 | Timeout exceeded | Pipeline fails |
| 4 | Network or API error | Pipeline fails |
| 5 | Usage error (bad arguments) | 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.
The Purplemet action automatically generates a visual summary in the Actions tab showing:
- Security rating with color indicator
- Issue count and severity breakdown
- Pass/fail status against the configured threshold
- Detailed issue breakdown (expandable)
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
version: 'v1.2.0'- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
- name: Upload Report
if: always()
uses: actions/upload-artifact@v4
with:
name: purplemet-report
path: purplemet-report.json
retention-days: 30- name: Install Purplemet CLI
run: curl -sSL https://raw.githubusercontent.com/purplemet/cli/main/scripts/install.sh | sh
- name: Run Analysis
env:
PURPLEMET_API_TOKEN: ${{ secrets.PURPLEMET_API_TOKEN }}
run: |
purplemet-cli analyze https://your-app.com \
--format html --output-file report.html \
--fail-on-severity high || true
- name: Upload HTML Report
if: always()
uses: actions/upload-artifact@v4
with:
name: security-report
path: report.htmlname: Security Analysis on Infra Changes
on:
push:
paths:
- 'infrastructure/**'
- 'nginx/**'
- 'Dockerfile'
- 'docker-compose.yml'
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
fail-severity: 'high'name: Security Analysis
on:
push:
branches: [main, staging]
jobs:
security:
runs-on: ubuntu-latest
steps:
- name: Set Target URL
id: target
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "url=https://app.example.com" >> $GITHUB_OUTPUT
else
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
fi
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: ${{ steps.target.outputs.url }}
fail-severity: 'high'The secret is missing or not accessible.
Fix: Add PURPLEMET_API_TOKEN as a repository secret:
Settings → Secrets and variables → Actions → New repository secret.
For organization secrets, verify the repository is in the allowed list.
The API token is invalid or expired.
Fix:
- Verify the token works:
purplemet-cli auth check - Create a new token at cloud.purplemet.com
- Update the repository secret
Fix: Increase the timeout. Default is 30 minutes (1800000 ms). Set 0 for no limit.
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
timeout: '3600000' # 60 minutesExit code 1 means vulnerabilities were found above the threshold. To make it a warning instead of a failure:
- uses: purplemet/purplemet-action@v1
id: analysis
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: 'https://your-app.example.com'
fail-severity: 'high'
continue-on-error: true
- name: Warn on Issues
if: steps.analysis.outputs.exit-code == '1'
run: echo "::warning::Security issues found - rating: ${{ steps.analysis.outputs.rating }}"Rate limit reached. The CLI automatically retries with the Retry-After delay.
Fix: If persistent, reduce analysis frequency or contact Purplemet support.
The specified version doesn't exist or the GitHub release is not accessible.
Fix:
- Check available versions at github.com/purplemet/cli/releases
- Use
version: 'latest'(default) or a valid tag likeversion: 'v1.0.0'
The workflow doesn't have the required permissions.
Fix: Add permissions to your workflow:
permissions:
security-events: write
contents: readAlso ensure GitHub Advanced Security is enabled for the repository (required for private repos).
By default, purplemet-cli analyze <url> auto-creates the site if the URL is not found. No extra step needed.
To disable this behavior, use --no-create.
Use a matrix strategy:
strategy:
fail-fast: false
matrix:
url:
- 'https://app1.example.com'
- 'https://app2.example.com'
steps:
- uses: purplemet/purplemet-action@v1
with:
api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
target-url: ${{ matrix.url }}The action works on self-hosted runners. Ensure:
-
curlandjqare available - Outbound HTTPS access to
api.purplemet.comandgithub.com - If behind a proxy, set
HTTP_PROXY/HTTPS_PROXYenvironment variables
- Actions tab: Job summary with rating, issues, and severity breakdown
- Security tab (if SARIF enabled): Code scanning alerts with detailed findings
-
Artifacts: Download
purplemet-report.jsonfrom the workflow run -
Step outputs: Access
rating,issues,result-jsonin subsequent steps