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