Docs
← Home Sign In Get Started

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 → SettingsSecrets and variablesActions
  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