Docs

Overview

The Shieldome GitHub Actions integration uses the REST API directly - no marketplace action, no Docker image to pull. The generated workflow starts a scan, polls for completion, uploads SARIF to GitHub Code Scanning, and fails the build if findings meet your configured severity threshold.

✅
What you get Scans run automatically on every push or PR. SARIF results appear in the Security → Code Scanning tab. The build fails if findings reach your severity threshold. The PDF report is attached as a downloadable workflow artifact.

Prerequisites

  • A Shieldome account with an API key - see API Keys
  • Your target URL must be publicly reachable from GitHub Actions runners (or use custom-ip for internal staging)
  • Repository permission: security-events: write (for SARIF upload to GitHub Security tab)

Step 1 - Add secrets to your repository

  1. Go to your GitHub repository → Settings → Secrets and variables → Actions
  2. Add secret SHIELDOME_API_KEY - your API key from Profile → API Keys
  3. Optionally add secret SHIELDOME_TARGET - the URL to scan (or hard-code it in the YAML)

Step 2 - Download the workflow file

The easiest way: open your Shieldome dashboard, enter your target URL, then click the CI/CD button and choose Download workflow YAML. The file is pre-filled with your target URL and ready to commit.

Alternatively, create .github/workflows/shieldome-scan.yml manually:

yaml - .github/workflows/shieldome-scan.yml
name: Shieldome Security Scan

on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
  schedule:
    - cron: '0 6 * * 1'  # Weekly on Monday at 06:00 UTC
  workflow_dispatch:

permissions:
  security-events: write
  contents: read

env:
  SHIELDOME_HOST: https://app.shieldome.com
  TARGET_URL:     ${{ secrets.SHIELDOME_TARGET }}  # or hard-code: https://your-app.com
  FAIL_ON:        high  # critical | high | medium | low | never

jobs:
  security-scan:
    name: Shieldome Security Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Start scan
        id: scan
        run: |
          RESPONSE=$(curl -sf --retry 3 -X POST "$SHIELDOME_HOST/api/v1/scan" \
            -H "Content-Type: application/json" \
            -H "X-Shieldome-Key: ${{ secrets.SHIELDOME_API_KEY }}" \
            -d '{"target":"'"$TARGET_URL"'","scan_type":"vuln"}')
          echo "$RESPONSE"
          SCAN_ID=$(echo "$RESPONSE" | jq -r '.scan_id // empty')
          if [ -z "$SCAN_ID" ]; then
            echo "::error::Failed to start scan. Check SHIELDOME_API_KEY and TARGET_URL."
            exit 1
          fi
          echo "scan_id=$SCAN_ID" >> $GITHUB_OUTPUT
          echo "Scan started: $SCAN_ID"

      - name: Wait for completion (max 15 min)
        run: |
          SCAN_ID="${{ steps.scan.outputs.scan_id }}"
          for i in $(seq 1 90); do
            DATA=$(curl -sf -H "X-Shieldome-Key: ${{ secrets.SHIELDOME_API_KEY }}" \
              "$SHIELDOME_HOST/api/v1/scan/$SCAN_ID")
            STATUS=$(echo "$DATA" | jq -r '.status')
            PCT=$(echo "$DATA"    | jq -r '.progress.percent // "-"')
            echo "[$i/90] status=$STATUS  progress=${PCT}%"
            if [ "$STATUS" = "completed" ]; then break; fi
            if [ "$STATUS" = "failed" ] || [ "$STATUS" = "aborted" ]; then
              echo "::error::Scan ended with status '$STATUS'"
              exit 1
            fi
            sleep 10
          done

      - name: Security gate
        id: gate
        run: |
          GATE=$(curl -sf -H "X-Shieldome-Key: ${{ secrets.SHIELDOME_API_KEY }}" \
            "$SHIELDOME_HOST/api/v1/scan/${{ steps.scan.outputs.scan_id }}/gate?fail_on=$FAIL_ON")
          echo "$GATE" | jq .

          PASSED=$(echo "$GATE" | jq -r '.passed')
          GRADE=$(echo "$GATE"  | jq -r '.grade')
          SCORE=$(echo "$GATE"  | jq -r '.risk_score')

          {
            echo "## 🛡 Shieldome Security Scan"
            echo ""
            echo "| | |"
            echo "|---|---|"
            echo "| **Target** | $TARGET_URL |"
            echo "| **Grade** | $GRADE |"
            echo "| **Risk Score** | $SCORE / 100 |"
            echo "| **Gate** | $FAIL_ON+ → $([ \"$PASSED\" = \"true\" ] && echo \"✅ PASSED\" || echo \"❌ FAILED\") |"
            echo ""
            echo "| Severity | Count |"
            echo "|---|---|"
            echo "$GATE" | jq -r '.counts | to_entries[] | "| \(.key) | \(.value) |"'
          } >> $GITHUB_STEP_SUMMARY

          if [ "$PASSED" != "true" ]; then
            echo "::error::Security gate FAILED - $FAIL_ON+ findings detected."
            exit 1
          fi

      - name: Download SARIF
        if: always()
        run: |
          curl -sf -H "X-Shieldome-Key: ${{ secrets.SHIELDOME_API_KEY }}" \
            "$SHIELDOME_HOST/api/v1/scan/${{ steps.scan.outputs.scan_id }}/sarif" \
            -o shieldome-results.sarif || true

      - name: Upload SARIF to GitHub Security tab
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: shieldome-results.sarif
          category:    shieldome
        continue-on-error: true

      - name: Download PDF report
        if: always()
        run: |
          curl -sf -H "X-Shieldome-Key: ${{ secrets.SHIELDOME_API_KEY }}" \
            "$SHIELDOME_HOST/api/scan/${{ steps.scan.outputs.scan_id }}/report" \
            -o shieldome-report.pdf || true

      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name:            shieldome-security-report
          path:            |
            shieldome-report.pdf
            shieldome-results.sarif
          retention-days: 30

Configuration options

VariableDefaultDescription
TARGET_URL-Required. URL to scan, e.g. https://staging.example.com
FAIL_ONhighFail the build at this severity: critical, high, medium, low, or never
scan_type (in curl body)vulnvuln, perf, both, or api
SHIELDOME_HOSThttps://app.shieldome.comOverride for self-hosted deployments

Advanced example - PR comment with security summary

yaml
- name: Comment on PR
  if: github.event_name == 'pull_request' && always()
  uses: actions/github-script@v7
  with:
    script: |
      const gate = JSON.parse(process.env.GATE_JSON || '{}');
      const icon = gate.passed ? '✅' : '❌';
      github.rest.issues.createComment({
        owner: context.repo.owner,
        repo:  context.repo.repo,
        issue_number: context.issue.number,
        body: [
          `## ${icon} Shieldome Security Scan`,
          `| Severity | Count |`,
          `|---|---|`,
          `| 🔴 Critical | ${gate.counts?.critical ?? 0} |`,
          `| 🟠 High     | ${gate.counts?.high ?? 0} |`,
          `| 🟡 Medium   | ${gate.counts?.medium ?? 0} |`,
          `| 🔵 Low      | ${gate.counts?.low ?? 0} |`,
          ``,
          `**Risk Score:** ${gate.risk_score ?? '-'}/100 · **Grade:** ${gate.grade ?? '-'}`,
          `[View full report](https://app.shieldome.com)`,
        ].join('\n')
      })

Authenticated scans

To scan behind a login, pass a session cookie via the API body:

yaml
- name: Start authenticated scan
  run: |
    curl -sf -X POST "$SHIELDOME_HOST/api/v1/scan" \
      -H "Content-Type: application/json" \
      -H "X-Shieldome-Key: ${{ secrets.SHIELDOME_API_KEY }}" \
      -d '{
        "target":    "${{ secrets.SHIELDOME_TARGET }}",
        "scan_type": "vuln",
        "cookie":    "${{ secrets.SCAN_SESSION_COOKIE }}"
      }'
💡
Scan staging before DNS cutover. Add "custom_ip": "10.0.1.50" to the scan body to hit a specific server IP while keeping the Host header set to your production domain. Useful for testing infrastructure changes before go-live.

GitLab CI

The same curl-based approach works in any CI system. See the GitLab CI guide and generic CI/CD guide for platform-specific examples.

Troubleshooting

ProblemSolution
SARIF upload fails with 403Add permissions: security-events: write to the job or top-level workflow
Build fails on the security gateSet FAIL_ON: critical or FAIL_ON: never while investigating new findings
Scan ID is empty / curl returns nothingThe API key is wrong or the target URL is unreachable. Run the curl command locally to confirm.
Scan times out after 90 × 10 s = 15 minFull scans with Playwright can take longer - increase the loop to 120 iterations or switch to scan_type: vuln
Want to scan a specific server IPAdd "custom_ip": "1.2.3.4" to the POST body - Host header stays correct