Skip to main content

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:

ExitMeaning
0Scan completed, no findings at or above the --fail-on threshold
1Scan completed, findings reported (or license-block)
2Error: 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 / ToolRecommended formatFlag
GitHub Code ScanningSARIF--format sarif
GitHub PR inline commentsgh-pr--format gh-pr
GitLab Security DashboardSARIF--format sarif
Any dashboard with JUnitJUnit XML--format junit
Any dashboard with CSVCSV--format csv
Stream to a downstream toolNDJSON--format ndjson
Human-readable terminalDefault 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