Add patched version column to vulnerability summary with multi-range support (#5)
* Initial plan * Initial plan for adding patched versions to vulnerability summary Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Add patched version column to vulnerability summary table Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Optimize API calls to use Set and Promise.all for better performance Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Remove type assertions and optimize ecosystem lookups with normalization Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Extract patch version type checking into helper function for clarity Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Support multiple version ranges per package in advisory lookup Handle scenarios where the same package has multiple vulnerable version ranges with different patched versions (e.g., GHSA-gwq6-fmvp-qp68 with .NET packages). - Store all vulnerability entries with version ranges, not just one per ecosystem - Implement version range matching to select correct patch version - Match package by ecosystem, name, AND version range - Add comprehensive test for multi-range scenario Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Fix first_patched_version extraction to handle string format The GitHub Advisory API returns first_patched_version as a simple string (e.g., "112.0.0"), not an object with an identifier field. Updated extractPatchVersionId to handle string format primarily while maintaining backward compatibility with object format. - Fixed extractPatchVersionId to check for string first - Updated test mocks to match actual API response format - Added specific test for RestSharp GHSA-4rr6-2v9v-wcpc case - Verified with actual API responses from multiple advisories Fixes issue where patched versions were showing as "N/A" instead of the actual version number. Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Rebuild dist with patched version fix The previous fix to extractPatchVersionId wasn't deployed because the dist/ folder (compiled JavaScript) wasn't rebuilt. This commit rebuilds and packages the action with npm run build && npm run package to include the fix. Changes in dist/: - Updated extractPatchVersionId to handle string format first - Includes all async vulnerability summary logic with API calls - Properly extracts patched versions from GitHub Advisory API This should resolve the issue where patched versions showed as "N/A" in actual GitHub Actions runs. Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> * Add comprehensive debug logging for patch version lookup Added detailed debug logging to help troubleshoot patch version issues: - Log when fetching advisory data from API - Log number of vulnerability entries found - Log each patch info entry added with details - Log when no patch version is found - Log during lookup phase with package details - Log when patch version is found vs not found - Log available entries when no match is found This will make it much easier to diagnose issues in GitHub Actions debug mode. Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>
This commit is contained in:
+144
-13
@@ -1,9 +1,22 @@
|
||||
import {expect, jest, test} from '@jest/globals'
|
||||
import {expect, jest, test, beforeEach} from '@jest/globals'
|
||||
import {Changes, ConfigurationOptions, Scorecard} from '../src/schemas'
|
||||
import * as summary from '../src/summary'
|
||||
import * as core from '@actions/core'
|
||||
import {createTestChange} from './fixtures/create-test-change'
|
||||
import {createTestVulnerability} from './fixtures/create-test-vulnerability'
|
||||
import * as utils from '../src/utils'
|
||||
|
||||
const mockOctokitRequest = jest.fn<any>()
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(utils, 'octokitClient').mockReturnValue({
|
||||
request: mockOctokitRequest
|
||||
} as any)
|
||||
|
||||
mockOctokitRequest.mockResolvedValue({
|
||||
data: {vulnerabilities: []}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
@@ -315,19 +328,19 @@ test('uses checkmarks for vulnerabilities if only license issues were found', ()
|
||||
expect(text).toContain('✅ 0 package(s) with unknown licenses')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - only includes section if any vulnerabilities found', () => {
|
||||
summary.addChangeVulnerabilitiesToSummary(emptyChanges, 'low')
|
||||
test('addChangeVulnerabilitiesToSummary() - only includes section if any vulnerabilities found', async () => {
|
||||
await summary.addChangeVulnerabilitiesToSummary(emptyChanges, 'low')
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toEqual('')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - includes all vulnerabilities', () => {
|
||||
test('addChangeVulnerabilitiesToSummary() - includes all vulnerabilities', async () => {
|
||||
const changes = [
|
||||
createTestChange({name: 'lodash'}),
|
||||
createTestChange({name: 'underscore', package_url: 'test-url'})
|
||||
]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('<h2>Vulnerabilities</h2>')
|
||||
@@ -335,7 +348,7 @@ test('addChangeVulnerabilitiesToSummary() - includes all vulnerabilities', () =>
|
||||
expect(text).toContain('underscore')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - includes advisory url if available', () => {
|
||||
test('addChangeVulnerabilitiesToSummary() - includes advisory url if available', async () => {
|
||||
const changes = [
|
||||
createTestChange({
|
||||
name: 'underscore',
|
||||
@@ -348,14 +361,14 @@ test('addChangeVulnerabilitiesToSummary() - includes advisory url if available',
|
||||
})
|
||||
]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('lodash')
|
||||
expect(text).toContain('<a href="test-url">test-summary</a>')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - groups vulnerabilities of a single package', () => {
|
||||
test('addChangeVulnerabilitiesToSummary() - groups vulnerabilities of a single package', async () => {
|
||||
const changes = [
|
||||
createTestChange({
|
||||
name: 'package-with-multiple-vulnerabilities',
|
||||
@@ -366,7 +379,7 @@ test('addChangeVulnerabilitiesToSummary() - groups vulnerabilities of a single p
|
||||
})
|
||||
]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text.match('package-with-multiple-vulnerabilities')).toHaveLength(1)
|
||||
@@ -374,10 +387,10 @@ test('addChangeVulnerabilitiesToSummary() - groups vulnerabilities of a single p
|
||||
expect(text).toContain('test-summary-2')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - prints severity statement if above low', () => {
|
||||
test('addChangeVulnerabilitiesToSummary() - prints severity statement if above low', async () => {
|
||||
const changes = [createTestChange()]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'medium')
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'medium')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain(
|
||||
@@ -385,15 +398,24 @@ test('addChangeVulnerabilitiesToSummary() - prints severity statement if above l
|
||||
)
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - does not print severity statement if it is set to "low"', () => {
|
||||
test('addChangeVulnerabilitiesToSummary() - does not print severity statement if it is set to "low"', async () => {
|
||||
const changes = [createTestChange()]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).not.toContain('Only included vulnerabilities')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - includes patched version column', async () => {
|
||||
const changes = [createTestChange()]
|
||||
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('Patched Version')
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - does not include entire section if no license issues found', () => {
|
||||
summary.addLicensesToSummary(emptyInvalidLicenseChanges, defaultConfig)
|
||||
const text = core.summary.stringify()
|
||||
@@ -508,3 +530,112 @@ test('addLicensesToSummary() - includes allowed dependency licences', () => {
|
||||
'<details><summary><strong>Excluded from license check</strong>:</summary> MIT, Apache-2.0</details>'
|
||||
)
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - handles multiple version ranges for same package', async () => {
|
||||
// Simulates GHSA-gwq6-fmvp-qp68 scenario with multiple version ranges
|
||||
const pkg8 = createTestChange({
|
||||
ecosystem: 'nuget',
|
||||
name: 'Microsoft.NetCore.App.Runtime.linux-arm',
|
||||
version: '8.0.1',
|
||||
vulnerabilities: [
|
||||
createTestVulnerability({
|
||||
advisory_ghsa_id: 'GHSA-test-multi',
|
||||
advisory_summary: 'Test Multi-Range Advisory',
|
||||
severity: 'high'
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const pkg9 = createTestChange({
|
||||
ecosystem: 'nuget',
|
||||
name: 'Microsoft.NetCore.App.Runtime.linux-arm',
|
||||
version: '9.0.1',
|
||||
vulnerabilities: [
|
||||
createTestVulnerability({
|
||||
advisory_ghsa_id: 'GHSA-test-multi',
|
||||
advisory_summary: 'Test Multi-Range Advisory',
|
||||
severity: 'high'
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
// Mock API response with multiple version ranges for same package
|
||||
mockOctokitRequest.mockResolvedValueOnce({
|
||||
data: {
|
||||
vulnerabilities: [
|
||||
{
|
||||
package: {
|
||||
ecosystem: 'NuGet',
|
||||
name: 'Microsoft.NetCore.App.Runtime.linux-arm'
|
||||
},
|
||||
vulnerable_version_range: '>= 8.0.0, <= 8.0.20',
|
||||
first_patched_version: '8.0.21'
|
||||
},
|
||||
{
|
||||
package: {
|
||||
ecosystem: 'NuGet',
|
||||
name: 'Microsoft.NetCore.App.Runtime.linux-arm'
|
||||
},
|
||||
vulnerable_version_range: '>= 9.0.0, <= 9.0.9',
|
||||
first_patched_version: '9.0.10'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const changes = [pkg8, pkg9]
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
|
||||
// Both packages should have correct patched versions based on their version ranges
|
||||
expect(text).toContain('8.0.21')
|
||||
expect(text).toContain('9.0.10')
|
||||
expect(mockOctokitRequest).toHaveBeenCalledWith('GET /advisories/{ghsa_id}', {
|
||||
ghsa_id: 'GHSA-test-multi'
|
||||
})
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - handles RestSharp GHSA-4rr6-2v9v-wcpc case', async () => {
|
||||
const pkg = createTestChange({
|
||||
ecosystem: 'nuget',
|
||||
name: 'RestSharp',
|
||||
version: '111.4.1',
|
||||
vulnerabilities: [
|
||||
createTestVulnerability({
|
||||
advisory_ghsa_id: 'GHSA-4rr6-2v9v-wcpc',
|
||||
advisory_summary:
|
||||
"CRLF Injection in RestSharp's `RestRequest.AddHeader` method",
|
||||
severity: 'moderate'
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
// Mock API response matching actual GitHub Advisory Database response
|
||||
mockOctokitRequest.mockResolvedValueOnce({
|
||||
data: {
|
||||
vulnerabilities: [
|
||||
{
|
||||
package: {
|
||||
ecosystem: 'nuget',
|
||||
name: 'RestSharp'
|
||||
},
|
||||
vulnerable_version_range: '>= 107.0.0-preview.1, < 112.0.0',
|
||||
first_patched_version: '112.0.0'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const changes = [pkg]
|
||||
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
|
||||
// Should show the correct patched version
|
||||
expect(text).toContain('112.0.0')
|
||||
expect(text).not.toContain('N/A')
|
||||
expect(mockOctokitRequest).toHaveBeenCalledWith('GET /advisories/{ghsa_id}', {
|
||||
ghsa_id: 'GHSA-4rr6-2v9v-wcpc'
|
||||
})
|
||||
})
|
||||
|
||||
+179
-44
@@ -779,7 +779,7 @@ function run() {
|
||||
let issueFound = false;
|
||||
if (config.vulnerability_check) {
|
||||
core.setOutput('vulnerable-changes', JSON.stringify(vulnerableChanges));
|
||||
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity);
|
||||
yield summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity);
|
||||
issueFound || (issueFound = yield printVulnerabilitiesBlock(vulnerableChanges, minSeverity, warnOnly));
|
||||
}
|
||||
if (config.license_check) {
|
||||
@@ -1628,6 +1628,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.addSummaryToSummary = addSummaryToSummary;
|
||||
exports.addChangeVulnerabilitiesToSummary = addChangeVulnerabilitiesToSummary;
|
||||
@@ -1644,6 +1653,60 @@ const icons = {
|
||||
warning: '⚠️'
|
||||
};
|
||||
const MAX_SCANNED_FILES_BYTES = 1048576;
|
||||
// Helper to check if a version falls within a vulnerable range
|
||||
// Supports basic semver comparisons like ">= 8.0.0, <= 8.0.20"
|
||||
function versionInRange(version, range) {
|
||||
if (!version || !range)
|
||||
return false;
|
||||
// Parse version into comparable parts
|
||||
const vParts = version.split('.').map(p => parseInt(p, 10));
|
||||
// Handle range formats like ">= 8.0.0, <= 8.0.20"
|
||||
const conditions = range.split(',').map(c => c.trim());
|
||||
for (const condition of conditions) {
|
||||
const match = condition.match(/([><=]+)\s*(\d+(?:\.\d+)*)/);
|
||||
if (!match)
|
||||
continue;
|
||||
const [, operator, rangeVer] = match;
|
||||
const rParts = rangeVer.split('.').map(p => parseInt(p, 10));
|
||||
// Compare versions part by part
|
||||
let cmp = 0;
|
||||
for (let i = 0; i < Math.max(vParts.length, rParts.length); i++) {
|
||||
const v = vParts[i] || 0;
|
||||
const r = rParts[i] || 0;
|
||||
if (v > r) {
|
||||
cmp = 1;
|
||||
break;
|
||||
}
|
||||
else if (v < r) {
|
||||
cmp = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if condition is satisfied
|
||||
if (operator === '>=' && cmp < 0)
|
||||
return false;
|
||||
if (operator === '>' && cmp <= 0)
|
||||
return false;
|
||||
if (operator === '<=' && cmp > 0)
|
||||
return false;
|
||||
if (operator === '<' && cmp >= 0)
|
||||
return false;
|
||||
if (operator === '=' && cmp !== 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function extractPatchVersionId(patchData) {
|
||||
// Handle string format (current API response)
|
||||
if (typeof patchData === 'string')
|
||||
return patchData;
|
||||
// Handle object format with identifier field (for backward compatibility)
|
||||
if (patchData && typeof patchData === 'object' && 'identifier' in patchData) {
|
||||
const id = patchData.identifier;
|
||||
return typeof id === 'string' ? id : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// generates the DR report summary and caches it to the Action's core.summary.
|
||||
// returns the DR summary string, ready to be posted as a PR comment if the
|
||||
// final DR report is too large
|
||||
@@ -1725,51 +1788,123 @@ function countScorecardWarnings(scorecard, config) {
|
||||
}, 0);
|
||||
}
|
||||
function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
||||
if (vulnerableChanges.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = [];
|
||||
const manifests = (0, utils_1.getManifestsSet)(vulnerableChanges);
|
||||
core.summary.addHeading('Vulnerabilities', 2);
|
||||
for (const manifest of manifests) {
|
||||
for (const change of vulnerableChanges.filter(pkg => pkg.manifest === manifest)) {
|
||||
let previous_package = '';
|
||||
let previous_version = '';
|
||||
for (const vuln of change.vulnerabilities) {
|
||||
const sameAsPrevious = previous_package === change.name &&
|
||||
previous_version === change.version;
|
||||
if (!sameAsPrevious) {
|
||||
rows.push([
|
||||
(0, utils_1.renderUrl)(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
(0, utils_1.renderUrl)(vuln.advisory_url, vuln.advisory_summary),
|
||||
vuln.severity
|
||||
]);
|
||||
}
|
||||
else {
|
||||
rows.push([
|
||||
{ data: '', colspan: '2' },
|
||||
(0, utils_1.renderUrl)(vuln.advisory_url, vuln.advisory_summary),
|
||||
vuln.severity
|
||||
]);
|
||||
}
|
||||
previous_package = change.name;
|
||||
previous_version = change.version;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (vulnerableChanges.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = [];
|
||||
const manifests = (0, utils_1.getManifestsSet)(vulnerableChanges);
|
||||
// Build set of unique advisories to query
|
||||
const advisorySet = new Set();
|
||||
for (const pkg of vulnerableChanges) {
|
||||
for (const vuln of pkg.vulnerabilities) {
|
||||
advisorySet.add(vuln.advisory_ghsa_id);
|
||||
}
|
||||
}
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4).addTable([
|
||||
[
|
||||
{ data: 'Name', header: true },
|
||||
{ data: 'Version', header: true },
|
||||
{ data: 'Vulnerability', header: true },
|
||||
{ data: 'Severity', header: true }
|
||||
],
|
||||
...rows
|
||||
]);
|
||||
}
|
||||
if (severity !== 'low') {
|
||||
core.summary.addQuote(`Only included vulnerabilities with severity <strong>${severity}</strong> or higher.`);
|
||||
}
|
||||
// Query GitHub API for patch info in parallel
|
||||
// Store all vulnerability entries (may be multiple per package with different ranges)
|
||||
const patchInfo = {};
|
||||
const apiClient = (0, utils_1.octokitClient)();
|
||||
yield Promise.all(Array.from(advisorySet).map((advId) => __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
core.debug(`Fetching advisory data for ${advId}`);
|
||||
const apiResult = yield apiClient.request('GET /advisories/{ghsa_id}', {
|
||||
ghsa_id: advId
|
||||
});
|
||||
patchInfo[advId] = [];
|
||||
const vulnList = apiResult.data.vulnerabilities || [];
|
||||
core.debug(`Found ${vulnList.length} vulnerability entries for ${advId}`);
|
||||
for (const v of vulnList) {
|
||||
if (v.package && v.package.ecosystem) {
|
||||
const normalizedEco = v.package.ecosystem.toLowerCase();
|
||||
const pkgName = v.package.name || '';
|
||||
const vulnRange = v.vulnerable_version_range || '';
|
||||
const patchVerId = extractPatchVersionId(v.first_patched_version);
|
||||
if (patchVerId) {
|
||||
patchInfo[advId].push({
|
||||
eco: normalizedEco,
|
||||
pkg: pkgName,
|
||||
range: vulnRange,
|
||||
patch: patchVerId
|
||||
});
|
||||
core.debug(`Added patch info for ${pkgName} (${normalizedEco}): ${patchVerId} for range ${vulnRange}`);
|
||||
}
|
||||
else {
|
||||
core.debug(`No patch version found for ${pkgName} (${normalizedEco}) in ${advId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
core.debug(`API call failed for ${advId}: ${e}`);
|
||||
patchInfo[advId] = [];
|
||||
}
|
||||
})));
|
||||
core.summary.addHeading('Vulnerabilities', 2);
|
||||
for (const manifest of manifests) {
|
||||
for (const change of vulnerableChanges.filter(pkg => pkg.manifest === manifest)) {
|
||||
let previous_package = '';
|
||||
let previous_version = '';
|
||||
for (const vuln of change.vulnerabilities) {
|
||||
const sameAsPrevious = previous_package === change.name &&
|
||||
previous_version === change.version;
|
||||
// Look up patch version by matching package name, ecosystem, and version range
|
||||
let patchVer = 'N/A';
|
||||
const advData = patchInfo[vuln.advisory_ghsa_id];
|
||||
if (advData && advData.length > 0) {
|
||||
const normalizedEco = change.ecosystem.toLowerCase();
|
||||
core.debug(`Looking up patch for ${change.name}@${change.version} (${normalizedEco}) in ${vuln.advisory_ghsa_id}`);
|
||||
// Find matching entry by ecosystem, package name, and version range
|
||||
const matchingEntry = advData.find(entry => entry.eco === normalizedEco &&
|
||||
entry.pkg === change.name &&
|
||||
versionInRange(change.version, entry.range));
|
||||
if (matchingEntry) {
|
||||
patchVer = matchingEntry.patch;
|
||||
core.debug(`Found patch version ${patchVer} for ${change.name}@${change.version}`);
|
||||
}
|
||||
else {
|
||||
core.debug(`No matching patch found for ${change.name}@${change.version}. Available entries: ${JSON.stringify(advData)}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.debug(`No advisory data available for ${vuln.advisory_ghsa_id}`);
|
||||
}
|
||||
if (!sameAsPrevious) {
|
||||
rows.push([
|
||||
(0, utils_1.renderUrl)(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
(0, utils_1.renderUrl)(vuln.advisory_url, vuln.advisory_summary),
|
||||
vuln.severity,
|
||||
patchVer
|
||||
]);
|
||||
}
|
||||
else {
|
||||
rows.push([
|
||||
{ data: '', colspan: '2' },
|
||||
(0, utils_1.renderUrl)(vuln.advisory_url, vuln.advisory_summary),
|
||||
vuln.severity,
|
||||
patchVer
|
||||
]);
|
||||
}
|
||||
previous_package = change.name;
|
||||
previous_version = change.version;
|
||||
}
|
||||
}
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4).addTable([
|
||||
[
|
||||
{ data: 'Name', header: true },
|
||||
{ data: 'Version', header: true },
|
||||
{ data: 'Vulnerability', header: true },
|
||||
{ data: 'Severity', header: true },
|
||||
{ data: 'Patched Version', header: true }
|
||||
],
|
||||
...rows
|
||||
]);
|
||||
}
|
||||
if (severity !== 'low') {
|
||||
core.summary.addQuote(`Only included vulnerabilities with severity <strong>${severity}</strong> or higher.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
function addLicensesToSummary(invalidLicenseChanges, config) {
|
||||
if (countLicenseIssues(invalidLicenseChanges) === 0) {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Generated
+423
-1
@@ -421,6 +421,7 @@
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
|
||||
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
@@ -898,6 +899,74 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
|
||||
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
|
||||
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
|
||||
@@ -911,7 +980,346 @@
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
|
||||
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
|
||||
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
|
||||
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
|
||||
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
|
||||
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
|
||||
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
|
||||
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
|
||||
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -1424,6 +1832,7 @@
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
|
||||
"integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@jest/types": "^29.6.3",
|
||||
@@ -1449,6 +1858,7 @@
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
|
||||
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/schemas": "^29.6.3",
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
@@ -1656,6 +2066,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz",
|
||||
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.1.0",
|
||||
@@ -2272,6 +2683,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
|
||||
"integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.5.1",
|
||||
"@typescript-eslint/scope-manager": "6.21.0",
|
||||
@@ -2307,6 +2719,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
|
||||
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "6.21.0",
|
||||
"@typescript-eslint/types": "6.21.0",
|
||||
@@ -2546,6 +2959,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
|
||||
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2944,6 +3358,7 @@
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
"integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/transform": "^29.7.0",
|
||||
"@types/babel__core": "^7.1.14",
|
||||
@@ -3152,6 +3567,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001541",
|
||||
"electron-to-chromium": "^1.4.535",
|
||||
@@ -3997,6 +4413,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
||||
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -4231,6 +4648,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz",
|
||||
"integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "7.4.0",
|
||||
"@typescript-eslint/types": "7.4.0",
|
||||
@@ -5936,6 +6354,7 @@
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^29.7.0",
|
||||
"@jest/types": "^29.6.3",
|
||||
@@ -6374,6 +6793,7 @@
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
|
||||
"integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^29.6.3",
|
||||
"@types/node": "*",
|
||||
@@ -7430,6 +7850,7 @@
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -8560,6 +8981,7 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -130,7 +130,7 @@ async function createSummary(
|
||||
scorecard,
|
||||
config
|
||||
)
|
||||
summary.addChangeVulnerabilitiesToSummary(
|
||||
await summary.addChangeVulnerabilitiesToSummary(
|
||||
vulnerabilities,
|
||||
config.fail_on_severity
|
||||
)
|
||||
|
||||
+4
-1
@@ -193,7 +193,10 @@ async function run(): Promise<void> {
|
||||
|
||||
if (config.vulnerability_check) {
|
||||
core.setOutput('vulnerable-changes', JSON.stringify(vulnerableChanges))
|
||||
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
|
||||
await summary.addChangeVulnerabilitiesToSummary(
|
||||
vulnerableChanges,
|
||||
minSeverity
|
||||
)
|
||||
issueFound ||= await printVulnerabilitiesBlock(
|
||||
vulnerableChanges,
|
||||
minSeverity,
|
||||
|
||||
+159
-7
@@ -2,7 +2,12 @@ import * as core from '@actions/core'
|
||||
import {SummaryTableRow} from '@actions/core/lib/summary'
|
||||
import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses'
|
||||
import {Change, Changes, ConfigurationOptions, Scorecard} from './schemas'
|
||||
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
|
||||
import {
|
||||
groupDependenciesByManifest,
|
||||
getManifestsSet,
|
||||
renderUrl,
|
||||
octokitClient
|
||||
} from './utils'
|
||||
|
||||
const icons = {
|
||||
check: '✅',
|
||||
@@ -12,6 +17,62 @@ const icons = {
|
||||
|
||||
const MAX_SCANNED_FILES_BYTES = 1048576
|
||||
|
||||
// Helper to check if a version falls within a vulnerable range
|
||||
// Supports basic semver comparisons like ">= 8.0.0, <= 8.0.20"
|
||||
function versionInRange(version: string, range: string): boolean {
|
||||
if (!version || !range) return false
|
||||
|
||||
// Parse version into comparable parts
|
||||
const vParts = version.split('.').map(p => parseInt(p, 10))
|
||||
|
||||
// Handle range formats like ">= 8.0.0, <= 8.0.20"
|
||||
const conditions = range.split(',').map(c => c.trim())
|
||||
|
||||
for (const condition of conditions) {
|
||||
const match = condition.match(/([><=]+)\s*(\d+(?:\.\d+)*)/)
|
||||
if (!match) continue
|
||||
|
||||
const [, operator, rangeVer] = match
|
||||
const rParts = rangeVer.split('.').map(p => parseInt(p, 10))
|
||||
|
||||
// Compare versions part by part
|
||||
let cmp = 0
|
||||
for (let i = 0; i < Math.max(vParts.length, rParts.length); i++) {
|
||||
const v = vParts[i] || 0
|
||||
const r = rParts[i] || 0
|
||||
if (v > r) {
|
||||
cmp = 1
|
||||
break
|
||||
} else if (v < r) {
|
||||
cmp = -1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check if condition is satisfied
|
||||
if (operator === '>=' && cmp < 0) return false
|
||||
if (operator === '>' && cmp <= 0) return false
|
||||
if (operator === '<=' && cmp > 0) return false
|
||||
if (operator === '<' && cmp >= 0) return false
|
||||
if (operator === '=' && cmp !== 0) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function extractPatchVersionId(patchData: unknown): string | null {
|
||||
// Handle string format (current API response)
|
||||
if (typeof patchData === 'string') return patchData
|
||||
|
||||
// Handle object format with identifier field (for backward compatibility)
|
||||
if (patchData && typeof patchData === 'object' && 'identifier' in patchData) {
|
||||
const id = (patchData as {identifier: unknown}).identifier
|
||||
return typeof id === 'string' ? id : null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// generates the DR report summary and caches it to the Action's core.summary.
|
||||
// returns the DR summary string, ready to be posted as a PR comment if the
|
||||
// final DR report is too large
|
||||
@@ -132,18 +193,77 @@ function countScorecardWarnings(
|
||||
)
|
||||
}
|
||||
|
||||
export function addChangeVulnerabilitiesToSummary(
|
||||
export async function addChangeVulnerabilitiesToSummary(
|
||||
vulnerableChanges: Changes,
|
||||
severity: string
|
||||
): void {
|
||||
): Promise<void> {
|
||||
if (vulnerableChanges.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const rows: SummaryTableRow[] = []
|
||||
|
||||
const manifests = getManifestsSet(vulnerableChanges)
|
||||
|
||||
// Build set of unique advisories to query
|
||||
const advisorySet = new Set<string>()
|
||||
for (const pkg of vulnerableChanges) {
|
||||
for (const vuln of pkg.vulnerabilities) {
|
||||
advisorySet.add(vuln.advisory_ghsa_id)
|
||||
}
|
||||
}
|
||||
|
||||
// Query GitHub API for patch info in parallel
|
||||
// Store all vulnerability entries (may be multiple per package with different ranges)
|
||||
const patchInfo: Record<
|
||||
string,
|
||||
{eco: string; pkg: string; range: string; patch: string}[]
|
||||
> = {}
|
||||
const apiClient = octokitClient()
|
||||
|
||||
await Promise.all(
|
||||
Array.from(advisorySet).map(async advId => {
|
||||
try {
|
||||
core.debug(`Fetching advisory data for ${advId}`)
|
||||
const apiResult = await apiClient.request('GET /advisories/{ghsa_id}', {
|
||||
ghsa_id: advId
|
||||
})
|
||||
|
||||
patchInfo[advId] = []
|
||||
const vulnList = apiResult.data.vulnerabilities || []
|
||||
core.debug(
|
||||
`Found ${vulnList.length} vulnerability entries for ${advId}`
|
||||
)
|
||||
|
||||
for (const v of vulnList) {
|
||||
if (v.package && v.package.ecosystem) {
|
||||
const normalizedEco = v.package.ecosystem.toLowerCase()
|
||||
const pkgName = v.package.name || ''
|
||||
const vulnRange = v.vulnerable_version_range || ''
|
||||
const patchVerId = extractPatchVersionId(v.first_patched_version)
|
||||
if (patchVerId) {
|
||||
patchInfo[advId].push({
|
||||
eco: normalizedEco,
|
||||
pkg: pkgName,
|
||||
range: vulnRange,
|
||||
patch: patchVerId
|
||||
})
|
||||
core.debug(
|
||||
`Added patch info for ${pkgName} (${normalizedEco}): ${patchVerId} for range ${vulnRange}`
|
||||
)
|
||||
} else {
|
||||
core.debug(
|
||||
`No patch version found for ${pkgName} (${normalizedEco}) in ${advId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
core.debug(`API call failed for ${advId}: ${e}`)
|
||||
patchInfo[advId] = []
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
core.summary.addHeading('Vulnerabilities', 2)
|
||||
|
||||
for (const manifest of manifests) {
|
||||
@@ -157,18 +277,49 @@ export function addChangeVulnerabilitiesToSummary(
|
||||
previous_package === change.name &&
|
||||
previous_version === change.version
|
||||
|
||||
// Look up patch version by matching package name, ecosystem, and version range
|
||||
let patchVer = 'N/A'
|
||||
const advData = patchInfo[vuln.advisory_ghsa_id]
|
||||
if (advData && advData.length > 0) {
|
||||
const normalizedEco = change.ecosystem.toLowerCase()
|
||||
core.debug(
|
||||
`Looking up patch for ${change.name}@${change.version} (${normalizedEco}) in ${vuln.advisory_ghsa_id}`
|
||||
)
|
||||
// Find matching entry by ecosystem, package name, and version range
|
||||
const matchingEntry = advData.find(
|
||||
entry =>
|
||||
entry.eco === normalizedEco &&
|
||||
entry.pkg === change.name &&
|
||||
versionInRange(change.version, entry.range)
|
||||
)
|
||||
if (matchingEntry) {
|
||||
patchVer = matchingEntry.patch
|
||||
core.debug(
|
||||
`Found patch version ${patchVer} for ${change.name}@${change.version}`
|
||||
)
|
||||
} else {
|
||||
core.debug(
|
||||
`No matching patch found for ${change.name}@${change.version}. Available entries: ${JSON.stringify(advData)}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
core.debug(`No advisory data available for ${vuln.advisory_ghsa_id}`)
|
||||
}
|
||||
|
||||
if (!sameAsPrevious) {
|
||||
rows.push([
|
||||
renderUrl(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
renderUrl(vuln.advisory_url, vuln.advisory_summary),
|
||||
vuln.severity
|
||||
vuln.severity,
|
||||
patchVer
|
||||
])
|
||||
} else {
|
||||
rows.push([
|
||||
{data: '', colspan: '2'},
|
||||
renderUrl(vuln.advisory_url, vuln.advisory_summary),
|
||||
vuln.severity
|
||||
vuln.severity,
|
||||
patchVer
|
||||
])
|
||||
}
|
||||
previous_package = change.name
|
||||
@@ -180,7 +331,8 @@ export function addChangeVulnerabilitiesToSummary(
|
||||
{data: 'Name', header: true},
|
||||
{data: 'Version', header: true},
|
||||
{data: 'Vulnerability', header: true},
|
||||
{data: 'Severity', header: true}
|
||||
{data: 'Severity', header: true},
|
||||
{data: 'Patched Version', header: true}
|
||||
],
|
||||
...rows
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user