# SARIF and Code Scanning

> Write review, guard and leaks findings as a SARIF 2.1.0 log with --sarif and upload them to GitHub Code Scanning. Covers the format, categories, fingerprints, failure modes and limits.

CommitBrief docs · v1.x · Guides

Canonical URL: https://commitbrief.com/docs/1.x/sarif/

---

SARIF (Static Analysis Results Interchange Format) is the JSON format
GitHub Code Scanning and most security dashboards read. `--sarif` makes
a review write one SARIF 2.1.0 log instead of cards, markdown or JSON.
Requires v1.19.0 or later. `guard --sarif` and `leaks --sarif`:
Requires v1.21.0 or later.

**Key facts.** Local boolean flag on the root review, on
`commitbrief diff`, on [`guard`](/docs/1.x/policy-gate) and on
[`leaks`](/docs/1.x/leaks). The review and `guard` need an API
provider. Every command fails closed: a degraded or partial run writes
no file.

## Writing the log

```sh
commitbrief diff origin/main...HEAD --sarif -o commitbrief.sarif
```

Without `-o` the log goes to stdout. It is a renderer choice like
`--json`, so it combines with `--fail-on`, which gates on the same set
the file contains.

## Uploading to GitHub

```yaml
permissions:
  contents: read
  security-events: write

steps:
  - uses: actions/checkout@v4
    with:
      fetch-depth: 0
  - run: commitbrief diff origin/${{ github.base_ref }}...HEAD --sarif -o commitbrief.sarif
    env:
      ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  - uses: github/codeql-action/upload-sarif@v4
    if: always()
    with:
      sarif_file: commitbrief.sarif
      category: commitbrief
```

For `guard` and `leaks`, use the category from the table in
[Categories](#categories).

`security-events: write` is required; private repositories also need
`actions: read`. Code Scanning availability depends on your GitHub
plan and repository visibility; check GitHub's documentation for your
account. Use a distinct `category` so CommitBrief results do not
replace those of another tool.

## What each result carries

| SARIF field | Value |
|-------------|-------|
| `ruleId`, `ruleIndex` | The finding's [rule id](/docs/1.x/rules) and its index in `tool.driver.rules[]` |
| `level` | `critical` and `high` map to `error`, `medium` to `warning`, `low` and `info` to `note` |
| `rank` | 90, 70, 50, 30, 10 by severity. GitHub ignores it. |
| `message.text` | `Title — Description`, plus `Suggestion: …` on a new line |
| `locations[].physicalLocation` | Repo-relative forward-slash URI with `uriBaseId` `%SRCROOT%`, `startLine`, `endLine` |
| `partialFingerprints["commitbrief/v1"]` | The finding's [`fingerprint`](/docs/1.x/output-formats#field-reference) |
| `properties["commitbrief/severity"]` | The original five-level severity |

`tool.driver.rules[]` lists all 30 rules, each with a short definition,
a default level, tags (the category, plus `security` for `sec.*`) and a
help link to the [rule list](/docs/1.x/rules). `automationDetails.id`
is `commitbrief/` for the review (see [Categories](#categories) for
`guard` and `leaks`). Because the fingerprint ignores line numbers,
Code Scanning keeps tracking an alert when surrounding code moves.

A finding with no usable line (line 0 or less) is anchored on line 1
and marked `properties["commitbrief/file-level"] = true`.

## Categories

Each command writes its own `automationDetails.id`, and each should be
uploaded with the matching `category`:

| Command | `automationDetails.id` | Upload `category` |
|---------|------------------------|-------------------|
| Root review and `diff` | `commitbrief/` | `commitbrief` |
| `guard` | `commitbrief/guard/` | `commitbrief/guard` |
| `leaks` | `commitbrief/leaks/` | `commitbrief/leaks` |

Code Scanning treats a result that is missing from a category as fixed.
If the three commands shared one category, a clean `leaks` run would
close the alerts a review had opened, and the reverse. Separate
categories keep each command's alerts independent.

## Policy gate: `guard --sarif`

Requires v1.21.0 or later.

```sh
commitbrief guard --sarif -o guard.sarif
```

The log holds only the findings that cause a violation under your
`.commitbrief/policy.yml`. It is built from the same set the verdict is
computed from, so the file and the exit code cannot disagree. When the
`total` cap is exceeded, every finding not waived by `rules.allow` is in
it; waived findings never are. A passing run writes a valid
log with `results: []`, which clears old `commitbrief/guard/` alerts.

It fails closed like the review: when the model response could not be
parsed into structured findings, whether in a live run or from
`--from-json` input, no file is written and the command exits 1. Re-run
the review. See [Policy gate](/docs/1.x/policy-gate).

## Secret scan: `leaks --sarif`

Requires v1.21.0 or later.

```sh
commitbrief leaks --sarif -o leaks.sarif
```

Every result uses the rule id `sec.hardcoded-secret`. A result never
contains the matched text, a snippet, an author or a fingerprint. The
names of the matching patterns are in `properties["commitbrief/patterns"]`,
and for a finding in history the short commit hash is in
`properties["commitbrief/commit"]`.

Because Code Scanning closes alerts that are absent from the upload,
`leaks --sarif` only writes a log for a complete scan:

- It refuses to combine with `--no-worktree`, `--no-history`, `--file`,
  `--dir`, `--exclude-file`, `--exclude-dir`, `--author`, `--committer`,
  `--start-date`, `--end-date`, `--text` or a git range. That is a usage
  error.
- If the history scan stops at the `--max-commits` window, no file is
  written and the command exits 1. Pass a larger window with
  `--max-commits N` and re-run.
- If the history is longer than the commit walk can cover, no file is
  written and the command exits 1. `--max-commits` cannot raise that
  limit.

The human report moves to stderr, and `--fail-on` still decides the
exit code, so a run with findings writes the log and exits 1. `--sarif`
does not combine with `--json` or `--patterns`. See
[Credential audit](/docs/1.x/leaks#sarif-output).

## Failure modes

| Situation | Result |
|-----------|--------|
| Empty diff after filtering | A valid log with `results: []`, so a clean run still clears old alerts |
| Degraded or unparsed model response, fresh or from cache | Error, no file written, exit 1 |
| CLI-backed provider (`claude-cli`, `gemini-cli`, `codex-cli`) | Refused before any call: these emit plain text, not findings |
| `--json`, `--markdown`, `--suggest-commit`, `--cli` or `--show-prompt` together | Refused as conflicting output flags |
| `guard` or `leaks` with a degraded or partial run | Error, no file written, exit 1; see the sections above |

The degraded case is deliberate: a half-parsed review uploaded as
clean would close real alerts. On a fresh degraded run the cache is
still written.

## Limits

- `--min-severity` does not filter SARIF. Every finding is in the file.
- `guard --sarif` lists violating findings only; the other commands list every finding.
- Severity and rule choice come from the model. A rule id the model
  gets wrong is still a wrong alert; see
  [When the model picks the wrong id](/docs/1.x/rules#when-the-model-picks-the-wrong-id).
- The `rank` field is informational only.

## See also

- [Rule IDs](/docs/1.x/rules) — the closed list behind `ruleId`.
- [Gate CI on review findings](/docs/1.x/ci-gate) — `--fail-on` and `guard`.
- [Output formats](/docs/1.x/output-formats) — cards, markdown and JSON.