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-format

Selects 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 for jq and 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 ruleId and helpUri, the JSON report uses the same two names, and the GitLab report uses its links array. * text (default): the human console rendering.

--report-output

Writes 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-base

Directory 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 uri is relative to the repository, and a compile_commands.json usually carries absolute paths. Point this flag at the checkout root ($GITHUB_WORKSPACE, $CI_PROJECT_DIR or $WORKSPACE) and the annotations land. A leading ./ is always stripped.

--fail-on

Makes 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-base when 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.yml

A composite action wrapping the contract.

integrations/ci/github/visao-analysis.yml

A 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:

  1. 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 ...
    
  2. 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-on is 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.