Beyond inventory, AIROM surfaces an artifact-risk overlay: structural, statically-detected properties of a model artifact — or the code that loads one — that enable code execution or content injection at load time. A poisoned checkpoint, an unsafe deserialization surface. Risks are not a separate security scan bolted on the side: every risk attaches to a component already in the AIBOM and carries the file and offset that prove it.
A risk is suspicion with evidence, never a verdict. Static analysis is evadable by construction, so the absence of risks is not a safety claim — and a flagged risk is not a malware conviction. Read it as “load this in a sandbox and look,” not “this is malware.”

The catalog

Severity is a fixed function of the risk id — never a judgment made at scan time — so output is deterministic. Every risk carries a detail list naming the exact evidence — the dotted callables, the matched gadgets, the specific op — plus the occurrence that located it.
Four of the five inspect an artifact (the bytes of a model file). unsafe-load is a code risk: it rides on the library the call uses (torch), with the call’s file:line as evidence. That path is extensible — see Extending the catalog.

How risks appear in output

Risks project into the top-level vulnerabilities[] array — a non-CVE id with source.name: airom, ratings[].method: other (no CVSS is fabricated), and affects[].ref pointing at the affected component’s bom-ref.
CycloneDX with a vulnerabilities[] block
Legacy airom:pickle.risk / airom:pickle.imports component properties are still emitted alongside the CycloneDX vulnerabilities[] entry for one release, then retire.

Coverage bounds

Artifact-risk scans read a bounded prefix of each file — the same --max-file-size cap (default 1 MiB) every content detector uses, so peak memory stays a function of configuration, not input size. A risk signal placed beyond that bound is not inspected: a GGUF chat_template pushed past the cap by a large preceding vocabulary, or a Lambda buried deep in an oversized Keras config, can evade the scan while a runtime that reads the whole file still executes it. Raise --max-file-size to widen the window. A clean scan of a capped read means “nothing dangerous in the inspected prefix,” not “safe.”

Extending the catalog

Code-level risks extend without Go. Any rule pack can attach a catalog risk to a match with a risk: field:
A rule that attaches a risk
The risk id is validated against the catalog at load time, so a typo fails airom rules lint like any other error. See writing rules.
It stays deterministic and offline — no LLM, no vulnerability database, no network. The same bytes produce the same risks on every run.

Where to next

Exit codes & --fail-on

The gate grammar, including the risk terms.

Evidence model

How every risk is anchored to a file and an offset.