SARIF puts AI inventory where engineers already look: the code-scanning tab, the PR annotation, the IDE problems pane. AIROM emits SARIF 2.1.0 — one rule per detector, one result per occurrence.
Emit SARIF
AIROM’s SARIF results are inventory, not alerts. A hosted-llm result says “this file references gpt-4.1,” not “this is a vulnerability.” That is why the default level is note — see Levels.

Document shape

scan.sarif (abridged)
One run per scan. tool.driver.semanticVersion and airom:tool.commit are the same ToolInfo that airom version prints and that every AIBOM carries.

Rules: one per detector

Each distinct detector that produced an occurrence becomes one rule. The ruleId is the detector ID itself — stable, so a rule keeps its identity across runs and releases.
a rule
name is the detector ID in UpperCamelCase — every non-alphanumeric character is a word boundary, so rules/openai/model-literal becomes RulesOpenaiModelLiteral. airom:method carries the detection method (source-code-analysis, binary-analysis, manifest-analysis, …). Rules are sorted by ID. Only detectors that actually fired appear — the rule list describes the run, not the catalog. Use airom detectors list for the catalog.

Results: one per occurrence

A component seen at twelve call sites produces twelve results, each anchored to its own location. The component is the unit of inventory; the occurrence is the unit of a SARIF result.
a result
The property bag carries airom:componentId (the graph anchor, shared by every result for one component), airom:kind, airom:confidence and airom:occurrence.confidence as JSON numbers, plus airom:provider, airom:purl and airom:pickle.risk when known. The scan-root application component produces no results — it is metadata, not a finding. A whole-file sighting emits a physicalLocation with no region rather than claiming line 0. Results are ordered by component ID, then path, then line, then detector, so the file is deterministic across runs.

Levels: note vs informational

GitHub Code Scanning ignores kind: "informational" results — they never appear in the UI. level: "note" renders. The default is chosen so the output is useful where it is actually consumed.
The two are exclusive per result: the default sets level and omits kind; the strict flag sets kind and omits level.
defaultConfiguration.level on each rule stays "note" in both modes. --sarif-strict-kinds changes the encoding of results only.

Risk results

Inventory results are informational, but artifact risks are not — they are the one thing in a SARIF projection meant to fail a review. Each risk becomes a dedicated risk/<slug> rule (e.g. risk/pickle-import) whose result carries a real severity levelerror for high, warning for medium — and a security-severity property so GitHub Code Scanning files it under Security, not a note. A poisoned checkpoint then surfaces as a security alert on the pull request that introduced it, anchored at the artifact’s file:line.
These carry a severity level regardless of --sarif-strict-kinds: a risk is a finding, not an inventory fact, so it is never downgraded to kind: "informational".

partialFingerprints

The fingerprint is sha256(detectorID | componentID | path), lowercase hex.
The line number is deliberately not in the recipe. Insert twenty lines above a model="gpt-4.1" literal and the fingerprint is unchanged, so GitHub tracks it as the same finding instead of closing one alert and opening another. Code motion does not churn your alert history. Moving the reference to a different file does produce a new fingerprint — that is a genuinely different sighting.

Roots and provenance

originalUriBaseIds.SRCROOT anchors artifact URIs to a filesystem root. It is emitted only when the target is a real path: always for a directory scan, and for a repo scan only when the target is a local worktree. For a remote repo there is no local root to anchor to, so SRCROOT is omitted. Independently of that, whenever the scan knows a git remote — a local worktree with a remote configured, or a remote URL — provenance travels as:

Unknowns are not results

A file AIROM could not fully process is not a finding — it is a gap in the scan, and it surfaces as a notification rather than being silently dropped or misfiled as a result.
executionSuccessful is true for any completed scan, unknowns included. A scan that finished is a scan that succeeded.

Upload to GitHub Code Scanning

1

Grant the workflow permission

security-events: write is required to upload SARIF.
2

Scan and emit SARIF

Findings are not failures — airom scan exits 0 no matter how much it finds, so the upload step always runs.
3

Upload the file

Use github/codeql-action/upload-sarif.
.github/workflows/aibom.yml
--min-confidence 0.8 is worth setting for Code Scanning. Extension-only dataset detection and keyword-only ai-config detection emit low-confidence (0.5–0.6) results on general-purpose repositories; without the filter, that noise lands in your security tab. See /concepts/confidence.
category: airom keeps AIROM’s results in their own namespace, so uploading them never disturbs alerts from other scanners in the same repository.

Adding a gate

Uploading SARIF does not fail a build. To make a build fail on a policy, use --fail-on in the same run:
The SARIF file is still written before the non-zero exit, so put the upload step under if: always() if you want the results even on a failing gate. See /reference/cli for the --fail-on grammar and exit codes.