Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/deploy-github-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Don't fetch history, only need current state
fetch-depth: 1
Expand Down Expand Up @@ -58,8 +58,9 @@ jobs:
cp findings.html _site/
cp feeds.html _site/
cp insights.html _site/
cp insights-agent.html _site/
cp demo.html _site/
echo "✅ Copied 14 HTML files"
echo "✅ Copied 15 HTML files"

# Copy JavaScript, CSS, images, and bundled demo SBOM JSON
cp -r js _site/
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/validate-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1

Expand All @@ -40,6 +40,7 @@ jobs:
"findings.html"
"feeds.html"
"insights.html"
"insights-agent.html"
"demo.html"
)

Expand Down Expand Up @@ -112,6 +113,7 @@ jobs:
"js/common.js"
"js/dependency-tree-resolver.js"
"js/country-data.js"
"js/city-data.js"
"js/depconfuse-service.js"
"js/deps-page.js"
"js/ecosystem-utils.js"
Expand Down Expand Up @@ -149,6 +151,8 @@ jobs:
"js/upload-page.js"
"js/insights-aggregator.js"
"js/insights-page.js"
"js/insights-agent.js"
"js/ai-nano.js"
"js/demo-page.js"
)

Expand Down
39 changes: 37 additions & 2 deletions CHANGELOG.md

Large diffs are not rendered by default.

165 changes: 157 additions & 8 deletions about.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<title>SBOM Play - About</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="css/style.css?v=0.0.9&cb=1780000000000" rel="stylesheet">
<link href="css/themes.css?v=0.0.9&cb=1780000000000" rel="stylesheet">
<script src="js/theme-manager.js?v=0.0.9&cb=1778000000000"></script>
<script src="js/depconfuse-service.js?v=0.0.9&cb=1778000000000"></script>
<link href="css/style.css?v=0.2.1&cb=1786000000000" rel="stylesheet">
<link href="css/themes.css?v=0.2.1&cb=1786000000000" rel="stylesheet">
<script src="js/theme-manager.js?v=0.2.1&cb=1786000000000"></script>
<script src="js/depconfuse-service.js?v=0.2.1&cb=1785700000000"></script>
</head>
<body>
<a class="visually-hidden-focusable" href="#main">Skip to content</a>
Expand All @@ -35,6 +35,7 @@
<a class="nav-link" href="audit.html">Audit</a>
<a class="nav-link" href="findings.html">Findings</a>
<a class="nav-link" href="insights.html">Insights</a>
<a class="nav-link" href="insights-agent.html">Agent</a>
<a class="nav-link" href="deps.html">Deps</a>
<a class="nav-link" href="repos.html">Repos</a>
<a class="nav-link" href="authors.html">Authors</a>
Expand Down Expand Up @@ -96,11 +97,12 @@ <h5 class="mb-0 d-flex justify-content-between align-items-center">
<li><a href="#how-it-works">How it works</a></li>
<li><a href="#direct-vs-transitive">Direct vs transitive</a></li>
<li><a href="#tech-debt">Tech-debt scoring</a></li>
<li><a href="#risk-portfolio">Risk portfolio scoring</a></li>
<li><a href="#eol-detection">End-of-life detection</a></li>
</ol>
</div>
<div class="col-md-6">
<ol class="mb-0" start="9">
<ol class="mb-0" start="10">
<li><a href="#dependency-confusion">Dependency confusion</a></li>
<li><a href="#author-enrichment">Author enrichment</a></li>
<li><a href="#compliance">SBOM compliance standards</a></li>
Expand Down Expand Up @@ -314,7 +316,8 @@ <h6 class="mt-3"><i class="fas fa-map-marked-alt me-2 text-info"></i>Map Service
</thead>
<tbody>
<tr><td><code>tile.openstreetmap.org</code></td><td>Map tiles for author locations</td></tr>
<tr><td><code>nominatim.openstreetmap.org</code></td><td>Geocoding service</td></tr>
<tr><td><code>geocoding-api.open-meteo.com</code></td><td>Primary geocoding API (fallback after static country/city tables)</td></tr>
<tr><td><code>photon.komoot.io</code></td><td>Secondary geocoding API (failover when Open-Meteo is rate-limited or unavailable)</td></tr>
</tbody>
</table>

Expand Down Expand Up @@ -520,6 +523,107 @@ <h6 class="mt-3">References</h6>
</div>
</div>

<!-- Risk Portfolio Scoring -->
<div class="card mb-4" id="risk-portfolio">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-chart-pie me-2"></i>Risk Portfolio Scoring</h5>
</div>
<div class="card-body">
<p>
At the top of the Insights page, the same underlying metrics that feed the Tech-Debt composite are
re-projected into three <strong>risk domains</strong> aimed at due-diligence and M&amp;A audiences.
Each domain gets an independent 0&ndash;100 score (higher is healthier) and a traffic-light verdict,
while the overall <strong>Tech-Health Score</strong> shown in the header is the Tech-Debt composite
score and grade (see <a href="#tech-debt">Tech-debt scoring</a>).
</p>

<h6 class="mt-3">Domain Scores</h6>
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr><th>Domain</th><th>Formula (starts at 100, penalties subtracted)</th></tr>
</thead>
<tbody>
<tr>
<td><i class="fas fa-shield-alt me-1"></i>Security Posture</td>
<td>&minus;20 per critical vuln, &minus;8 per high vuln, &minus;25 per malware advisory,
and &minus;15 / &minus;8 when the median direct-dep CVE dwell time exceeds 90 / 30 days.</td>
</tr>
<tr>
<td><i class="fas fa-tools me-1"></i>Operational Health</td>
<td>&minus;3 per EOL component, &minus;5 per repo inactive &gt; 1 year, &minus;3 per repo
missing an SBOM (major-drift components are folded in as a partial offset).</td>
</tr>
<tr>
<td><i class="fas fa-gavel me-1"></i>License &amp; Compliance</td>
<td>&minus;8 per high-risk license, &minus;2 per unpinned GitHub Action.</td>
</tr>
</tbody>
</table>

<h6 class="mt-3">Verdict Thresholds</h6>
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr><th>Traffic light</th><th>Penalty (100 &minus; score)</th><th>Overall verdict (Tech-Health)</th></tr>
</thead>
<tbody>
<tr><td><span class="badge bg-success">Good</span></td><td>&le; 30</td><td>&ge; 75: low risk &mdash; standard monitoring</td></tr>
<tr><td><span class="badge bg-warning">Moderate</span></td><td>31 &ndash; 55</td><td>55 &ndash; 74: moderate risk &mdash; plan remediation</td></tr>
<tr><td><span class="badge bg-danger">Needs Attention</span></td><td>&gt; 55</td><td>&lt; 55: elevated risk &mdash; active remediation</td></tr>
</tbody>
</table>

<div class="alert alert-info py-2 mt-3">
<i class="fas fa-info-circle me-2"></i>
<strong>Heuristic weights:</strong> the domain penalty coefficients are tuned for at-a-glance triage,
not a formal risk model. The authoritative single number remains the Tech-Health / Tech-Debt score.
</div>
</div>
</div>

<!-- On-device AI (Gemini Nano) -->
<div class="card mb-4" id="on-device-ai">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-robot me-2"></i>On-Device AI (Gemini Nano)</h5>
</div>
<div class="card-body">
<p>
The <strong><a href="insights-agent.html">Agent</a></strong> page (linked from
<a href="insights.html">Insights</a>) uses Chrome&rsquo;s built-in <strong>Prompt API</strong> with
<strong>Gemini Nano</strong>. All prompts, context, and model output stay on your machine — SBOM Play
does not upload your analysis to a cloud LLM for this feature.
</p>

<h6 class="mt-3">How it works</h6>
<p>
The Agent runs an app-driven tool-use loop: JavaScript executes typed
<code>query_sbom()</code> handlers (vulnerabilities, licenses, drift, EOL, supply chain, etc.)
against in-memory stored analysis, then asks Gemini Nano to summarize those JSON results.
Answers deep-link packages, repositories, authors, and advisory IDs into the matching pages.
</p>

<h6 class="mt-3">Availability</h6>
<p>
Requires a Chromium browser with the Prompt API and Gemini Nano enabled (see
<a href="https://developer.chrome.com/docs/ai/prompt-api" target="_blank" rel="noreferrer noopener">Chrome Prompt API documentation</a>).
When the model is missing or unsupported, the page shows a non-blocking &ldquo;AI not available&rdquo; state;
the rest of SBOM Play continues to work offline from IndexedDB.
</p>

<div class="alert alert-info py-2 mt-3">
<i class="fas fa-shield-alt me-2"></i>
<strong>Privacy:</strong> user and model text is escaped before lightweight markdown rendering in the UI.
The shared adapter (<code>js/ai-nano.js</code>) targets the modern <code>LanguageModel</code> API and
falls back to the legacy <code>window.ai.languageModel</code> where needed.
</div>

<h6 class="mt-3">References</h6>
<ul>
<li><a href="https://developer.chrome.com/docs/ai/prompt-api" target="_blank" rel="noreferrer noopener">Chrome — Prompt API</a></li>
<li><a href="https://developer.chrome.com/docs/ai/get-started" target="_blank" rel="noreferrer noopener">Chrome — Get started with built-in AI</a></li>
</ul>
</div>
</div>

<!-- EOX Detection Methodology -->
<div class="card mb-4" id="eol-detection">
<div class="card-header">
Expand Down Expand Up @@ -839,7 +943,49 @@ <h6 class="mt-4"><i class="fas fa-share-alt me-2 text-success"></i>4. Shared-rep
appear on directly reflects the number of packages an account compromise would compromise.
</p>

<h6 class="mt-4"><i class="fas fa-robot me-2 text-dark"></i>5. Bots and automated accounts</h6>
<h6 class="mt-4"><i class="fas fa-map-marker-alt me-2 text-info"></i>5. Location geocoding</h6>
<p>
GitHub profile <code>location</code> strings are free-form text — "San Francisco, CA", "Germany",
"Bengaluru", or joke entries like "Milky Way". SBOM Play resolves them to an ISO country code for
the Authors map and sanctioned-country checks using a four-step cascade that avoids hammering any
single public geocoder:
</p>
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr><th>Step</th><th>Resolver</th><th>When it applies</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Static country table (<code>country-data.js</code>)</td>
<td>Trailing segment is a country name, alpha-2/3 code, or US state (e.g. "Berlin, Germany")</td>
</tr>
<tr>
<td>2</td>
<td>Static city table (<code>city-data.js</code>, GeoNames cities ≥50k pop.)</td>
<td>Bare major-city names with no cross-country ambiguity (e.g. "Tokyo", "Bengaluru")</td>
</tr>
<tr>
<td>3</td>
<td><a href="https://open-meteo.com/en/docs/geocoding-api" target="_blank" rel="noreferrer noopener">Open-Meteo Geocoding API</a></td>
<td>Remaining strings; primary network fallback, no API key required</td>
</tr>
<tr>
<td>4</td>
<td><a href="https://photon.komoot.io/" target="_blank" rel="noreferrer noopener">Photon (komoot)</a></td>
<td>Failover when Open-Meteo returns no result or is rate-limited</td>
</tr>
</tbody>
</table>
<p class="text-muted small">
<i class="fas fa-info-circle me-1"></i>
All successful and failed resolutions are cached in IndexedDB for 7 days so each unique string is
fetched at most once per session. Network requests are serialized through a shared queue with
per-provider spacing and cooldown on HTTP 429/503. The Authors map itself never triggers geocoding —
it reads only from cache and static tables. Nominatim is no longer used.
</p>

<h6 class="mt-4"><i class="fas fa-robot me-2 text-dark"></i>6. Bots and automated accounts</h6>
<p>
GitHub contributors include automated accounts: <code>dependabot[bot]</code>,
<code>github-actions[bot]</code>, <code>renovate[bot]</code>, <code>pre-commit-ci[bot]</code>, and
Expand All @@ -857,6 +1003,9 @@ <h6 class="mt-4"><i class="fas fa-book me-2"></i>References</h6>
<li><a href="https://packages.ecosyste.ms/docs" target="_blank" rel="noreferrer noopener">ecosyste.ms API Documentation</a> — aggregated package metadata for ecosystems without a native authors endpoint</li>
<li><a href="https://docs.npmjs.com/cli/v10/configuring-npm/package-json#people-fields-author-contributors" target="_blank" rel="noreferrer noopener">npm package.json: <code>author</code> and <code>contributors</code> fields</a></li>
<li><a href="https://packaging.python.org/en/latest/specifications/core-metadata/#author" target="_blank" rel="noreferrer noopener">PyPI core metadata: <code>Author</code> / <code>Maintainer</code> fields</a></li>
<li><a href="https://www.geonames.org/" target="_blank" rel="noreferrer noopener">GeoNames</a> — static major-city table (<code>cities15000</code>, CC BY 4.0)</li>
<li><a href="https://open-meteo.com/en/docs/geocoding-api" target="_blank" rel="noreferrer noopener">Open-Meteo Geocoding API</a> — primary network geocoding fallback</li>
<li><a href="https://photon.komoot.io/" target="_blank" rel="noreferrer noopener">Photon (komoot)</a> — secondary geocoding failover</li>
</ul>
</div>
</div>
Expand Down Expand Up @@ -1049,7 +1198,7 @@ <h5 class="mb-0"><i class="fas fa-tag me-2"></i>Version Information</h5>
</div>
<div class="card-body">
<p>
<strong>Current Version:</strong> 0.0.8
<strong>Current Version:</strong> 0.2.1
</p>
<p>
<a href="CHANGELOG.md" target="_blank" class="btn btn-outline-primary btn-sm">
Expand Down
29 changes: 15 additions & 14 deletions audit.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<title>SBOM Play - SBOM Quality Audit</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="css/style.css?v=0.0.9" rel="stylesheet">
<link href="css/themes.css?v=0.0.9" rel="stylesheet">
<script src="js/theme-manager.js?v=0.0.9&cb=1777494400000"></script>
<script src="js/analytics.js?v=0.0.9&cb=1777494400000"></script>
<link href="css/style.css?v=0.2.1" rel="stylesheet">
<link href="css/themes.css?v=0.2.1" rel="stylesheet">
<script src="js/theme-manager.js?v=0.2.1&cb=1777494400000"></script>
<script src="js/analytics.js?v=0.2.1&cb=1777494400000"></script>
<script defer data-domain="cyfinoid.github.io" src="https://plausible.io/js/script.outbound-links.js"></script>
</head>
<body>
Expand All @@ -36,6 +36,7 @@
<a class="nav-link active" href="audit.html" aria-current="page">Audit</a>
<a class="nav-link" href="findings.html">Findings</a>
<a class="nav-link" href="insights.html">Insights</a>
<a class="nav-link" href="insights-agent.html">Agent</a>
<a class="nav-link" href="deps.html">Deps</a>
<a class="nav-link" href="repos.html">Repos</a>
<a class="nav-link" href="authors.html">Authors</a>
Expand Down Expand Up @@ -183,17 +184,17 @@ <h6 class="text-primary">About Cyfinoid Research</h6>
</div>
</footer>

<script src="js/indexeddb-manager.js?v=0.0.9&cb=1777494400000"></script>
<script src="js/common.js?v=0.0.9&cb=1780200000000"></script>
<script src="js/cache-manager.js?v=0.0.9&cb=1777494400000"></script>
<script src="js/view-manager.js?v=0.0.9&cb=1780200000000"></script>
<script src="js/sbom-processor.js?v=0.0.9&cb=1779000000000"></script>
<script src="js/sbom-quality-processor.js?v=0.0.9&cb=1777494400000"></script>
<script src="js/storage-manager.js?v=0.0.9&cb=1779000000000"></script>
<script src="js/github-client.js?v=0.0.9&cb=1778000000000"></script>
<script src="js/app.js?v=0.0.9&cb=1780100000000"></script>
<script src="js/indexeddb-manager.js?v=0.2.1&cb=1782100000000"></script>
<script src="js/common.js?v=0.2.1&cb=1780200000000"></script>
<script src="js/cache-manager.js?v=0.2.1&cb=1777494400000"></script>
<script src="js/view-manager.js?v=0.2.1&cb=1780200000000"></script>
<script src="js/sbom-processor.js?v=0.2.1&cb=1779000000000"></script>
<script src="js/sbom-quality-processor.js?v=0.2.1&cb=1777494400000"></script>
<script src="js/storage-manager.js?v=0.2.1&cb=1779000000000"></script>
<script src="js/github-client.js?v=0.2.1&cb=1778000000000"></script>
<script src="js/app.js?v=0.2.1&cb=1780100000000"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/audit-page.js?v=0.0.9&cb=1780200000000"></script>
<script src="js/audit-page.js?v=0.2.1&cb=1780200000000"></script>
</body>
</html>

Loading