Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Feeds page removed from top navigation** (`feeds.html`): the page is retained and fully functional but no longer occupies a slot in the header navbar, reducing nav clutter ahead of the upcoming Insights page. Users can still access it via direct URL.

### Fixed
- **GitHub Enterprise "internal" repositories now included in org/user scans** (`js/github-client.js`, `js/app.js`): the repository filter in `getRepositories()` only kept `visibility === 'public'`, so internal repositories (a visibility level GitHub Enterprise offers for org-owned repos — visible to all enterprise members but not public) were silently dropped even when the configured token could see them. The org and user REST paths now accept both `public` and `internal` visibility; since the GitHub API itself only returns internal repos to authenticated callers with access, unauthenticated scans are unaffected. The "no repositories found" alert on `index.html` was reworded to match. Fetching an SBOM for an internal repo still requires a token with access to that repo (existing `fetchSBOM` 401/403 handling covers the failure case).
- **Findings page threw `ReferenceError: Cannot access 'UNPINNABLE_ACTION_RULES' before initialization`** (`js/findings-page.js`): the new rule-classification Set was originally declared as a `const` inside the DOMContentLoaded handler, just before `generateSecurityFindingsHTML`. Function declarations in the same scope hoist their bodies but not their `const` bindings, and the handler's `await loadFindingsData()` call (which transitively calls `generateSecurityFindingsHTML` via the `renderFunction` callback) runs at source-position 184 — well before the `const` at source-position ~294. The function body referenced the binding while it was still in the temporal dead zone, throwing on the first page load. Moved `UNPINNABLE_ACTION_RULES` to module scope (top of `js/findings-page.js`, outside the DOMContentLoaded handler) so the binding is initialised at script-parse time, before any handler runs. Cache-buster bumped on `findings.html`.
- **`window.app` now references the live `SBOMPlayApp` instance** ([`js/app.js`](js/app.js)): page scripts such as [`index-page.js`](js/index-page.js) and the demo page can call `window.app` reliably after initialization.
- **GitHub analysis now persists version drift, staleness, and EOX on each dependency in the saved blob** (`js/app.js`, `js/insights-aggregator.js`, `insights.html`): the post-SBOM path `runLicenseAndVersionDriftEnrichment` → `fetchVersionDriftData` attached drift to the in-memory `allDependencies` array but never mirrored it onto `sbomProcessor.dependencies`, so the following `exportData()` wrote empty `versionDrift` / `staleness` fields — Insights' Package Age and Version Drift charts showed 0% coverage even after a full scan. The flow now copies nested `staleness` onto each dep, calls `EnrichmentPipeline.syncDriftToProcessor` after drift fetch (matching upload / `runFullEnrichment`), and `syncEOXToProcessor` after EOX fetch. `InsightsAggregator` treats `dep.versionDrift.staleness` as a fallback when top-level `dep.staleness` is missing. Removed the temporary Insights-only IndexedDB backfill from `js/insights-page.js` in favour of correct scan-time persistence.
Expand Down
2 changes: 1 addition & 1 deletion js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,7 @@ class SBOMPlayApp {
const repositories = await this.githubClient.getRepositories(ownerName);

if (repositories.length === 0) {
this.showAlert('No public repositories found for this organization or user', 'info');
this.showAlert('No public or internal repositories found for this organization or user', 'info');
this.finishAnalysis();
return;
}
Expand Down
16 changes: 10 additions & 6 deletions js/github-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -759,13 +759,16 @@ class GitHubClient {

if (response.ok) {
const repos = await this.getAllPages(url, response);
// Filter: Only include public repos AND repos owned by this organization
// Filter: Only include public/internal repos AND repos owned by this organization.
// GitHub Enterprise orgs can have "internal" repositories (visible to all
// enterprise members but not public). The API only returns internal repos to
// authenticated callers with access, so no extra auth gate is needed here.
const filteredRepos = repos.filter(repo => {
const isPublic = repo.visibility === 'public';
const isIncluded = repo.visibility === 'public' || repo.visibility === 'internal';
const ownerMatch = repo.owner &&
(repo.owner.login?.toLowerCase() === normalizedOwnerName ||
repo.full_name?.toLowerCase().startsWith(`${normalizedOwnerName}/`));
return isPublic && ownerMatch;
return isIncluded && ownerMatch;
});

if (filteredRepos.length < repos.length) {
Expand Down Expand Up @@ -818,13 +821,14 @@ class GitHubClient {
if (response.ok) {
console.log(`✅ Found user (REST): ${ownerName}`);
const repos = await this.getAllPages(url, response);
// Filter: Only include public repos AND repos owned by this user
// Filter: Only include public/internal repos AND repos owned by this user
// (internal visibility is org-only on GitHub Enterprise, included here for parity)
const filteredRepos = repos.filter(repo => {
const isPublic = repo.visibility === 'public';
const isIncluded = repo.visibility === 'public' || repo.visibility === 'internal';
const ownerMatch = repo.owner &&
(repo.owner.login?.toLowerCase() === normalizedOwnerName ||
repo.full_name?.toLowerCase().startsWith(`${normalizedOwnerName}/`));
return isPublic && ownerMatch;
return isIncluded && ownerMatch;
});

if (filteredRepos.length < repos.length) {
Expand Down