The formats
VEX and SPDX
vex emits an OpenVEX 0.2.0 document over the CVE
overlay. Every statement is affected, and not_affected is unreachable by
construction: AIROM does no reachability analysis, so it knows a component is
present at a version an advisory lists as vulnerable and nothing more. An
all-clear it cannot justify is the one output that would make you stop looking.
The upstream fixed version goes in action_statement, never a fixed status —
that would assert your product is remediated when it is still running the
vulnerable release.
spdx emits an SPDX 3.0.1 JSON-LD
graph. Take it when your downstream tooling ingests SPDX — and know what you are
giving up. It is the lossiest format AIROM emits, and the loss lands squarely on
the thing AIROM exists to record: SPDX 3.0.1 has no slot for an occurrence,
so no file:line evidence survives. Each package states its occurrence count
in a comment so you know what you are missing, but if you need the evidence
itself, take CycloneDX or native JSON.
What it does carry: models as ai_AIPackage, datasets as
dataset_DatasetPackage, suppliers as Organization elements, licenses as
hasDeclaredLicense edges, model retirement as validUntilTime, and both
overlays through the Security profile. Required fields on the AI and Dataset
classes follow SPDX’s NOASSERTION discipline rather than being omitted — a
consumer can tell “we looked and do not know” from “this tool never looked”.
json and yaml are the two lossless formats — they carry the whole
Inventory, including relationship evidence that CycloneDX and SARIF have no
place to put. If you need to keep everything, keep one of those.The default: table
With no output flag, AIROM writes a table to stdout.Scan the working directory
KIND, NAME, VERSION,
PROVIDER, CONF, LOCATION, EVIDENCE — plus a VULN column (top severity +
count) when a scan surfaces CVEs, followed by a per-CVE detail table.
LOCATION is the primary sighting (path:line); EVIDENCE is the total
occurrence count (2 occ), not a file list. --wide expands every occurrence
below the table. Empty scalars render as -. The scan-root application component
is omitted from the table — it is metadata, not a finding — so the component
count covers findings only.
If nothing matches, the table degrades to a single line:
--stats or the JSON output for the detail.
Multi-output: one scan, several files
-o/--output takes fmt[=path] and is repeatable. Omit =path and the format
goes to stdout.
Emit CycloneDX and SARIF in one pass
bom.json and scan.sarif are two projections of one
graph, so they can never disagree about what was found — no second traversal, no
re-detection, no skew.
A file plus a table on stdout
--format, the single-format alias
--format is a convenience spelling for a single format on stdout. These are
equivalent:
flags > AIROM_* env > .airom.yaml), and within one layer an output list
beats the single-format alias.
Both spellings work as env vars
Writers are pure projections
This is an architectural invariant, not a stylistic claim, and it has consequences worth relying on:Deterministic
The assembler sorts everything before a writer runs. Two scans of an unchanged
tree produce byte-identical output. YAML and JSON diff cleanly in review.
(
--stats is the exception: timings are volatile by nature.)No format-only facts
A writer renders nothing the graph lacks. If a field is missing from
CycloneDX, it is missing from the graph — not dropped by the writer.
Consistent across formats
The same component, confidence, and evidence appear in every format you ask
for, because they are read from one assembled inventory.
Cheap to add formats
A format is a function from inventory to bytes. Adding one cannot change what
a scan finds.
Format-specific flags
--stats only shows up in json and yaml — they are the formats that
serialize ScanStats. Without it, the stats block is trimmed to
filesWalked, filesProcessed and filesFailed; the volatile timing and
per-detector numbers are dropped so the output stays reproducible. The table,
CycloneDX and SARIF writers never render the stats block either way.--min-confidence filters at the presentation layer. On general-purpose
directories, --min-confidence 0.8 is the practical setting — see
/concepts/confidence.Findings are not failures
Every format exits0 on a completed scan, however many components it found.
Output selection has no bearing on exit status; only --fail-on (and
--exit-code) create a gate. See /reference/cli.