Skip to content

github actions

Purplemet CI edited this page Jun 9, 2026 · 3 revisions

GitHub Actions Integration

This guide covers all aspects of integrating Purplemet security analyses into GitHub Actions workflows.

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. Repository Secret

Add the token as a GitHub Actions secret:

  1. Go to your repository on GitHub
  2. Navigate to SettingsSecrets and variablesActions
  3. Click New repository secret
  4. Name: PURPLEMET_API_TOKEN
  5. Value: your API token
  6. 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.

3. Permissions (SARIF only)

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).

Quick Start

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 main and on pull requests
  • Fails the pipeline if high or critical vulnerabilities are found
  • Generates a visual summary in the Actions tab

Installation Methods

Method 1: Purplemet Action (recommended)

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'

Method 2: Binary Installation

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 high

Method 3: Docker Image

Uses 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 high

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

Parameters

Action Inputs

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

Environment Variables

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

CLI Flags

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

Outputs

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

Using Outputs

- 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

Using JSON Output

- name: Parse Results
  if: always()
  run: |
    cat purplemet-report.json | jq '.analysis.issueCnts'

Note: The result-json output contains the full JSON result, but for shell processing prefer reading the purplemet-report.json file directly (generated by the action) as it avoids shell escaping issues with multiline content.

Complete Pipeline Examples

Basic: Analysis on Push and PR

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'

After Deployment: Analyze Staging Environment

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'

Scheduled Weekly Analysis

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

Multi-Site Analysis with Matrix

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'

Warning Mode (Non-blocking)

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

Full CI/CD Pipeline with Build, Deploy, and Analysis

name: 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'

Binary Install with Custom Handling

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_CODE

SARIF and GitHub Code Scanning

What is SARIF?

SARIF (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.

Prerequisites for SARIF

  1. GitHub Advanced Security must be enabled (free for public repos)
  2. Workflow must have security-events: write permission
  3. The repository must be checked out (actions/checkout) for SARIF upload

Using the Action with SARIF

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

Results appear in:

  • Security tab → Code scanning alerts
  • Pull request Files changed tab as inline annotations

Manual SARIF Upload

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

Results and Exit Codes

Exit Codes

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

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.

Job Summary

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)

Advanced Usage

Pin a Specific CLI Version

- uses: purplemet/purplemet-action@v1
  with:
    api-token: ${{ secrets.PURPLEMET_API_TOKEN }}
    target-url: 'https://your-app.example.com'
    version: 'v1.2.0'

Save Report Artifact

- 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

Generate HTML Report Artifact

- 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.html

Conditional Analysis Based on Changed Files

name: 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'

Using Environment-Specific URLs

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'

FAQ / Common Errors

"PURPLEMET_API_TOKEN is not set"

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.


"Access is not authorized without a valid session"

The API token is invalid or expired.

Fix:

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

The analysis takes too long and times out

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 minutes

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

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

"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.


"Download failed" during CLI installation

The specified version doesn't exist or the GitHub release is not accessible.

Fix:


SARIF upload fails with "Resource not accessible by integration"

The workflow doesn't have the required permissions.

Fix: Add permissions to your workflow:

permissions:
  security-events: write
  contents: read

Also ensure GitHub Advanced Security is enabled for the repository (required for private repos).


How do I analyze a site that doesn't exist yet?

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.


How do I analyze multiple sites?

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

How do I use a self-hosted runner?

The action works on self-hosted runners. Ensure:

  • curl and jq are available
  • Outbound HTTPS access to api.purplemet.com and github.com
  • If behind a proxy, set HTTP_PROXY / HTTPS_PROXY environment variables

Where can I see the analysis results?

  1. Actions tab: Job summary with rating, issues, and severity breakdown
  2. Security tab (if SARIF enabled): Code scanning alerts with detailed findings
  3. Artifacts: Download purplemet-report.json from the workflow run
  4. Step outputs: Access rating, issues, result-json in subsequent steps

Clone this wiki locally