-
Notifications
You must be signed in to change notification settings - Fork 3
Add Dependency Review and Submission #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…ew and submission
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 15 out of 18 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 15 out of 18 changed files in this pull request and generated 15 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ity/github-sbom-toolkit into dependency-review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 15 out of 18 changed files in this pull request and generated 12 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| console.debug(chalk.blue(`Force-submission enabled: submitting component snapshot for ${fullName} branch ${b.name}...`)); | ||
| if (await submitSnapshotIfPossible({ octokit: this.octokit, owner: org, repo: repo.name, branch: b.name, languages: this.opts.submitLanguages, quiet: this.opts.quiet, componentDetectionBinPath: this.opts.componentDetectionBinPath })) { | ||
| // brief delay to allow snapshot ingestion | ||
| await new Promise(r => setTimeout(r, 1500)); |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic number 1500 (milliseconds) for the delay after snapshot submission is hardcoded. This delay allows time for snapshot ingestion before retrying the dependency review diff. Consider:
- Extracting this as a named constant (e.g.,
SNAPSHOT_INGESTION_DELAY_MS) to make its purpose clear - Making it configurable through options if different GitHub instances have different ingestion times
- Adding a comment explaining why this specific duration was chosen
The same applies to the 3000ms delay on line 539.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 15 out of 18 changed files in this pull request and generated 12 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 15 out of 18 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| console.debug(chalk.green(`Fetching branch diff for ${fullName} branch ${b.name}...`)); | ||
| } else { | ||
| console.debug(chalk.yellow(`Skipping branch diff for ${fullName} branch ${b.name} (no new commits).`)); |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The debug logging at lines 303 and 305 uses different colors (green for fetching, yellow for skipping). While this is helpful for human readers, it creates inconsistency with line 286 which uses blue. Consider standardizing debug message colors: e.g., blue for informational, yellow for warnings/skips, green for success, red for errors.
| console.debug(chalk.green(`Fetching branch diff for ${fullName} branch ${b.name}...`)); | |
| } else { | |
| console.debug(chalk.yellow(`Skipping branch diff for ${fullName} branch ${b.name} (no new commits).`)); | |
| console.debug(chalk.blue(`Fetching branch diff for ${fullName} branch ${b.name}...`)); | |
| } else { | |
| console.debug(chalk.blue(`Skipping branch diff for ${fullName} branch ${b.name} (no new commits).`)); |
| @@ -0,0 +1,32 @@ | |||
| # Changelog | |||
|
|
|||
| ## [2025-12-04] – 0.2.0 - Branch scanning and dependency submission | |||
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The CHANGELOG.md file shows a date of [2025-12-04] on line 3, but the current date is December 8, 2025. This appears to be a date in the past (4 days ago). While this could be intentional if the work was completed then, typically changelogs reflect the actual release date. Verify this is the intended date.
| ## [2025-12-04] – 0.2.0 - Branch scanning and dependency submission | |
| ## [2025-12-08] – 0.2.0 - Branch scanning and dependency submission |
| } | ||
| // Validate repo format if provided | ||
| if (options.repo) { | ||
| if (typeof options.repo !== "string" || !options.repo.includes("/")) { |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message at line 54 says 'If specifying "repo", it must be in the format "org/repo".' However, this is checking if the repo option is not a string OR doesn't include a slash. If options.repo is not a string, the error message is misleading because it assumes the value was provided but in wrong format. Consider separate error messages for "repo must be a string" vs "repo must be in format org/repo".
| if (typeof options.repo !== "string" || !options.repo.includes("/")) { | |
| if (typeof options.repo !== "string") { | |
| throw new Error('If specifying "repo", it must be a string.'); | |
| } | |
| if (!options.repo.includes("/")) { |
| let downloadURL: string = ""; | ||
| // TODO: do we need to handle different architectures here? | ||
| // can we allow x64 on MacOS? We could allow an input parameter to override? | ||
| const assetName = process.platform === "win32" ? "component-detection-win-x64.exe" : process.platform === "linux" ? "component-detection-linux-x64" : "component-detection-osx-arm64"; |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In componentDetection.ts at line 318, the platform check uses a ternary for Windows vs Linux vs macOS, but the logic process.platform === "linux" ? "component-detection-linux-x64" : "component-detection-osx-arm64" assumes anything that's not Windows or Linux is macOS. This could fail on other platforms. Consider adding an explicit check for darwin platform or throwing an error for unsupported platforms.
| const assetName = process.platform === "win32" ? "component-detection-win-x64.exe" : process.platform === "linux" ? "component-detection-linux-x64" : "component-detection-osx-arm64"; | |
| let assetName: string; | |
| if (process.platform === "win32") { | |
| assetName = "component-detection-win-x64.exe"; | |
| } else if (process.platform === "linux") { | |
| assetName = "component-detection-linux-x64"; | |
| } else if (process.platform === "darwin") { | |
| assetName = "component-detection-osx-arm64"; | |
| } else { | |
| throw new Error(`Unsupported platform: ${process.platform}`); | |
| } |
| if (args.enterprise && args.org) throw new Error("Specify only one of --enterprise or --org"); | ||
| if (args.repo && (args.enterprise || args.org)) throw new Error("Specify only one of --enterprise, --org, or --repo"); | ||
| if (args.repo && !(args.repo as string).includes("/")) throw new Error("--repo must be in the format owner/repo"); | ||
| if (syncing && !args.sbomCache) throw new Error("--sync-sboms requires --sbom-cache to write updated SBOMs to disk"); |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] In the CLI argument checking at lines 55-66, there's logic validating that --sync-sboms requires either --enterprise, --org, or --repo. However, at line 62, there's an additional check if (syncing && !args.sbomCache) that requires --sbom-cache to be provided when syncing. This check should logically be part of the main syncing validation block starting at line 56, not as a separate check, to keep related validation logic together and improve readability.
| if (syncing && !args.sbomCache) throw new Error("--sync-sboms requires --sbom-cache to write updated SBOMs to disk"); | |
| if (!args.sbomCache) throw new Error("--sync-sboms requires --sbom-cache to write updated SBOMs to disk"); |
| // Some ecosystems require a namespace (e.g., Maven, NuGet) | ||
| const ecosystemsWithNamespace = new Set(['maven', 'nuget', 'composer', 'golang']); | ||
| if (ecosystemsWithNamespace.has(change.ecosystem) && change.namespace) { | ||
| p = `pkg:${change.ecosystem}/${change.namespace}/${change.name}${change.version ? '@' + change.version : ''}`; | ||
| } else { | ||
| p = `pkg:${change.ecosystem}/${change.name}${change.version ? '@' + change.version : ''}`; | ||
| } | ||
|
|
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the malwareMatcher.ts file, lines 162-169 appear to be orphaned code that doesn't belong to any function. These lines seem to be constructing a purl for Maven/NuGet ecosystems with namespaces, but they are placed outside of any function context. This code should either be removed or properly integrated into the enumerateDiffPackages function where it logically belongs.
| // Some ecosystems require a namespace (e.g., Maven, NuGet) | ||
| const ecosystemsWithNamespace = new Set(['maven', 'nuget', 'composer', 'golang']); | ||
| if (ecosystemsWithNamespace.has(change.ecosystem) && change.namespace) { | ||
| p = `pkg:${change.ecosystem}/${change.namespace}/${change.name}${change.version ? '@' + change.version : ''}`; | ||
| } else { | ||
| p = `pkg:${change.ecosystem}/${change.name}${change.version ? '@' + change.version : ''}`; | ||
| } |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The enumerateBasePackages function is annotated to return packages with __branch property (line 150), but the code at line 161 doesn't actually set this property on any returned packages. The comment at line 161 mentions "Annotate with default branch for reporting (if known)" but no annotation code follows. Either the code to set __branch is missing, or the return type annotation should not include __branch for base packages.
| // Some ecosystems require a namespace (e.g., Maven, NuGet) | |
| const ecosystemsWithNamespace = new Set(['maven', 'nuget', 'composer', 'golang']); | |
| if (ecosystemsWithNamespace.has(change.ecosystem) && change.namespace) { | |
| p = `pkg:${change.ecosystem}/${change.namespace}/${change.name}${change.version ? '@' + change.version : ''}`; | |
| } else { | |
| p = `pkg:${change.ecosystem}/${change.name}${change.version ? '@' + change.version : ''}`; | |
| } | |
| const branch = (repo as any).defaultBranch || undefined; | |
| return list.map(pkg => ({ | |
| ...pkg, | |
| __branch: branch | |
| })); |
| // Move applyQueries to module scope | ||
| function applyQueries( | ||
| candidatePurls: string[], | ||
| queries: ParsedQuery[], | ||
| found: Map<string, string>, | ||
| branchTag?: string, | ||
| fallbackVersion?: string | ||
| ) { | ||
| const unique = Array.from(new Set(candidatePurls)); | ||
| for (const p of unique) { | ||
| const pLower = p.toLowerCase(); | ||
| const outKey = branchTag ? `${p}@${branchTag}` : p; | ||
| for (const q of queries) { | ||
| if (q.isPrefixWildcard) { | ||
| const prefix = q.lower.slice(0, -1); | ||
| if (pLower.startsWith(prefix)) { if (!found.has(outKey)) found.set(outKey, q.raw); } | ||
| continue; | ||
| } | ||
| if (q.versionConstraint && q.type && q.name) { | ||
| if (!pLower.startsWith("pkg:")) continue; | ||
| const body = p.slice(4); | ||
| const atIdx = body.indexOf("@"); | ||
| const main = atIdx >= 0 ? body.slice(0, atIdx) : body; | ||
| const ver = atIdx >= 0 ? body.slice(atIdx + 1) : fallbackVersion; | ||
| const slashIdx = main.indexOf("/"); | ||
| if (slashIdx < 0) continue; | ||
| const pType = main.slice(0, slashIdx).toLowerCase(); | ||
| const pName = main.slice(slashIdx + 1); | ||
| if (pType === q.type && pName.toLowerCase() === q.name.toLowerCase() && ver) { | ||
| try { | ||
| const coerced = semver.coerce(ver)?.version || ver; | ||
| if (semver.valid(coerced) && semver.satisfies(coerced, q.versionConstraint, { includePrerelease: true })) { | ||
| if (!found.has(outKey)) found.set(outKey, q.raw); | ||
| } | ||
| } catch { /* ignore */ } | ||
| } | ||
| } else if (q.exact) { | ||
| if (pLower === q.exact) { if (!found.has(outKey)) found.set(outKey, q.raw); } | ||
| } | ||
| } | ||
| } | ||
| } |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The applyQueries function at line 595-635 in sbomCollector.ts is declared at module scope (outside the class), which breaks the expected encapsulation. This function appears to be extracted from inside the searchByPurlsWithReasons method but is placed incorrectly. The comment at line 594 says "// Move applyQueries to module scope" which suggests this was intentional, but it's placed in the middle of the class definition rather than before or after it, making the code structure confusing and potentially breaking the class context.
| continue; | ||
| } | ||
|
|
||
| const base = this.opts.branchDiffBase || sbom?.defaultBranch; |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At line 290, there's a condition checking sbom.defaultBranch, but at this point in the flow, if skipped is true, sbom is the baseline (line 280) which may not have the defaultBranch property set if it came from an older cache. Similarly at line 313 for sbom?.defaultBranch. It would be safer to use repo.default_branch from the repo metadata which is known to be available.
| const base = this.opts.branchDiffBase || sbom?.defaultBranch; | |
| const base = this.opts.branchDiffBase || repo.default_branch; |
| export async function getLanguageIntersection(octokit: Octokit, owner: string, repo: string, languages: string[] | undefined, quiet: boolean = false): Promise<string[]> { | ||
| const langResp = await octokit.request('GET /repos/{owner}/{repo}/languages', { owner, repo }); | ||
| const repoLangs = Object.keys(langResp.data || {}); | ||
| const wanted = languages; | ||
| const intersect = wanted ? repoLangs.filter(l => wanted.some(w => w.toLowerCase() === l.toLowerCase())) : repoLangs; | ||
| if (!intersect.length) { | ||
| if (!quiet) console.error(chalk.yellow(`Skipping submission: none of selected languages present in repo (${repoLangs.join(', ')})`)); | ||
| return []; | ||
| } | ||
| return intersect; |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The function getLanguageIntersection is declared as async but doesn't actually await anything - the only await-able operation is octokit.request which is correctly awaited. However, the function is marked as returning Promise<string[]> which is correct for async functions. This is not a bug, but the async keyword could be removed for clarity since the function doesn't use any await internally. Minor optimization - not critical.
| export async function getLanguageIntersection(octokit: Octokit, owner: string, repo: string, languages: string[] | undefined, quiet: boolean = false): Promise<string[]> { | |
| const langResp = await octokit.request('GET /repos/{owner}/{repo}/languages', { owner, repo }); | |
| const repoLangs = Object.keys(langResp.data || {}); | |
| const wanted = languages; | |
| const intersect = wanted ? repoLangs.filter(l => wanted.some(w => w.toLowerCase() === l.toLowerCase())) : repoLangs; | |
| if (!intersect.length) { | |
| if (!quiet) console.error(chalk.yellow(`Skipping submission: none of selected languages present in repo (${repoLangs.join(', ')})`)); | |
| return []; | |
| } | |
| return intersect; | |
| export function getLanguageIntersection(octokit: Octokit, owner: string, repo: string, languages: string[] | undefined, quiet: boolean = false): Promise<string[]> { | |
| return octokit.request('GET /repos/{owner}/{repo}/languages', { owner, repo }).then(langResp => { | |
| const repoLangs = Object.keys(langResp.data || {}); | |
| const wanted = languages; | |
| const intersect = wanted ? repoLangs.filter(l => wanted.some(w => w.toLowerCase() === l.toLowerCase())) : repoLangs; | |
| if (!intersect.length) { | |
| if (!quiet) console.error(chalk.yellow(`Skipping submission: none of selected languages present in repo (${repoLangs.join(', ')})`)); | |
| return []; | |
| } | |
| return intersect; | |
| }); |
Adds branch scanning, dependency submission, and more robustness.