From Static Analysis to SARIF: Enterprise Code Quality
Output formats that integrate with GitHub, Azure, and beyond. Your analyzer is useless if nobody sees the results.
Your code analyzer produces results. Then what? If the output doesn’t plug into the tools your team already uses, nobody sees it. SARIF is the bridge.
I learned this the hard way. I built a connascence analyzer that could detect nine types of code coupling across entire codebases. Beautiful terminal output. Rich tables. Color-coded severity. And absolutely zero adoption, because developers don’t live in terminals anymore. They live in pull requests.
The results had to appear where the work happens. That meant learning SARIF.
What SARIF Actually Is
SARIF — Static Analysis Results Interchange Format — is a JSON-based standard (OASIS SARIF 2.1.0) for representing static analysis results. Think of it as what PDF did for documents: a universal container that any tool can produce and any viewer can consume.
Before SARIF, every analyzer had its own output format. ESLint produced one shape of JSON. Pylint produced another. SonarQube had its own XML. If you wanted to aggregate results from multiple tools, you wrote custom parsers for each one. If you wanted those results in your PR, you wrote custom integrations for each one.
SARIF killed that problem. One format. Multiple producers. Multiple consumers.
The Three Parts That Matter
A SARIF file has a lot of optional fields. Most of them don’t matter for practical use. Three things do.
Rule definitions describe what each analyzer checks for. In the connascence analyzer, I define rules like CON001 for Connascence of Name, CON003 for Connascence of Meaning, all the way through the nine types. Each rule gets an ID, a human-readable description, a severity level, and a help URI pointing to documentation. The rule definition is where you encode your expertise. It’s the difference between “violation on line 47” and “Connascence of Algorithm detected: this module shares a serialization format with utils.py — changes to either side must be synchronized.”
Severity mapping translates your internal risk model into a standard vocabulary. SARIF defines three levels: error, warning, and note. I map strong connascence (CoI, CoV) to error, medium connascence (CoA, CoE) to warning, and weak connascence (CoN, CoT) to note. This mapping is opinionated. It has to be. A tool that marks everything as “warning” teaches nobody anything.
Location-based reporting pins each result to a specific file, line, and column. This is what makes SARIF results actionable in a PR. GitHub Code Scanning reads the location data and renders annotations inline — right next to the code that triggered the finding. No context switching. No “go check the build log.” The problem appears where the code lives.
GitHub Code Scanning Integration
GitHub’s code scanning feature consumes SARIF natively. Upload a SARIF file via the github/codeql-action/upload-sarif action, and every finding appears as an annotation on the pull request.
Here’s what that looks like in practice. A developer opens a PR that changes a serialization function. The connascence analyzer runs in CI, detects that three other modules share the same serialization algorithm (CoA), and produces a SARIF result pointing to the changed function. GitHub renders that as an inline annotation: “Connascence of Algorithm — 3 modules depend on this serialization format. Verify compatibility.”
The developer sees this before any human reviewer does. They check the dependent modules, confirm compatibility, and the PR proceeds. Or they realize they need to update the other modules too, and the PR grows to include all the necessary changes. Either way, the coupling was visible at the moment it mattered.
The workflow YAML is minimal:
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: connascence-results.sarif
That’s it. GitHub handles the rendering, the annotation placement, the diff integration. You produce the SARIF file. GitHub does the rest.
Azure DevOps and Beyond
SARIF isn’t GitHub-specific. Azure DevOps consumes it through the SARIF SAST Scans Tab extension. VS Code has the SARIF Viewer extension. JetBrains IDEs can import SARIF results. The format is the constant; the viewers are interchangeable.
This matters for enterprise adoption. Teams using Azure Pipelines get the same inline annotations. Teams using Jenkins can archive SARIF files and view them through the Warnings Next Generation plugin. The analyzer doesn’t need to know which CI system or IDE the team uses. It produces SARIF. The environment consumes it.
GuardSpine’s SARIF Pipeline
In GuardSpine’s codeguard-action, SARIF is one of three output formats. The Action produces evidence bundles (the full audit trail), PR comments (the human-readable summary), and SARIF (the machine-readable results).
The SARIF output serves a different purpose than the evidence bundle. The bundle is for audit — a tamper-evident record of every decision in the review pipeline. SARIF is for development workflow — getting findings in front of developers at the moment they can act on them.
Each risk tier maps to a SARIF severity. L0 changes produce notes. L1-L2 produce warnings. L3-L4 produce errors. GitHub treats errors differently from warnings — they show up with red annotations rather than yellow. That visual distinction maps directly to the risk model. Critical changes look critical.
The rule definitions in CodeGuard’s SARIF output include the risk tier rationale. A developer seeing a SARIF annotation on their auth middleware change doesn’t just see “L4 — Critical.” They see “L4 — Authentication flow modification. Council review required. Evidence bundle: [link].” The SARIF finding becomes the entry point into the full governance trail.
CI/CD Pipeline Integration Patterns
SARIF fits into CI/CD pipelines at three points. Each one serves a different purpose.
Pre-merge gates run the analyzer on PR diffs and upload SARIF. Developers see findings before code lands on the main branch, scoped to the changes in the PR.
Post-merge scans run on the full codebase after merges to main. This catches systemic issues — connascence patterns that emerge from the interaction of multiple changes. Results go into dashboards rather than PR annotations.
Scheduled audits run weekly or monthly full-codebase scans. Total violation count. Violation density per module. Trend over time.
The key insight: SARIF is the same format at all three points. The analyzer doesn’t change. The consumers do.
The Format Decision I Almost Got Wrong
When I first built the connascence analyzer, I generated custom JSON output. It had everything a developer needed — violation type, location, severity, related files. Clean structure. Good naming.
Nobody used it.
The problem was integration friction. Every team that wanted to use the analyzer had to write a custom parser, a custom renderer, and a custom CI integration. Even teams that loved the analysis results bounced off the setup cost.
Switching to SARIF eliminated that friction. Teams with GitHub got instant PR annotations. Teams with Azure got the SAST tab. Teams with VS Code got inline highlighting. The analysis didn’t change. The packaging did. And packaging is what determines adoption.
This is a lesson that applies far beyond SARIF. The best analysis in the world is worthless if the results don’t appear where decisions get made. Format decisions are adoption decisions. Choose the format your users already consume.
Making It Work
If you’re building any kind of code analyzer — linter, security scanner, quality checker — produce SARIF output. Not as an afterthought. As a first-class output format. Define your rules carefully. Map severities deliberately. Include enough location detail for inline rendering.
The SARIF 2.1.0 spec is verbose, but the minimum viable SARIF file is small. A $schema reference, a runs array with one entry, a tool object with your rule definitions, and a results array with your findings. Each result needs a ruleId, a level, a message, and a locations array. That’s enough for GitHub Code Scanning to render useful annotations.
The spec supports much more — code flows, thread flow locations, graphs, taxonomies. You don’t need any of that to start. Ship the minimum. Add richness when you find specific viewer features that warrant it.
Integrating code quality into your CI/CD? I’ve done it at scale: https://cal.com/davidyoussef