Skip to content

Commit 5f231f8

Browse files
committed
Add ghsa details to prs
1 parent c5771aa commit 5f231f8

2 files changed

Lines changed: 153 additions & 22 deletions

File tree

src/commands/fix/coana-fix.mts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
66
import { getFixEnv } from './fix-env-helpers.mts'
77
import {
88
enablePrAutoMerge,
9+
fetchGhsaDetails,
910
openCoanaPr,
1011
setGitRemoteGithubRepoUrl,
1112
} from './pull-request.mts'
@@ -141,6 +142,7 @@ export async function coanaFix(
141142
return { ok: true, data: { fixed: false } }
142143
}
143144

145+
const ghsaDetails = await fetchGhsaDetails(ids)
144146
const scanBaseNames = new Set(scanFilepaths.map(p => path.basename(p)))
145147

146148
let count = 0
@@ -149,7 +151,7 @@ export async function coanaFix(
149151
// Process each GHSA ID individually, similar to npm-fix/pnpm-fix.
150152
ghsaLoop: for (let i = 0, { length } = ids; i < length; i += 1) {
151153
const id = ids[i]!
152-
debugFn('notice', `Processing GHSA ID: ${id}`)
154+
debugFn('notice', `check: ${id}`)
153155

154156
// Apply fix for single GHSA ID.
155157
// eslint-disable-next-line no-await-in-loop
@@ -190,11 +192,10 @@ export async function coanaFix(
190192

191193
overallFixed = true
192194

193-
// Create PR if in CI environment
194-
try {
195-
const branch = `socket/coana-fix/${id}`
195+
const branch = `socket/fix/${id}`
196196

197-
// Check if branch already exists
197+
try {
198+
// Check if branch already exists.
198199
// eslint-disable-next-line no-await-in-loop
199200
if (await gitRemoteBranchExists(branch, cwd)) {
200201
debugFn('notice', `skip: remote branch "${branch}" exists`)
@@ -203,14 +204,16 @@ export async function coanaFix(
203204

204205
debugFn('notice', `pr: creating for ${id}`)
205206

207+
const summary = ghsaDetails.get(id)?.summary
208+
206209
const pushed =
207210
// eslint-disable-next-line no-await-in-loop
208211
(await gitCreateBranch(branch, cwd)) &&
209212
// eslint-disable-next-line no-await-in-loop
210213
(await gitCheckoutBranch(branch, cwd)) &&
211214
// eslint-disable-next-line no-await-in-loop
212215
(await gitCommit(
213-
`fix: Apply Coana security fix for ${id}`,
216+
`fix: ${id}${summary ? ` - ${summary}` : ''}`,
214217
modifiedFiles,
215218
{
216219
cwd,
@@ -251,6 +254,7 @@ export async function coanaFix(
251254
{
252255
baseBranch: fixEnv.baseBranch,
253256
cwd,
257+
ghsaDetails,
254258
},
255259
)
256260

@@ -294,7 +298,10 @@ export async function coanaFix(
294298
}
295299

296300
count += 1
297-
debugFn('notice', `Processed ${count}/${Math.min(limit, ids.length)} fixes`)
301+
debugFn(
302+
'notice',
303+
`increment: count ${count}/${Math.min(limit, ids.length)}`,
304+
)
298305
if (count >= limit) {
299306
break ghsaLoop
300307
}

src/commands/fix/pull-request.mts

Lines changed: 139 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Octokit } from '@octokit/rest'
1010
import semver from 'semver'
1111

1212
import { PackageURL } from '@socketregistry/packageurl-js'
13+
import { joinAnd } from '@socketsecurity/registry/lib/arrays'
1314
import { debugDir, debugFn, isDebug } from '@socketsecurity/registry/lib/debug'
1415
import {
1516
readJson,
@@ -139,6 +140,96 @@ export async function cacheFetch<T>(
139140
return data
140141
}
141142

143+
export type GhsaDetails = {
144+
ghsaId: string
145+
cveId?: string
146+
summary: string
147+
severity: string
148+
publishedAt: string
149+
withdrawnAt?: string
150+
references: Array<{
151+
url: string
152+
}>
153+
vulnerabilities: {
154+
nodes: Array<{
155+
package: {
156+
ecosystem: string
157+
name: string
158+
}
159+
vulnerableVersionRange: string
160+
}>
161+
}
162+
}
163+
164+
export async function fetchGhsaDetails(
165+
ids: string[],
166+
): Promise<Map<string, GhsaDetails>> {
167+
const results = new Map<string, GhsaDetails>()
168+
if (!ids.length) {
169+
return results
170+
}
171+
const octokitGraphql = getOctokitGraphql()
172+
try {
173+
const gqlCacheKey = `${ids.join('-')}-graphql-snapshot`
174+
const gqlResp = await cacheFetch(gqlCacheKey, () =>
175+
octokitGraphql(
176+
`
177+
query($identifiers: [SecurityAdvisoryIdentifierFilter!]!) {
178+
securityAdvisories(first: ${ids.length}, identifiers: $identifiers) {
179+
nodes {
180+
ghsaId
181+
cveId
182+
summary
183+
severity
184+
publishedAt
185+
withdrawnAt
186+
references {
187+
url
188+
}
189+
vulnerabilities(first: 10) {
190+
nodes {
191+
package {
192+
ecosystem
193+
name
194+
}
195+
vulnerableVersionRange
196+
}
197+
}
198+
}
199+
}
200+
}`,
201+
{
202+
identifiers: ids.map(id => ({
203+
type: 'GHSA',
204+
value: id,
205+
})),
206+
},
207+
),
208+
)
209+
210+
const advisories: GhsaDetails[] =
211+
(gqlResp as any)?.securityAdvisories?.nodes || []
212+
for (const advisory of advisories) {
213+
if (advisory.ghsaId) {
214+
results.set(advisory.ghsaId, advisory)
215+
}
216+
}
217+
218+
// Log any missing advisories
219+
for (const id of ids) {
220+
if (!results.has(id)) {
221+
debugFn('notice', `No advisory found for ${id}`)
222+
}
223+
}
224+
} catch (e) {
225+
debugFn(
226+
'error',
227+
`Failed to fetch GHSA details: ${(e as Error)?.message || 'Unknown error'}`,
228+
)
229+
}
230+
return results
231+
}
232+
142233
export type CleanupPrsOptions = {
143234
newVersion?: string | undefined
144235
purl?: string | undefined
@@ -243,9 +334,8 @@ export async function enablePrAutoMerge({
243334
node_id: prId,
244335
}: Pr): Promise<PrAutoMergeState> {
245336
const octokitGraphql = getOctokitGraphql()
246-
let error: unknown
247337
try {
248-
const response = await octokitGraphql(
338+
const gqlResp = await octokitGraphql(
249339
`
250340
mutation EnableAutoMerge($pullRequestId: ID!) {
251341
enablePullRequestAutoMerge(input: {
@@ -259,21 +349,20 @@ export async function enablePrAutoMerge({
259349
}`,
260350
{ pullRequestId: prId },
261351
)
262-
const respPrNumber = (response as any)?.enablePullRequestAutoMerge
352+
const respPrNumber = (gqlResp as any)?.enablePullRequestAutoMerge
263353
?.pullRequest?.number
264354
if (respPrNumber) {
265355
return { enabled: true }
266356
}
267357
} catch (e) {
268-
error = e
269-
}
270-
if (
271-
error instanceof GraphqlResponseError &&
272-
Array.isArray(error.errors) &&
273-
error.errors.length
274-
) {
275-
const details = error.errors.map(({ message: m }) => m.trim())
276-
return { enabled: false, details }
358+
if (
359+
e instanceof GraphqlResponseError &&
360+
Array.isArray(e.errors) &&
361+
e.errors.length
362+
) {
363+
const details = e.errors.map(({ message: m }) => m.trim())
364+
return { enabled: false, details }
365+
}
277366
}
278367
return { enabled: false }
279368
}
@@ -371,6 +460,7 @@ async function getSocketPrsWithContext(
371460
state: GQL_PR_STATE
372461
title: string
373462
}
463+
374464
const nodes: GqlPrNode[] =
375465
(gqlResp as any)?.repository?.pullRequests?.nodes ?? []
376466
for (let i = 0, { length } = nodes; i < length; i += 1) {
@@ -516,6 +606,7 @@ export async function openPr(
516606
export type OpenCoanaPrOptions = {
517607
baseBranch?: string | undefined
518608
cwd?: string | undefined
609+
ghsaDetails?: Map<string, GhsaDetails> | undefined
519610
}
520611

521612
export async function openCoanaPr(
@@ -525,7 +616,7 @@ export async function openCoanaPr(
525616
ghsaIds: string[],
526617
options?: OpenCoanaPrOptions | undefined,
527618
): Promise<OctokitResponse<Pr> | null> {
528-
const { baseBranch = 'main' } = {
619+
const { baseBranch = 'main', ghsaDetails } = {
529620
__proto__: null,
530621
...options,
531622
} as OpenCoanaPrOptions
@@ -538,9 +629,42 @@ export async function openCoanaPr(
538629

539630
let prBody = ''
540631
if (vulnCount === 1) {
541-
prBody = `[Socket](https://socket.dev/) fix for [${ghsaIds[0]}](https://github.com/advisories/${ghsaIds[0]}).`
632+
const ghsaId = ghsaIds[0]!
633+
const details = ghsaDetails?.get(ghsaId)
634+
635+
prBody = `[Socket](https://socket.dev/) fix for [${ghsaId}](https://github.com/advisories/${ghsaId}).`
636+
if (details) {
637+
const packages = details.vulnerabilities.nodes.map(
638+
v => `${v.package.name} (${v.package.ecosystem})`,
639+
)
640+
641+
prBody += [
642+
'',
643+
'',
644+
`**Vulnerability Summary:** ${details.summary}`,
645+
'',
646+
`**Severity:** ${details.severity}`,
647+
'',
648+
`**Affected Packages:** ${joinAnd(packages)}`,
649+
].join('\n')
650+
}
542651
} else {
543-
prBody = `[Socket](https://socket.dev/) fixes for ${vulnCount} GHSAs.\n\n**Fixed GHSAs:**\n${ghsaIds.map(id => `- [${id}](https://github.com/advisories/${id})`).join('\n')}`
652+
prBody = [
653+
`[Socket](https://socket.dev/) fixes for ${vulnCount} GHSAs.`,
654+
'',
655+
'**Fixed Vulnerabilities:**',
656+
...ghsaIds.map(id => {
657+
const details = ghsaDetails?.get(id)
658+
const item = `- [${id}](https://github.com/advisories/${id})`
659+
if (details) {
660+
const packages = details.vulnerabilities.nodes.map(
661+
v => `${v.package.name}`,
662+
)
663+
return `${item} - ${details.summary} (${joinAnd(packages)})`
664+
}
665+
return item
666+
}),
667+
].join('\n')
544668
}
545669

546670
try {

0 commit comments

Comments
 (0)