Learn more about this client-side SBOM analysis tool
SBOM Play is a client-side web application for analyzing Software Bill of Materials (SBOM) data from GitHub repositories, organizations, and users. Built for security professionals to identify dependency vulnerabilities, assess license compliance, and understand software supply chain risks in real-time.
The tool features comprehensive SBOM analysis including dependency tracking, vulnerability detection via OSV.dev integration, license compliance checking, author analysis with funding detection, and SBOM quality assessment.
Key Principle: All analysis happens directly in your browser - no data ever leaves your machine.
SBOM Play is designed with privacy and security as top priorities:
For security-conscious environments, airgapped networks, or self-hosted deployments, the following domains must be allowed through your firewall or proxy. Domains are grouped by functionality and criticality.
| Domain | Purpose |
|---|---|
cdn.jsdelivr.net | Bootstrap CSS/JS, marked.js, DOMPurify, js-yaml, Chart.js |
cdnjs.cloudflare.com | Font Awesome icons |
unpkg.com | Leaflet maps library |
| Domain | Purpose |
|---|---|
api.github.com | REST API and GraphQL API for SBOM fetching |
raw.githubusercontent.com | Raw file access for FUNDING.yml info |
github.com | User-facing links (non-essential for analysis) |
| Domain | Purpose |
|---|---|
api.osv.dev | OSV vulnerability lookups |
| Domain | Purpose |
|---|---|
registry.npmjs.org | npm package metadata (authors, funding, repository URL) |
pypi.org | PyPI package metadata (authors, project URLs) |
crates.io | Cargo/Rust package metadata |
rubygems.org | RubyGems package metadata |
packages.ecosyste.ms | Cross-ecosystem package metadata (Maven, NuGet, Go, etc.) — used for ecosystems where the native registry blocks browser CORS (Maven Central) or has no JSON API |
api.deps.dev | Google deps.dev API — licenses, version graphs, and labeled SOURCE_REPO / HOMEPAGE / ISSUE_TRACKER links for every supported ecosystem |
| Domain | Purpose |
|---|---|
tile.openstreetmap.org | Map tiles for author locations |
nominatim.openstreetmap.org | Geocoding service |
| Domain | Purpose |
|---|---|
plausible.io | Privacy-focused analytics (optional) |
Every dependency in a portfolio is classified per consuming repository as either direct (the repo's own code declares it) or transitive (the repo only inherits it through the dependency tree of something else it declared). The same package can be direct in one repo and transitive in another. SBOM Play computes this attribution centrally with a deterministic two-pass algorithm so every page that splits Direct vs Transitive — Licenses, Vulnerabilities, Dependencies, Findings — reads from the same source of truth.
package.json, pom.xml,
requirements.txt, or .github/workflows/*.yml) — the team
consciously brought it in.
For every repository in the portfolio, walk that repo's SBOM-declared dependencies and
consult its directDependencies set (built from the SBOM's
DEPENDS_ON relationships from the root package). Each
(dep, repo) pair is added as direct in that repo if the
SBOM declared it as a top-level edge from the repository, and as
transitive in that repo otherwise.
Some transitives never appear in the SBOM at all — they're discovered later when SBOM Play
walks each direct dep's full version graph through the registry resolver
(deps.dev,
packages.ecosyste.ms).
For every repo we BFS from its direct seed set through each dep's child registry edges
and mark every reached (dep, repo) pair transitive in that repo. Pass 1
always wins on direct classification — Pass 2 never overwrites a direct mark with a
transitive one.
directIn / transitiveIn inside each
ecosystem's resolver loop. When npm and Maven resolved concurrently, a Maven dep with no
SBOM-listed repos got attributed to repos that only had npm direct deps (cross-ecosystem
leak). The per-repo BFS scope is naturally per-ecosystem because dep.children
only contains same-ecosystem children, so this class of leak is impossible by construction.
GitHub's dependency-graph SBOMs flatten the tree — every DEPENDS_ON
relationship goes from the repo's main package, so the SBOM marks
every listed dep as a direct dep of the repo, including transitives. If we
only trusted the SBOM (Pass 1), the Direct/Transitive split for GitHub-fetched
portfolios would be meaningless. Pass 2's BFS through resolver-discovered edges is what
recovers the real shape — anything reached only via dep.children is
transitive, regardless of what the flat SBOM said.
For organisation-wide aggregations, a dep that's transitive in every repo it touches can still be considered "imputed-directly used" by the org if the package is also directly declared in any other repo in the portfolio (or a sister portfolio sharing the same lockfile family). This is a forward-looking concept that the forthcoming Insights page surfaces — it gives M&A and CTO audiences a more honest picture of "what the org actually owns" beyond SBOM-listed direct edges.
The Insights page computes a single Tech-Debt composite score (0-100, higher is healthier) from six weighted sub-components. Direct dependencies carry 3× weight compared to transitive ones, reflecting the higher risk and remediation urgency of code you directly control.
| Component | Weight | What it measures |
|---|---|---|
| Version Drift | 30% | Major/minor updates behind, 3× weight for direct deps |
| Vulnerabilities | 30% | Severity-weighted CVE count (C=30, H=12, M=3 for direct; C=10, H=4, M=1 for transitive) |
| Package Age | 15% | Fraction of deps with last release > 2 years ago |
| License Risk | 10% | Fraction of deps with copyleft/high-risk licenses |
| EOL Risk | 10% | Fraction of deps flagged as probable EOL (staleness heuristic or endoflife.date) |
| Repo Hygiene | 5% | Fraction of repos with D/F SBOM grade or missing SBOM |
| Grade | Health Score Range |
|---|---|
| A | 90 – 100 |
| B | 75 – 89 |
| C | 55 – 74 |
| D | 35 – 54 |
| F | 0 – 34 |
Charts on the Insights page are rendered with Chart.js 4.4,
loaded from the jsdelivr CDN. Theme-aware colors are driven by --chart-text-color
and --chart-grid-color CSS custom properties.
SBOM Play uses multiple data sources and heuristics to identify end-of-life (EOL) and end-of-support (EOS) dependencies. Understanding which software components are no longer maintained is critical for supply chain security.
We query the endoflife.date API for official end-of-life information. This database tracks EOL dates for major software products including:
When official EOL data is unavailable, we use staleness analysis to infer probable abandonment. Research shows that packages without updates for extended periods are likely unmaintained.
| Threshold | Classification | Rationale |
|---|---|---|
| 24+ months (2 years) | Probable EOL | Most active packages release at least once per year. 2 years of inactivity suggests abandonment. |
| 36+ months (3 years) | Highly Likely EOL | Very high confidence of abandonment. Security vulnerabilities likely unpatched. |
Note: This heuristic has limitations. Some stable packages (e.g., small utilities) may be "complete" and not require updates. We recommend manual review for probable EOL findings.
When a GitHub repository is archived, it's a clear signal from the maintainer that the project is no longer active. We detect dependencies whose source repositories have been archived and flag them accordingly.
Dependency confusion (also known as "namespace confusion" or "substitution attacks") is a supply chain attack where an attacker publishes a malicious package to a public registry with the same name as an internal/private package. When developers or CI/CD systems resolve dependencies, they may inadvertently download the malicious public package instead of the intended private one.
SBOM Play uses a multi-layered approach to detect potential dependency confusion vulnerabilities in your SBOMs:
| Detection Type | Severity | Description |
|---|---|---|
| Namespace Not Found | HIGH |
The namespace/organization (e.g., @mycompany/ in npm or com.mycompany in Maven)
doesn't exist in the public registry. An attacker could register the entire namespace and publish
malicious packages. This is the highest-confidence indicator.
|
| Package Not Found | MEDIUM | The specific package doesn't exist in the public registry. Could be a private/internal package that an attacker could claim by publishing a package with that name. |
| GitHub Action Missing | HIGH | For GitHub Actions, the owner/organization doesn't exist on GitHub. An attacker could create the organization and publish a malicious action. |
SBOM Play checks packages against 36+ public registries via the ecosyste.ms API, plus GitHub for Actions:
@scope/package) to identify namespaces@mycompany/) for all internal packagesFor every package in the SBOM, SBOM Play attempts to attribute a list of authors — the humans (and bots) whose accounts can ship code into the package. This list is what powers the Authors page, the geographic risk map, the sanctioned-country detector, and the contributor correlation in the Findings report. Because no single registry exposes all the information we need, the pipeline draws from up to three sources per package, in a fixed precedence order, and every API call is made once per package and reused.
| Order | Source | What it gives us | Applies to |
|---|---|---|---|
| 1 | Native registry (npm, PyPI, crates.io, RubyGems) | Authoritative author / maintainers field, declared by the publisher |
Ecosystems with a public per-package authors API |
| 2 | packages.ecosyste.ms | Aggregated maintainers / owners + repository_url + homepage |
Maven, NuGet, Go, Composer (no native authors API) |
| 3 | GitHub /repos/{owner}/{repo}/contributors (top 10) |
Tentative author list derived from commit-history contributors of the source repository | Any package whose source URL points to github.com |
Sources 1 and 2 are mutually exclusive (we use whichever is available for the ecosystem). Source 3 runs in addition to whichever of 1 or 2 returned data — registries publish only the maintainer-of-record, but real-world risk lives in the broader contributor population that can land code through pull requests.
For a Maven artifact like org.springframework.boot:spring-boot-starter-web, the registry
publishes a single "Pivotal" maintainer entry. The actual humans who can merge code into that
package are the contributors of spring-projects/spring-boot on GitHub — currently
around 1,000 people, of whom we capture the top 10 by contribution count. These are tentative
correlations (we have no email/identity authority for them), so they're flagged with a
⚠️ badge in the Authors page so you can distinguish
them from registry-attested maintainers.
Per-run we issue exactly one /repos/{owner}/{repo}/contributors REST
call per unique GitHub repository, deduped by an in-memory cache keyed on
${owner}/${repo}. Profile fields (location, company, GitHub user type) are filled in
afterwards by a single batched GraphQL query — never per-user REST calls. So a 159-package Spring
Boot SBOM where every package points at spring-projects/spring-boot issues
1 contributors call and a handful of GraphQL profile calls, not 159 + 1,590.
spring-projects/spring-boot, babel/babel,
angular/angular, …), all of those packages will list the same set of contributors as
authors. This is intentional, not a bug.
From a threat-model perspective, those packages share a single supply-chain blast radius. A contributor with merge rights to the shared repository can land malicious code into every package that ships from it, in a single commit. Listing the contributors once per package (rather than once per repository) makes that fan-out explicit on the Authors page: an author shown as having "23 packages" across "1 repo" is actually a single point of failure for 23 packages, and that's exactly what the Repository Usage column on the Authors page surfaces with the High Risk / Moderate Risk badges.
The downside is visual repetition — a Spring-heavy SBOM will show the same ~10 Pivotal employees on every Spring artifact. The upside is risk fidelity: the count of packages those employees appear on directly reflects the number of packages an account compromise would compromise.
GitHub contributors include automated accounts: dependabot[bot],
github-actions[bot], renovate[bot], pre-commit-ci[bot], and
similar. We do not filter them out at the enrichment stage — instead the Authors page detects bot
accounts (by the canonical [bot] suffix, GitHub's type: 'Bot' profile
field, and a denylist of common automation accounts) and routes them to a dedicated
Active Bots in the Environments section. The human authors table stays clean,
while the bot inventory remains visible — bots that can ship code through PRs are part of the
threat model and worth knowing about.
author and contributors fieldsAuthor / Maintainer fieldsSBOM Play assesses SBOMs against multiple international compliance standards. Understanding these standards helps organizations ensure their SBOMs meet regulatory requirements.
The Cybersecurity and Infrastructure Security Agency (CISA) updated SBOM requirements in August 2025, building upon the 2021 NTIA guidelines. Key additions marked with NEW.
| Element | Description | Status |
|---|---|---|
| Software Producer | Entity that creates components (was "Supplier Name") | Renamed |
| Component Name | Name of each software component | Required |
| Component Version | Version identifier | Required |
| Software Identifier | PURL, CPE, OmniBOR, or SWHID | Required |
| Component Hash | Cryptographic fingerprint (SHA-256+) | NEW |
| License Information | Legal terms for each component | NEW |
| Dependency Relationship | Upstream component relationships | Required |
| SBOM Author | Entity that created the SBOM (was "Author of SBOM Data") | Renamed |
| Timestamp | Date and time SBOM was assembled | Required |
| Tool Name | Tool used to generate SBOM | NEW |
| Generation Context | Pre-build, build, or post-build stage | NEW |
The German Federal Office for Information Security (BSI) Technical Guideline provides requirements for SBOMs in the European context, with stricter requirements around cryptographic hashes.
| Requirement | Description |
|---|---|
| SBOM Specification | Must use CycloneDX 1.5+ or SPDX 2.2.1+ |
| Creator Contact | Email or URL required for SBOM creator |
| Component Hash | SHA-256 minimum (stricter than CISA) |
| License Expression | Valid SPDX license expression required |
| SBOM URI | Unique namespace or serial number |
| Source Code URI | Link to component source (where available) |
Our quality scoring is aligned with sbomqs v2.0, an industry-leading SBOM quality assessment tool. The overall score is calculated from 7 weighted categories:
| Category | Weight | What It Measures |
|---|---|---|
| Identification | 12% | Component names, versions, unique identifiers (PURLs, CPEs) |
| Provenance | 15% | SBOM author, timestamp, tool info, supplier data |
| Integrity | 18% | Cryptographic hashes (SHA-256+), digital signatures |
| Completeness | 15% | Dependencies, source URIs, component purpose |
| Licensing | 18% | Valid SPDX expressions, concluded/declared licenses |
| Vulnerability | 12% | Security references, vulnerability tracking readiness |
| Structural | 10% | Spec version, format validity, schema compliance |
SBOM Play has been featured, launched, and presented at the following venues:
Current Version: 0.0.8
The following open-source projects provided insights and inspiration for key features:
This project was developed with the assistance of AI tools, most notably Cursor IDE and Claude Code. These tools helped accelerate development and improve velocity. All AI-generated code has been carefully reviewed and validated through human inspection to ensure it aligns with the project's intended functionality and quality standards.