diff --git a/CHANGELOG.md b/CHANGELOG.md index be79c5b..b5f3190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/js/app.js b/js/app.js index 9cd9329..bd4190c 100644 --- a/js/app.js +++ b/js/app.js @@ -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; } diff --git a/js/github-client.js b/js/github-client.js index 2faf3c3..1079a6b 100644 --- a/js/github-client.js +++ b/js/github-client.js @@ -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) { @@ -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) {