Skip to main content

GitHub Actions

Run vulkro-sf on every pull request and upload the findings to GitHub Code Scanning.

# .github/workflows/vulkro-sf.yml
name: Vulkro for Salesforce
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
contents: read
security-events: write # to upload SARIF
jobs:
scan:
runs-on: ubuntu-latest
env:
VULKRO_ACCOUNT_TOKEN: ${{ secrets.VULKRO_ACCOUNT_TOKEN }} # CI key from the portal
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # needed for --gate-vs
- name: Install vulkro-sf
run: curl -fsSL https://dist.vulkro.com/install-sf.sh | bash
- name: Sign in with the CI key
run: vulkro-sf login
- name: Scan
run: vulkro-sf scan ./force-app --format sarif > sf.sarif
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sf.sarif
category: vulkro-sf

Create the CI key in the portal (app.vulkro.com) and save it as the repository secret VULKRO_ACCOUNT_TOKEN. Without it the scan exits 1 with license_required. if: always() uploads the SARIF even when the scan fails on findings.

Fail only on new findings​

On pull requests, replace the Scan step with this one. The SARIF still holds every finding, but the step fails only on findings the pull request introduced:

- name: Scan (fail on new findings only)
run: vulkro-sf scan ./force-app --gate-vs origin/${{ github.base_ref }} --format sarif > sf.sarif

To annotate the pull request with only the new findings, use --format github-annotations instead of sarif (no file redirect needed). See Baselines.

Exit codes​

0 passes; 1 fails the job (findings, or no licence); 2 fails it as an error (read stderr).

Air-gapped runners​

Bake a pinned binary into the runner image (VULKRO_SF_VERSION), set VULKRO_OFFLINE=1, and license it by setting VULKRO_LICENSE_B64 to a base64 .lic file stored as a secret, or with vulkro-sf activate <file.lic> at image build. See Licensing.

Live-org audits on a schedule​

Live-org commands need an sf login on the runner. Use the Salesforce CLI's JWT bearer flow with a dedicated Connected App: certificate-based, scopes api and refresh_token (never Full), limited to one read-only CI user.

- name: Install the Salesforce CLI
run: npm install -g @salesforce/cli
- name: sf JWT login
env:
SF_JWT_SERVER_KEY: ${{ secrets.SF_JWT_SERVER_KEY }}
run: |
echo "$SF_JWT_SERVER_KEY" > server.key
sf org login jwt --client-id ${{ secrets.SF_CONSUMER_KEY }} \
--jwt-key-file server.key --username ${{ secrets.SF_USERNAME }} --alias prod-ci
rm server.key
- name: Live-org posture report
run: vulkro-sf org report --target-org prod-ci -O posture.html

Run this from a schedule: workflow rather than on every pull request. The CI user needs API Enabled and View Setup and Configuration; see Live-org setup.

See also: CI/CD integration, SARIF output, The sf CLI handoff