Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions __tests__/summary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,49 @@ test('addChangeVulnerabilitiesToSummary() - handles RestSharp GHSA-4rr6-2v9v-wcp
})
})

test('addChangeVulnerabilitiesToSummary() - handles GHSA-vc5p-v9hr-52mj maven range', async () => {
const pkg = createTestChange({
ecosystem: 'maven',
name: 'org.apache.logging.log4j:log4j-core',
version: '2.12.4',
vulnerabilities: [
createTestVulnerability({
advisory_ghsa_id: 'GHSA-vc5p-v9hr-52mj',
advisory_summary: 'Apache Log4j vulnerable to XXE in JDBC Appender',
severity: 'high'
})
]
})

// Mock API response from https://api.github.com/advisories/GHSA-vc5p-v9hr-52mj
mockOctokitRequest.mockResolvedValueOnce({
data: {
vulnerabilities: [
{
package: {
ecosystem: 'maven',
name: 'org.apache.logging.log4j:log4j-core'
},
vulnerable_version_range: '>= 2.0-beta9, < 2.25.3',
first_patched_version: '2.25.3'
}
]
}
})

const changes = [pkg]
await summary.addChangeVulnerabilitiesToSummary(changes, 'low', true)

const text = core.summary.stringify()

// Should show the correct patched version for this advisory range
expect(text).toContain('2.25.3')
expect(text).not.toContain('N/A')
expect(mockOctokitRequest).toHaveBeenCalledWith('GET /advisories/{ghsa_id}', {
ghsa_id: 'GHSA-vc5p-v9hr-52mj'
})
})

test('addChangeVulnerabilitiesToSummary() - handles version coercion for non-strict semver versions', async () => {
// Test that versions like "8.0" (without patch version) can be coerced to "8.0.0"
// for successful range matching in fail-open mode (patch selection)
Expand Down Expand Up @@ -778,6 +821,50 @@ test('addChangeVulnerabilitiesToSummary() - handles invalid versions in fail-ope
expect(text).toContain('N/A')
})

test('addChangeVulnerabilitiesToSummary() - handles GHSA-r9w3-57w2-gch2 go pseudo-version range', async () => {
const pkg = createTestChange({
ecosystem: 'go',
name: 'github.com/ory/hydra',
version: '2.1.0-pre.2',
vulnerabilities: [
createTestVulnerability({
advisory_ghsa_id: 'GHSA-r9w3-57w2-gch2',
advisory_summary:
'Ory Hydra has a SQL injection via forged pagination tokens',
severity: 'high'
})
]
})

// Mock API response from https://api.github.com/advisories/GHSA-r9w3-57w2-gch2
mockOctokitRequest.mockResolvedValueOnce({
data: {
vulnerabilities: [
{
package: {
ecosystem: 'go',
name: 'github.com/ory/hydra'
},
vulnerable_version_range: '< 2.3.1-0.20260320110106-0b84568fffcc',
first_patched_version: '2.3.1-0.20260320110106-0b84568fffcc'
}
]
}
})

const changes = [pkg]
await summary.addChangeVulnerabilitiesToSummary(changes, 'low', true)

const text = core.summary.stringify()

// Should show the correct patched version for this advisory range
expect(text).toContain('2.3.1-0.20260320110106-0b84568fffcc')
expect(text).not.toContain('N/A')
expect(mockOctokitRequest).toHaveBeenCalledWith('GET /advisories/{ghsa_id}', {
ghsa_id: 'GHSA-r9w3-57w2-gch2'
})
})

test('addChangeVulnerabilitiesToSummary() - respects concurrency limit for API calls', async () => {
// Create 15 packages with different vulnerabilities to test concurrency limiting
const packages = Array.from({length: 15}, (_, i) =>
Expand Down
22 changes: 21 additions & 1 deletion src/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function versionInRange(
// Validate version and range explicitly to enforce fail-closed semantics
// semver.satisfies() typically returns false for invalid inputs without throwing
let validVersion = semver.valid(trimmedVersion)
const validRange = semver.validRange(semverRange)
let validRange = semver.validRange(semverRange)

// For fail-open mode (patch selection), try coercing invalid versions
// to handle common real-world formats like "8.0", date-based versions, etc.
Expand All @@ -88,6 +88,26 @@ function versionInRange(
}
}

// For fail-open mode, try coercing invalid version components within the range string
// to handle formats like "2.0-beta9" which should be "2.0.0-beta9" for strict semver
if (!validRange && !failClosed) {
const coercedRangeStr = semverRange.replace(
/(?<=[><=~^\s]|^)(\d+\.\d+(?:\.\d+)?(?:-[^\s]*)?)(?=\s|$)/g,
match => {
const coerced = semver.coerce(match)
return coerced ? coerced.version : match
}
)
if (coercedRangeStr !== semverRange) {
validRange = semver.validRange(coercedRangeStr)
if (validRange) {
core.debug(
`Coerced range "${semverRange}" to "${coercedRangeStr}" for range matching`
)
}
}
}

if (!validVersion || !validRange) {
if (failClosed) {
const issues: string[] = []
Expand Down
Loading