Skip to content

Conversation

@aegilops
Copy link
Contributor

@aegilops aegilops commented Dec 4, 2025

Adds branch scanning, dependency submission, and more robustness.

  • fetching SBOM diffs for non-default branches
  • automatic dependency snapshot submission using Component Detection
  • better CLI options and documentation
  • improved advisory sync reliability
  • support for org listing with Enterprise Server

Copilot AI review requested due to automatic review settings December 4, 2025 17:54
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings December 8, 2025 17:17
aegilops and others added 2 commits December 8, 2025 17:18
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link

Copilot AI left a 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.

aegilops and others added 3 commits December 8, 2025 17:40
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings December 8, 2025 17:52
Copy link

Copilot AI left a 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>
Copilot AI review requested due to automatic review settings December 8, 2025 18:07
Copy link

Copilot AI left a 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));
Copy link

Copilot AI Dec 8, 2025

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:

  1. Extracting this as a named constant (e.g., SNAPSHOT_INGESTION_DELAY_MS) to make its purpose clear
  2. Making it configurable through options if different GitHub instances have different ingestion times
  3. Adding a comment explaining why this specific duration was chosen

The same applies to the 3000ms delay on line 539.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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>
Copilot AI review requested due to automatic review settings December 8, 2025 18:16
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Dec 8, 2025

@aegilops I've opened a new pull request, #28, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link

Copilot AI left a 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>
Copilot AI review requested due to automatic review settings December 8, 2025 18:30
Copy link

Copilot AI left a 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.

Comment on lines +303 to +305
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).`));
Copy link

Copilot AI Dec 8, 2025

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.

Suggested change
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).`));

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,32 @@
# Changelog

## [2025-12-04] – 0.2.0 - Branch scanning and dependency submission
Copy link

Copilot AI Dec 8, 2025

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.

Suggested change
## [2025-12-04] – 0.2.0 - Branch scanning and dependency submission
## [2025-12-08] – 0.2.0 - Branch scanning and dependency submission

Copilot uses AI. Check for mistakes.
}
// Validate repo format if provided
if (options.repo) {
if (typeof options.repo !== "string" || !options.repo.includes("/")) {
Copy link

Copilot AI Dec 8, 2025

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".

Suggested change
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("/")) {

Copilot uses AI. Check for mistakes.
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";
Copy link

Copilot AI Dec 8, 2025

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.

Suggested change
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}`);
}

Copilot uses AI. Check for mistakes.
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");
Copy link

Copilot AI Dec 8, 2025

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.

Suggested change
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");

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +169
// 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 : ''}`;
}

Copy link

Copilot AI Dec 8, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +168
// 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 : ''}`;
}
Copy link

Copilot AI Dec 8, 2025

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.

Suggested change
// 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
}));

Copilot uses AI. Check for mistakes.
Comment on lines +594 to +635
// 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); }
}
}
}
}
Copy link

Copilot AI Dec 8, 2025

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.

Copilot uses AI. Check for mistakes.
continue;
}

const base = this.opts.branchDiffBase || sbom?.defaultBranch;
Copy link

Copilot AI Dec 8, 2025

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.

Suggested change
const base = this.opts.branchDiffBase || sbom?.defaultBranch;
const base = this.opts.branchDiffBase || repo.default_branch;

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +37
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;
Copy link

Copilot AI Dec 8, 2025

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.

Suggested change
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;
});

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants