Inspiration

Supply chain attacks kept getting worse and the industry's response was to mandate SBOMs. Fine. But nobody solved the next problem: how do you actually validate one? Most teams either skip it or write a one-off script that checks three fields. You get a file, you assume it's good, and you ship.

The real pain is in TPRM - when a vendor hands you an SBOM and you need to know if it's actually useful or just checkbox compliance. There was no tool that could take an arbitrary SBOM, score it honestly against multiple standards, pull live CVE data, and give you something auditable. So I built one.


What it does

Verity is an SBOM validation and risk assessment platform. You give it an SBOM file, it tells you exactly how bad it is and why.

  • Parses CycloneDX (1.0 to 1.7) and SPDX (2.1 to 3.0) across JSON, XML, YAML, tag-value, and RDF formats
  • Scores quality 0 to 10 across 7 weighted categories: Structural, Identification, Provenance, Integrity, License Compliance, Vulnerability and Traceability, and Completeness. Outputs a letter grade A to F
  • Validates against NTIA Minimum Elements, BSI TR-03183-2 (v1.1, v2.0, v2.1), FSCT v3, and OpenChain Telco v1.1. Both pass/fail and continuous 0 to 10 profile scores
  • Pulls live vulnerability data from OSV.dev, CISA KEV, and EPSS. NVD enrichment is optional
  • Scores risk per component based on missing fields, license type (AGPL/GPL/LGPL tiers), and CVE severity
  • Flags malicious packages via OSV MAL- advisories and detects EOL/EOS components via endoflife.date
  • YAML policy engine that gates on license type, CVE severity, KEV status, score thresholds, or component names
  • Workspace support for teams, scan history, aggregate analytics, and export to PDF and JSON
  • CLI for CI/CD pipelines with configurable fail thresholds
  • Web UI built in React with full quality breakdown, compliance panel, component tables, and export buttons

How we built it

Backend is Python 3.11, FastAPI, and async SQLAlchemy. The parser normalizes both CycloneDX and SPDX into a shared internal model before anything else touches it. Scoring is a weighted engine with 7 base categories. Component Security Health is appended separately when vulnerability data is available so it doesn't distort the base score.

Compliance checkers are independent modules per standard. Each one knows its own field-level rules, version differences, and format-specific N/A conditions. The scored profiles sit on top and compute weighted continuous scores, excluding N/A checks from the denominator so the number is actually meaningful.

Vulnerability enrichment runs async. OSV batch queries, KEV lookups, and EPSS scores happen in parallel. NVD fills CVSS gaps when an API key is provided. The policy engine parses a YAML file post-scan and evaluates rules with glob pattern matching, severity filters, and KEV status checks.

Frontend is React 18, TypeScript, Vite, and Tailwind, reverse proxied through nginx. PDF reports use WeasyPrint with a custom template. Alembic runs migrations automatically on container startup.


Challenges we ran into

SBOM format variance is genuinely painful. CycloneDX and SPDX evolve separately, have overlapping but non-identical semantics, and serialize differently per version. Some features only exist in one format: document signatures are CDX-only, BOM links are CDX-only, license acknowledgements are a BSI v2.1 specific concept. Getting the parser to handle CDX 1.0 through 1.7 and SPDX 2.1 through 3.0 without losing spec-specific nuance took a lot of careful spec-reading.

The compliance standards are also genuinely ambiguous. BSI TR-03183-2 has three active versions with different SHALL/SHOULD tiers. Some requirements are format-specific. Modeling this so a check correctly returns N/A instead of FAIL when the format makes it inapplicable required real design work, not just a lookup table.

Async vulnerability enrichment had its own edge cases. Some components have no PURL, some PURLs 404, some OSV responses are partial. Handling failures gracefully without silently dropping data was harder than the happy path.


Accomplishments that we're proud of

Every deducted point in the quality score traces back to a specific field gap or compliance miss. It's visible in the UI and in the PDF. No black box scores.

The continuous compliance profile scoring was something I wanted from the start. Pass/fail is too binary for supplier management. You want a number you can track over time as the supplier improves their tooling.

The policy engine ended up being genuinely flexible. A single YAML file can gate a CI pipeline on license risk, KEV status, EPSS score, or quality threshold. Teams can codify their TPRM requirements once and apply them everywhere.

Malicious package detection via OSV MAL- advisories was a late addition but an important one. Pure CVE scanning misses typosquatting and supply chain injection. Flagging these in the Component Security Health category makes them visible without mixing them into the CVE risk score.


What we learned

Real SBOMs are much worse than the standards assume. Even SBOMs from major tooling often fail basic NTIA checks. Missing supplier fields, no PURLs, no dependency relationships. The sample SBOMs in the repo are intentionally bad because that's what you actually see.

Compliance and quality are not the same thing and conflating them is a mistake. A document can pass NTIA and still score D because provenance data, checksums, and lifecycle fields are absent. Keeping these as separate scoring axes gives teams a clearer picture of what actually needs fixing.


What's next for Verity SBOM Validator

  • VEX ingestion to suppress known-not-affected CVEs from risk scores
  • SBOM diff between two scans of the same component across supplier releases
  • Supplier portal where vendors can self-submit against a configured policy baseline before delivery
  • GitHub Actions integration for SBOM upload and policy gates in CI
  • SLSA provenance correlation to link components to build attestations

Built With

Share this project:

Updates