Skip to main content

GitHub Actions

A drop-in workflow that scans every push and PR, uploads SARIF to Code Scanning, and blocks merges on new Critical/High findings.

No pipeline change with Vulkro Cloud

If this repository is connected to Vulkro Cloud, you do not need a pipeline step for pull requests: Vulkro Cloud scans each pull request into a tracked branch and reports back on it with a status and a summary comment (pull-request checks are on every Vulkro Cloud plan). See Pull requests in Vulkro Cloud.

Minimal workflow​

.github/workflows/vulkro.yml
name: vulkro
on:
push:
branches: [main]
pull_request:

jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # required to upload SARIF
env:
VULKRO_ACCOUNT_TOKEN: ${{ secrets.VULKRO_ACCOUNT_TOKEN }} # CI key from the portal
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install vulkro
run: curl -fsSL https://dist.vulkro.com/install.sh | bash

- name: Sign in with the CI key
run: vulkro login

- name: Run scan (SARIF)
run: vulkro scan . --format sarif > vulkro.sarif

- name: Upload to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: vulkro.sarif

- name: Block PR on new Critical/High
run: vulkro scan . --min-confidence high

Licence the runner​

Every machine that scans needs an active trial or Pro, CI runners included. Create a CI key in the customer portal (app.vulkro.com) and store it as a repository secret named VULKRO_ACCOUNT_TOKEN; the workflow above exposes it to the job and vulkro login exchanges it for a lease. A CI key works for the 14-day trial and for paid plans, uses no device seat, and can be revoked. Without it, the scan step fails with exit code 1 and license_required. See Accounts and licensing.

Caching the binary + CVE bundle​

Speed up subsequent runs by caching ~/.local/bin/vulkro and ~/.vulkro/data/:

- name: Cache vulkro
uses: actions/cache@v4
id: cache
with:
path: |
~/.local/bin/vulkro
~/.vulkro/data
key: vulkro-${{ runner.os }}-v0.34.1

- name: Install vulkro
if: steps.cache.outputs.cache-hit != 'true'
run: |
curl -fsSL https://dist.vulkro.com/install.sh \
| VULKRO_BIN_DIR="$HOME/.local/bin" bash

A cached install takes <1 s. Without cache, the install + first-run CVE bundle fetch is ~10 s.

PR-scoped scans (only-new-findings gate)​

- name: Scan only changed lines
run: vulkro scan . --gate-vs origin/${{ github.base_ref }}
if: github.event_name == 'pull_request'

The gate uses the diff-scoped findings. Full-repo findings are still emitted in JSON.

PR-comment workflow​

- name: Generate PR comment
if: github.event_name == 'pull_request'
run: vulkro scan . --format gh-pr > vulkro-comment.md

- name: Post PR comment
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr comment ${{ github.event.pull_request.number }} --body-file vulkro-comment.md

Monorepo: scan only the affected service​

- name: Detect changed services
id: changed
run: |
echo "services=$(git diff --name-only origin/main...HEAD | awk -F/ '/^services\// {print $2}' | sort -u | jq -R . | jq -sc .)" >> $GITHUB_OUTPUT

- name: Scan changed services
run: |
for svc in $(echo '${{ steps.changed.outputs.services }}' | jq -r .[]); do
vulkro scan "services/$svc" --format sarif > "vulkro-$svc.sarif"
done

- name: Upload SARIFs
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: .

Air-gapped self-hosted runners​

If your runner can't reach vulkro.com, mirror the binary internally and license the runner with a licence file instead of a CI key: store the base64 of a .lic file as the secret VULKRO_LICENSE_B64 (no vulkro login step is needed):

env:
VULKRO_OFFLINE: "1"
VULKRO_LICENSE_B64: ${{ secrets.VULKRO_LICENSE_B64 }} # base64 of a .lic file
steps:
- name: Install vulkro (internal mirror)
run: |
curl -fsSL https://artifacts.internal/vulkro/install.sh | bash
env:
VULKRO_CDN_BASE_URL: "https://artifacts.internal/vulkro-cve"