Security gates that do not punish the team.
A CI pipeline is where code stops being a draft, and AI assistants have multiplied how much of it arrives there every day. Vulkro gives the pipeline a deterministic security gate: the same commit always gets the same verdict, so a red build means a real finding, never a flaky one.
Adding a SAST step to CI is easy. Adding one that does not drag every run out, bury the team's shared channel in false positives, or upload the repository to a vendor cloud on every push is harder.
Vulkro is designed for the gate. Single binary install, scan times you can verify on the public corpus, deterministic exit codes, and the right output formats for whichever CI you use. The scan runs on the runner itself; nothing is uploaded.
The gate contract
vulkro scan --fail-on critical,high --format sarif --output vulkro.sarif
Three exit codes, every subcommand, by contract:
| Exit | Meaning |
|---|---|
0 | Scan completed, no findings at or above the --fail-on threshold |
1 | Scan completed, findings reported (or license-block) |
2 | Error: bad arguments, IO failure, internal crash |
This is in the --help text of every subcommand. Your CI's
if exit_code in (0, 1) logic is the right shape; exit 2 is
a "something broke, page someone" signal.
Baseline + ratchet for the retrofit
A repo that has been running without SAST for years will not turn green on day one. Two options:
Baseline once, ratchet forever. Commit the current state
into .vulkro-baseline.json. Subsequent scans with
--ratchet (auto-discovers the baseline file) only fail on
findings the PR introduces. Existing debt is tracked separately
and burned down on its own cadence.
Compare against a base ref. vulkro scan --gate-vs origin/main
diffs against a ref and fails only on NEW findings the working
branch introduces. Useful for PRs where the baseline approach
would be too coarse.
Either way the security team gets the visibility they want without the team getting a "burn down 4,000 findings" assignment they will never finish.
Output formats per CI
| CI / Tool | Recommended format | Flag |
|---|---|---|
| GitHub Code Scanning | SARIF | --format sarif |
| GitHub PR inline comments | gh-pr | --format gh-pr |
| GitLab Security Dashboard | SARIF | --format sarif |
| Any dashboard with JUnit | JUnit XML | --format junit |
| Any dashboard with CSV | CSV | --format csv |
| Stream to a downstream tool | NDJSON | --format ndjson |
| Human-readable terminal | Default table | (no flag needed) |
SARIF is the workhorse: it works in GitHub Code Scanning, GitLab, Azure DevOps, BitBucket, and most other dashboards that have implemented the spec.
GitHub Actions in 12 lines
name: vulkro
on:
pull_request:
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # full history so --gate-vs works
- run: curl -fsSL https://install.vulkro.com | sh
- run: vulkro scan --ratchet --format sarif --output vulkro.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: vulkro.sarif }
if: always() ensures the SARIF uploads even when Vulkro
returns exit 1 (findings present), so the Code Scanning tab
gets the data and the build still fails the way you want it to.
GitLab CI in 8 lines
vulkro:
image: ubuntu:24.04
before_script:
- curl -fsSL https://install.vulkro.com | sh
script:
- vulkro scan --ratchet --format sarif --output vulkro.sarif
artifacts:
reports:
sast: vulkro.sarif
GitLab's sast report type ingests SARIF natively.
Pre-commit hook
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: vulkro
name: vulkro scan
entry: vulkro scan --fail-on critical
language: system
pass_filenames: false
types_or: [python, javascript, typescript, go]
pass_filenames: false because vulkro takes a single project
root (not a list of files) and cross-file taint flow matters,
so scanning the project is the right primitive. types_or
scopes the hook to only fire when files in the supported
languages change. On the public corpus, every repo but one
scans in about three seconds or less, which is what makes a
full scan in a pre-commit hook practical.
Performance
Speed claims in this space are usually vibes. Ours regenerate from the public benchmark corpus.
A cold vulkro scan finishes all 15 corpus repos in 39.3 seconds
end to end. 14 of the 15 finish in about three seconds or less;
the largest (juice-shop, a full TypeScript application) takes
15.1 seconds. Each sample is a complete process invocation, with
startup and report serialization included, so the numbers reflect
what a CI job actually pays.
Rerun the timing yourself: bench/comparison/run.sh regenerates
the speed table on the public corpus.
Honest comparison
vs. cloud SAST products (Snyk Code, Semgrep Cloud, Checkmarx): Vulkro never uploads code; the scan runs on the runner itself. Performance is generally faster because there is no upload step. The trade-off: no shared dashboard or cross-team triage UI.
vs. CodeQL via GitHub Actions: CodeQL is excellent and free for public repos. Vulkro complements it (different rule set, faster scans, easier multi-language story); the two coexist in the same SARIF pipeline. For private repos GHAS pricing kicks in; Vulkro is the alternative, licensed per seat through our team.
vs. Semgrep CE: on the public benchmark corpus Vulkro finds 44 of the 76 catalogued bugs in the languages both tools support, where Semgrep CE finds 15, at 0.81 precision against 0.62. Semgrep CE is LGPL-licensed; Vulkro requires an account, with a license-file route that suits CI runners.
Deployment fit and licensing
The runner needs your Vulkro account connected, the same as any
machine that scans. For ephemeral CI runners the license-file
route avoids interactive login entirely; the
install guide covers both. A
14-day trial of the full product starts on your first
vulkro login, and licenses are issued per seat, directly by
our team. Scan evidence (SARIF, JUnit, JSON) is written to the
runner's workspace and retained by your CI, not by a vendor.
Honest scope
The gate is static analysis at merge time, not a full appsec program. Vulkro does not watch your production traffic, does not replace a penetration test, and reachability ranking orders the findings without claiming to prove exploitability. What it does is deterministic: the same diff gets the same verdict on every run, which is the property a CI gate actually needs.
Read the full benchmark methodology · CLI reference · Output formats