CI/CD integration
This page is for the engineer adding vulkro-sf to a continuous
integration pipeline. The artifact you produce is a PR-time security
gate: every pull request runs the scan, and the build fails on
findings before merge.
The model
The standard pattern is the same across CI providers:
- Install
vulkro-sfinto the runner. On a self-hosted runner bake the binary into the image; on a hosted runner install it as the first step of the job. - Run
vulkro-sf scanover the project source. - Gate on the exit code:
0passes the job,1fails it (findings),2is a hard error. - Upload the SARIF (and optionally the HTML report) as a job artifact, so reviewers can read findings outside the runner log.
The sections below give a minimal working snippet for each major CI provider.
GitHub Actions
The recommended pattern uploads SARIF to GitHub Code Scanning, so findings render as inline PR comments and stay in the Security tab.
# .github/workflows/vulkro-sf.yml
name: Vulkro for Salesforce
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
contents: read
security-events: write # required to upload SARIF
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install vulkro-sf
run: |
curl -fsSL https://dist.vulkro.com/install-sf.sh | bash
vulkro-sf --version
- name: Scan
run: |
vulkro-sf scan ./force-app \
--format sarif \
--min-confidence high \
-o vulkro-sf.sarif
# `vulkro-sf scan` exits 0 (clean), 1 (findings), 2 (error).
# We let exit 1 propagate so the job fails on findings.
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: vulkro-sf.sarif
category: vulkro-sf
- name: Archive HTML report
if: always()
uses: actions/upload-artifact@v4
with:
name: vulkro-sf-report
path: vulkro-sf.sarif
The if: always() on the upload step ensures the SARIF lands in Code
Scanning even when the scan exits 1; otherwise a failing scan would
skip the upload and reviewers would not see what failed.
GitLab CI
GitLab parses SARIF into its Vulnerability Report under the same
report:sast keyword.
# .gitlab-ci.yml
stages:
- security
vulkro-sf-scan:
stage: security
image: ubuntu:24.04
before_script:
- apt-get update && apt-get install -y curl ca-certificates
- curl -fsSL https://dist.vulkro.com/install-sf.sh | bash
- export PATH="/usr/local/bin:$PATH"
- vulkro-sf --version
script:
- vulkro-sf scan ./force-app
--format sarif
--min-confidence high
-o vulkro-sf.sarif
artifacts:
when: always
reports:
sast: vulkro-sf.sarif
paths:
- vulkro-sf.sarif
expire_in: 30 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
The job fails when vulkro-sf scan exits 1, the SARIF appears under
the Security and Compliance tab of the merge request, and the
artifact is retained for 30 days.
Bitbucket Pipelines
# bitbucket-pipelines.yml
image: ubuntu:24.04
pipelines:
pull-requests:
'**':
- step:
name: Vulkro for Salesforce
script:
- apt-get update && apt-get install -y curl ca-certificates
- curl -fsSL https://dist.vulkro.com/install-sf.sh | bash
- export PATH="/usr/local/bin:$PATH"
- vulkro-sf scan ./force-app
--format sarif
--min-confidence high
-o vulkro-sf.sarif
artifacts:
- vulkro-sf.sarif
Bitbucket does not parse SARIF natively, but the artifact is
downloadable from the pipeline UI. Pair with a SARIF viewer (the
sarif-tools CLI or a JetBrains plugin) for human review.
The exit-code contract
Every vulkro-sf subcommand follows the same contract:
0: success, no findings. CI passes.1: scan completed and findings were reported. CI should fail the job.2: error (bad arguments, IO failure, project not detected, internal crash). CI should fail the job and surface stderr.
In every snippet above the CI provider's default behaviour (fail on
non-zero exit) handles this automatically. The split between 1 and
2 matters when a downstream step needs to distinguish "there are
findings to look at" from "the scan itself broke": gate notifications
or autoreviewer comments on exit 1, page on exit 2.