CI/CD integration¶
Visao is built to drop into a pipeline as a single command. It reads the
compile_commands.json your build already produces, emits a
machine-readable report in the format your CI understands, and signals
pass/fail through its exit code.
The contract¶
Every platform integration is glue around one invocation:
visao --report-format <sarif|gitlab|json> \
--report-output <file> \
--report-uri-base <project-root> \
--severity <Low|Medium|High|...> \
--fail-on <none|low|medium|high|critical> \
--baseline <sarif> \
<files | compile_commands.json>
--report-formatSelects the machine document:
sarif: SARIF 2.1.0, the de-facto static-analysis interchange format (GitHub Code Scanning, Jenkins Warnings NG, Azure DevOps).gitlab: GitLab’s SAST report schema, which GitLab ingests instead of SARIF.json: a compact Visao-native findings array forjqand ad-hoc scripting.
Each document names the rule of a finding and links to its page in the catalogue at https://defectdb.org. SARIF uses
ruleIdandhelpUri, the JSON report uses the same two names, and the GitLab report uses itslinksarray. *text(default): the human console rendering.--report-outputWrites the document to a file and keeps the human console output alongside it. Without this flag, the document goes to stdout in place of the console rendering. A pipe then receives a pure, parseable stream, because the engine writes its progress messages to stderr.
--report-uri-baseDirectory prefix stripped from every file path, so the report’s locations are relative to the repository root. GitHub Code Scanning anchors inline annotations onto the diff when the SARIF
uriis relative to the repository, and acompile_commands.jsonusually carries absolute paths. Point this flag at the checkout root ($GITHUB_WORKSPACE,$CI_PROJECT_DIRor$WORKSPACE) and the annotations land. A leading./is always stripped.--fail-onMakes the process exit non-zero when at least one surviving finding is of the given severity or higher. A CI step then goes red without any output parsing. The default,
none, preserves the historical always-zero exit code that existing scripts rely on. Gating is strictly opt-in.--baseline/--baseline-write--baseline-write <file>records the current findings as a SARIF snapshot. A later run with--baseline <file>suppresses every finding already in the snapshot, in both the report and the gate. A pull request then fails only on newly introduced issues. Findings are matched by rule id, file, message and the offending source line, never by line number. The baseline therefore survives edits that shift code around. Use the same--report-uri-basewhen you write and when you check, so paths compare equal.
--severity filters findings before both the report and the gate.
The document and the exit code then show exactly what you ask to see.
The SARIF driver also carries a rules[] catalog (one entry per
distinct rule, with its description), which GitHub shows alongside each
annotation.
Quick examples¶
# SARIF to a file, fail the step on High or Critical findings
visao --report-format sarif --report-output visao.sarif \
--fail-on high compile_commands.json
# Pure SARIF on stdout (chatter goes to stderr)
visao --report-format sarif compile_commands.json > visao.sarif
# GitLab SAST report
visao --report-format gitlab --report-output gl-sast-report.json \
compile_commands.json
# Scriptable JSON: count findings with jq
visao --report-format json src/*.c | jq '.findings | length'
Ready-made templates for each platform live under integrations/ci/.
GitHub Actions¶
SARIF is native to GitHub: github/codeql-action/upload-sarif renders
findings as inline pull-request annotations and in the Security › Code
scanning tab.
integrations/ci/github/action.ymlA composite action wrapping the contract.
integrations/ci/github/visao-analysis.ymlA complete example workflow: install Visao, run it to
visao.sarif, upload, then enforce the gate.
The essential three steps:
- name: Run Visao
run: visao --report-format sarif --report-output visao.sarif
--report-uri-base "$GITHUB_WORKSPACE"
--fail-on high compile_commands.json
continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: visao.sarif
--report-uri-base "$GITHUB_WORKSPACE" makes the annotations land on
the diff. GitHub maps a finding onto a checked-out file through the
relative path, and a compile_commands.json carries absolute paths.
GitLab CI¶
GitLab does not ingest SARIF, so Visao emits GitLab’s own SAST schema
directly. The artifact, declared under artifacts:reports:sast,
populates the merge-request security widget.
Include integrations/ci/gitlab/visao.gitlab-ci.yml; its core is:
visao-sast:
stage: test
script:
- visao --report-format gitlab --report-output gl-sast-report.json
--fail-on high compile_commands.json
artifacts:
when: always
reports:
sast: gl-sast-report.json
Jenkins¶
The Warnings Next Generation plugin ingests SARIF directly and provides trend graphs, per-file annotations and quality gates.
integrations/ci/jenkins/Jenkinsfile.snippet records visao.sarif through
recordIssues(tools: [sarif(pattern: 'visao.sarif')]) and gates on the
error count. This is also how Visao’s own pipeline can self-check.
Severity mapping¶
The same Visao severity drives every output:
Visao |
SARIF level |
GitLab severity |
|---|---|---|
Critical |
error |
Critical |
High |
error |
High |
Medium |
warning |
Medium |
Low |
warning |
Low |
Paranoid |
warning |
Info |
Diff-time reporting¶
A report for a pull request lists the warnings that the pull request
introduces. The recipe, which all four templates under
integrations/ci implement, is one snapshot and one flag:
On the default branch, record the baseline and publish it as a build artifact:
visao --report-format sarif --report-output visao.sarif \ --baseline visao-baseline.sarif --baseline-write ...
On a pull or merge request, restore the latest default-branch baseline and analyse with it:
visao --report-format sarif --report-output visao.sarif \ --baseline visao-baseline.sarif --fail-on high ...
Findings already in the baseline are dropped before the report is written and before
--fail-onis evaluated, so the gate answers “does this change introduce anything at or above High”, not “does the repository have any”. A missing baseline (the first run, an expired artifact) is not an error: everything is reported, as before.
The baseline is matched on the rule, the file, the message and the
offending source line, never on a line number, so edits above a finding
do not make it look new. The same fingerprint, hashed, is written to every
SARIF result as partialFingerprints["visao/v1"]; GitHub Code Scanning
uses that field to track an alert across commits instead of guessing from
its location. The document also declares $schema for SARIF 2.1.0.