From 3e17dd9cb59595ef46ba395eccefa9088b28ce2d Mon Sep 17 00:00:00 2001 From: Robert Rathsack Date: Fri, 24 Apr 2026 18:20:42 +0200 Subject: [PATCH 1/2] fetch all security alerts --- .env.example | 1 + action.yml | 4 + src/dependabot/util.ts | 11 + src/dependabot/verified_commits.test.ts | 373 +++++++++++++++++++++++- src/dependabot/verified_commits.ts | 163 +++++++++-- src/dry-run.ts | 6 +- src/main.ts | 3 +- 7 files changed, 525 insertions(+), 36 deletions(-) diff --git a/.env.example b/.env.example index d2b965fa..a92ec2a4 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ LOCAL_GITHUB_ACCESS_TOKEN=XXXX +# INPUT_FETCH-DEPTH=0 diff --git a/action.yml b/action.yml index f7e3b2bc..33b39b8f 100644 --- a/action.yml +++ b/action.yml @@ -21,6 +21,10 @@ inputs: type: boolean description: 'If true, the action will not validate the user or the commit verification status' default: false + fetch-depth: + description: The number of vulnerability alerts to fetch, <= 0 for all + required: false + default: '0' outputs: dependency-names: description: 'A comma-separated list of all package names updated.' diff --git a/src/dependabot/util.ts b/src/dependabot/util.ts index 32247882..4f811b3d 100644 --- a/src/dependabot/util.ts +++ b/src/dependabot/util.ts @@ -1,4 +1,5 @@ import type { Context } from './github-context' +import { getInput } from '@actions/core' export function parseNwo (nwo: string): {owner: string; repo: string} { const [owner, name] = nwo.split('/') @@ -29,3 +30,13 @@ export function getTitle (context: Context): string { const { pull_request: pr } = context.payload return pr?.title || '' } + +export function getNumberInput (inputName: string, defaultVal: number): number; +export function getNumberInput (inputName: string, defaultVal?: number): number | undefined { + const inputStr = getInput(inputName) + let num = Number.parseInt(inputStr, 10) + if (Number.isNaN(num)) { + return defaultVal + } + return num +} diff --git a/src/dependabot/verified_commits.test.ts b/src/dependabot/verified_commits.test.ts index 902172cb..1cd32ef2 100644 --- a/src/dependabot/verified_commits.test.ts +++ b/src/dependabot/verified_commits.test.ts @@ -2,7 +2,13 @@ import * as github from '@actions/github' import * as core from '@actions/core' import nock from 'nock' import { Context } from './github-context' -import { getAlert, getMessage, trimSlashes, getCompatibility } from './verified_commits' +import { + getAlert, + getMessage, + trimSlashes, + getCompatibility, + createFetchVulnerabilityAlertsQuery +} from './verified_commits' beforeAll(() => { nock.disableNetConnect() @@ -149,7 +155,151 @@ test('it returns the commit message for a PR authored exclusively by Dependabot expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toEqual('Bump lodash from 1.0.0 to 2.0.0') }) -const query = '{"query":"\\n {\\n repository(owner: \\"dependabot\\", name: \\"dependabot\\") { \\n vulnerabilityAlerts(first: 100) {\\n nodes {\\n vulnerableManifestFilename\\n vulnerableManifestPath\\n vulnerableRequirements\\n state\\n securityVulnerability { \\n package { name } \\n }\\n securityAdvisory { \\n cvss { score }\\n ghsaId \\n }\\n }\\n }\\n }\\n }"}' +test('createFetchVulnerabilityAlertsQuery', () => { + expect(createFetchVulnerabilityAlertsQuery("foo", "bar")).toEqual(` + { + repository(owner: "foo", name: "bar") { + vulnerabilityAlerts(first: 100 ) { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { + cvss { score } + ghsaId + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + }`) +}) + +test('createFetchVulnerabilityAlertsQuery with maxResults', () => { + expect(createFetchVulnerabilityAlertsQuery("foo", "bar", 0)).toEqual(` + { + repository(owner: "foo", name: "bar") { + vulnerabilityAlerts(first: 100 ) { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { + cvss { score } + ghsaId + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + }`) + + expect(createFetchVulnerabilityAlertsQuery("foo", "bar", 25)).toEqual(` + { + repository(owner: "foo", name: "bar") { + vulnerabilityAlerts(first: 25 ) { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { + cvss { score } + ghsaId + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + }`) + + expect(createFetchVulnerabilityAlertsQuery("foo", "bar", 150)).toEqual(` + { + repository(owner: "foo", name: "bar") { + vulnerabilityAlerts(first: 100 ) { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { + cvss { score } + ghsaId + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + }`) +}) + +test('createFetchVulnerabilityAlertsQuery with endCursor', () => { + expect(createFetchVulnerabilityAlertsQuery("foo", "bar", 0, "c123")).toEqual(` + { + repository(owner: "foo", name: "bar") { + vulnerabilityAlerts(first: 100 , after: "c123") { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { + cvss { score } + ghsaId + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + }`) +}) + +/** + * Wraps the GraphQL query in a json object which would be sent over the wire. + * + * To get something readable from nock unmatched query error, you can do the opposite steps + * in order to get something readable, e.g. via Node REPL: + * let s = "unexpected_query" + * console.log(JSON.parse(s).query) + */ +function createGraphQlJsonBody(maxResults = 100, endCursor?: string): string { + const query = createFetchVulnerabilityAlertsQuery('dependabot', 'dependabot', maxResults, endCursor) + return JSON.stringify({query}) +} + +const query = createGraphQlJsonBody() const response = { data: { @@ -164,7 +314,10 @@ const response = { securityVulnerability: { package: { name: 'coffee-script' } }, securityAdvisory: { cvss: { score: 4.5 }, ghsaId: 'FOO' } } - ] + ], + pageInfo: { + hasNextPage: false + } } } } @@ -183,7 +336,10 @@ const responseWithManifestFileAtRoot = { securityVulnerability: { package: { name: 'coffee-script' } }, securityAdvisory: { cvss: { score: 4.5 }, ghsaId: 'FOO' } } - ] + ], + pageInfo: { + hasNextPage: false + } } } } @@ -249,6 +405,215 @@ test('it returns default if it does not match the name', async () => { expect(await getAlert('coffee', '4.0.1', '/', mockGitHubClient, mockGitHubPullContext())).toEqual({ alertState: '', cvss: 0, ghsaId: '' }) }) +const responseFetchAllPage1 = { + data: { + repository: { + vulnerabilityAlerts: { + nodes: [ + { + vulnerableManifestFilename: 'yarn.lock', + vulnerableManifestPath: 'yarn.lock', + vulnerableRequirements: '= 4.17.11', + state: 'FIXED', + securityVulnerability: { + package: { + name: 'lodash' + } + }, + securityAdvisory: { + cvss: { + score: 9.1 + }, + ghsaId: 'GHSA-jf85-cpcp-j695' + } + } + ], + pageInfo: { + hasNextPage: true, + endCursor: 'Y3Vyc29yOnYyOpHPAAAAAUU_eqA=' + } + } + } + } +} + +const defaultAlertFetchDepth = 0 + +const queryFetchAllPage2 = createGraphQlJsonBody(defaultAlertFetchDepth, responseFetchAllPage1.data.repository.vulnerabilityAlerts.pageInfo.endCursor) + +const responseFetchAllPage2 = { + data: { + repository: { + vulnerabilityAlerts: { + nodes: [ + { + vulnerableManifestFilename: 'yarn.lock', + vulnerableManifestPath: 'yarn.lock', + vulnerableRequirements: '= 3.12.0', + state: 'FIXED', + securityVulnerability: { package: { name: 'js-yaml' } }, + securityAdvisory: { + cvss: { score: 0 }, + ghsaId: 'GHSA-8j8c-7jfh-h6hx' + }, + }, + ], + pageInfo: { + hasNextPage: true, + endCursor: 'Y3Vyc29yOnYyOpHOLxj2uQ==' + } + } + } + } +} + +const queryFetchAllPage3 = createGraphQlJsonBody(defaultAlertFetchDepth, responseFetchAllPage2.data.repository.vulnerabilityAlerts.pageInfo.endCursor) + +test('fetch all vulnerability alert pages', async () => { + const queryFetchAllPage1 = query + const responseFetchAllPage3 = response + + nock('https://api.github.com') + .post('/graphql', queryFetchAllPage1) + .reply(200, responseFetchAllPage1) + .post('/graphql', queryFetchAllPage2) + .reply(200, responseFetchAllPage2) + .post('/graphql', queryFetchAllPage3) + .reply(200, responseFetchAllPage3) + + expect( + await getAlert( + 'coffee-script', + '4.0.1', + '/wwwroot', + mockGitHubClient, + mockGitHubPullContext() + ) + ).toEqual({ alertState: 'DISMISSED', cvss: 4.5, ghsaId: 'FOO' }) +}) + +test('fetch all vulnerability alert pages, match on page 2', async () => { + const queryFetchAllPage1 = query + + nock('https://api.github.com') + .post('/graphql', queryFetchAllPage1) + .reply(200, responseFetchAllPage1) + .post('/graphql', queryFetchAllPage2) + .reply(200, responseFetchAllPage2) + .post('/graphql', queryFetchAllPage3) + .replyWithError('impl should not continue fetching this page') + + expect( + await getAlert( + 'js-yaml', + '3.12.0', + '/', + mockGitHubClient, + mockGitHubPullContext() + ) + ).toEqual({ alertState: 'FIXED', cvss: 0, ghsaId: 'GHSA-8j8c-7jfh-h6hx' }) +}) + +test('fetch all vulnerability alerts, 3 pages, fetch-depth 2', async () => { + const queryFetch1 = createGraphQlJsonBody(2) + const queryFetch2 = createGraphQlJsonBody(1, responseFetchAllPage1.data.repository.vulnerabilityAlerts.pageInfo.endCursor) + const queryFetch3 = createGraphQlJsonBody(100, responseFetchAllPage2.data.repository.vulnerabilityAlerts.pageInfo.endCursor) + + nock('https://api.github.com') + .post('/graphql', queryFetch1) + .reply(200, responseFetchAllPage1) + .post('/graphql', queryFetch2) + .reply(200, responseFetchAllPage2) + .post('/graphql', queryFetch3) + .replyWithError('impl should not continue fetching this page') + + expect( + await getAlert( + 'coffee-script', + '4.0.1', + '/wwwroot', + mockGitHubClient, + mockGitHubPullContext(), + 2 + ) + ).toEqual({ alertState: '', cvss: 0, ghsaId: '' }) + + expect(core.warning).toHaveBeenCalledWith('Query has more results, but reached number of max results configured via fetch-depth') +}) + +test('fetch all vulnerability alerts, 3 pages, fetch-depth 3', async () => { + const queryFetch1 = createGraphQlJsonBody(3) + const queryFetch2 = createGraphQlJsonBody(2, responseFetchAllPage1.data.repository.vulnerabilityAlerts.pageInfo.endCursor) + const queryFetch3 = createGraphQlJsonBody(1, responseFetchAllPage2.data.repository.vulnerabilityAlerts.pageInfo.endCursor) + const responseFetchAllPage3 = response + + nock('https://api.github.com') + .post('/graphql', queryFetch1) + .reply(200, responseFetchAllPage1) + .post('/graphql', queryFetch2) + .reply(200, responseFetchAllPage2) + .post('/graphql', queryFetch3) + .reply(200, responseFetchAllPage3) + + expect( + await getAlert( + 'coffee-script', + '4.0.1', + '/wwwroot', + mockGitHubClient, + mockGitHubPullContext(), + 3 + ) + ).toEqual({ alertState: 'DISMISSED', cvss: 4.5, ghsaId: 'FOO' }) +}) + +const responseWithoutEqInFrontOfVulnerableRequirements = { + data: { + repository: { + vulnerabilityAlerts: { + nodes: [ + { + vulnerableManifestFilename: 'yarn.lock', + vulnerableManifestPath: 'cypress/yarn.lock', + vulnerableRequirements: '4.4.0', + state: 'OPEN', + securityVulnerability: { + package: { + name: 'terser' + } + }, + securityAdvisory: { + cvss: { + score: 7.5 + }, + ghsaId: 'GHSA-4wf5-vphf-c2xc' + } + } + ], + pageInfo: { + hasNextPage: false + } + } + } + } +} + +test('it returns alert without eq in front of vulnerableRequirements', async () => { + nock('https://api.github.com') + .post('/graphql', query) + .reply(200, responseWithoutEqInFrontOfVulnerableRequirements) + + expect( + await getAlert( + 'terser', + '4.4.0', + '/cypress', + mockGitHubClient, + mockGitHubPullContext() + ) + ).toEqual({ alertState: 'OPEN', cvss: 7.5, ghsaId: 'GHSA-4wf5-vphf-c2xc' }) +}) + test('trimSlashes should only trim slashes from both ends', () => { expect(trimSlashes('')).toEqual('') expect(trimSlashes('///')).toEqual('') diff --git a/src/dependabot/verified_commits.ts b/src/dependabot/verified_commits.ts index 0d029a48..fe09c42d 100644 --- a/src/dependabot/verified_commits.ts +++ b/src/dependabot/verified_commits.ts @@ -56,37 +56,144 @@ export async function getMessage (client: InstanceType, context: return commit.message } -export async function getAlert (name: string, version: string, directory: string, client: InstanceType, context: Context): Promise { - const alerts: any = await client.graphql(` - { - repository(owner: "${context.repo.owner}", name: "${context.repo.repo}") { - vulnerabilityAlerts(first: 100) { - nodes { - vulnerableManifestFilename - vulnerableManifestPath - vulnerableRequirements - state - securityVulnerability { - package { name } - } - securityAdvisory { +/** + * @see https://docs.github.com/en/graphql/reference/objects#repositoryvulnerabilityalert + */ +interface RepositoryVulnerabilityAlert { + vulnerableManifestFilename: string + vulnerableManifestPath: string + vulnerableRequirements: string + state: "OPEN" | "FIXED" | "DISMISSED" + securityVulnerability: { + package: { + name: string + } + } + securityAdvisory: { + cvss: { + score: number + } + ghsaId: string + } +} + +type CursorValue = string | null | undefined; + +interface PageInfoForward { + hasNextPage: boolean + endCursor: CursorValue +} + +interface RepositoryVulnerabilityAlertsResult { + repository: { + vulnerabilityAlerts: { + nodes: RepositoryVulnerabilityAlert[] + pageInfo: PageInfoForward + } + } +} + +export function createFetchVulnerabilityAlertsQuery(repoOwner: string, repoName: string, nResults: number = 100, endCursor?: CursorValue): string { + const first = nResults < 1 || nResults > 100 ? 100 : nResults + return ` + { + repository(owner: "${repoOwner}", name: "${repoName}") { + vulnerabilityAlerts(first: ${first} ${endCursor ? ', after: "' + endCursor + '"' : ''}) { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { cvss { score } - ghsaId - } - } - } - } - }`) - - const nodes = alerts?.repository?.vulnerabilityAlerts?.nodes - const found = nodes.find((a: any) => (version === '' || a.vulnerableRequirements === `= ${version}`) && - trimSlashes(a.vulnerableManifestPath) === trimSlashes(`${directory}/${a.vulnerableManifestFilename}`) && - a.securityVulnerability.package.name === name) + ghsaId + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + }` +} + +type FindAlertFunction = (element: RepositoryVulnerabilityAlert) => boolean + +function createFindAlertFunction(name: string, version: string, directory: string): FindAlertFunction { + return function (repoAlert: RepositoryVulnerabilityAlert) { + return ( + (version === "" || repoAlert.vulnerableRequirements === `${version}` || repoAlert.vulnerableRequirements === `= ${version}`) && + trimSlashes(repoAlert.vulnerableManifestPath) === trimSlashes(`${directory}/${repoAlert.vulnerableManifestFilename}`) && + repoAlert.securityVulnerability.package.name === name + ) + } +} + +async function fetchAndFilterVulnerabilityAlerts( + client: InstanceType, + repoOwner: string, + repoName: string, + fetchDepth: number, + findFn: FindAlertFunction, + endCursor?: CursorValue +): Promise { + let fetchedResults = 0 + while (true) { + core.debug(`Fetching vulnerability alerts for cursor ${endCursor ?? 'start'}`) + const query = createFetchVulnerabilityAlertsQuery(repoOwner, repoName, fetchDepth - fetchedResults, endCursor) + const result: RepositoryVulnerabilityAlertsResult = await client.graphql(query) + + const vulnerabilityAlerts = result.repository.vulnerabilityAlerts + const nodes = vulnerabilityAlerts.nodes + const found = nodes.find(findFn) + + if (found) { + return found + } + const pageInfo = vulnerabilityAlerts.pageInfo + if (!pageInfo.hasNextPage) { + return undefined + } + + fetchedResults += nodes.length + if (fetchDepth > 0 && fetchedResults >= fetchDepth) { + core.warning("Query has more results, but reached number of max results configured via fetch-depth") + break + } + + endCursor = pageInfo.endCursor + if (!endCursor) { + core.warning("pageInfo.hasNextPage is true, but endCursor is missing; stopping pagination to avoid infinite loop") + break + } + } + + return undefined +} + +export async function getAlert (name: string, version: string, directory: string, client: InstanceType, context: Context, fetchDepth: number = 0): Promise { + const findFn = createFindAlertFunction(name, version, directory) + + const repoAlert = await fetchAndFilterVulnerabilityAlerts(client, context.repo.owner, context.repo.repo, fetchDepth, findFn) + + if (repoAlert) { + core.debug(`Found matching vulnerability alert`) + return { + alertState: repoAlert?.state ?? '', + ghsaId: repoAlert?.securityAdvisory.ghsaId ?? '', + cvss: repoAlert?.securityAdvisory.cvss.score ?? 0 + } + } + core.debug(`Did not find matching vulnerability alert`) return { - alertState: found?.state ?? '', - ghsaId: found?.securityAdvisory.ghsaId ?? '', - cvss: found?.securityAdvisory.cvss.score ?? 0.0 + alertState: '', + ghsaId: '', + cvss: 0, } } diff --git a/src/dry-run.ts b/src/dry-run.ts index 207c2297..417f94f2 100755 --- a/src/dry-run.ts +++ b/src/dry-run.ts @@ -1,4 +1,3 @@ - import * as github from '@actions/github' import { Context } from './dependabot/github-context' import * as dotenv from 'dotenv' @@ -7,7 +6,7 @@ import { hideBin } from 'yargs/helpers' import { getMessage, getAlert, getCompatibility } from './dependabot/verified_commits' import { parse } from './dependabot/update_metadata' -import { getBranchNames, parseNwo } from './dependabot/util' +import { getBranchNames, parseNwo, getNumberInput } from './dependabot/util' async function check (args: any): Promise { try { @@ -51,7 +50,8 @@ async function check (args: any): Promise { if (commitMessage) { console.log('This appears to be a valid Dependabot Pull Request.') const branchNames = getBranchNames(newContext) - const lookupFn = (name: string, version: string, directory: string) => getAlert(name, version, directory, githubClient, actionContext) + const fetchDepth = getNumberInput('fetch-depth', 0) + const lookupFn = (name: string, version: string, directory: string) => getAlert(name, version, directory, githubClient, actionContext, fetchDepth) const updatedDependencies = await parse(commitMessage, pullRequest.body, branchNames.headName, branchNames.baseName, lookupFn, getCompatibility) diff --git a/src/main.ts b/src/main.ts index f8c4274f..c8d29222 100644 --- a/src/main.ts +++ b/src/main.ts @@ -28,7 +28,8 @@ export async function run (): Promise { const title = util.getTitle(github.context) let alertLookup: updateMetadata.alertLookup | undefined if (core.getInput('alert-lookup')) { - alertLookup = (name, version, directory) => verifiedCommits.getAlert(name, version, directory, githubClient, github.context) + const fetchDepth = util.getNumberInput('fetch-depth', 0); + alertLookup = (name, version, directory) => verifiedCommits.getAlert(name, version, directory, githubClient, github.context, fetchDepth) } const scoreLookup = core.getInput('compat-lookup') ? verifiedCommits.getCompatibility : undefined From f4ede5b62bfda0a98de8f9da563da3206f74fd04 Mon Sep 17 00:00:00 2001 From: Robert Rathsack Date: Mon, 27 Apr 2026 10:22:10 +0200 Subject: [PATCH 2/2] update dist build --- dist/index.js | 111 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 26 deletions(-) diff --git a/dist/index.js b/dist/index.js index e9f6a4aa..303fa1ab 100644 --- a/dist/index.js +++ b/dist/index.js @@ -31401,33 +31401,83 @@ async function getMessage(client, context3, skipCommitVerification = false, skip } return commit.message; } -async function getAlert(name, version, directory, client, context3) { - const alerts = await client.graphql(` - { - repository(owner: "${context3.repo.owner}", name: "${context3.repo.repo}") { - vulnerabilityAlerts(first: 100) { - nodes { - vulnerableManifestFilename - vulnerableManifestPath - vulnerableRequirements - state - securityVulnerability { - package { name } - } - securityAdvisory { +function createFetchVulnerabilityAlertsQuery(repoOwner, repoName, nResults = 100, endCursor) { + const first = nResults < 1 || nResults > 100 ? 100 : nResults; + return ` + { + repository(owner: "${repoOwner}", name: "${repoName}") { + vulnerabilityAlerts(first: ${first} ${endCursor ? ', after: "' + endCursor + '"' : ""}) { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { cvss { score } - ghsaId - } - } - } - } - }`); - const nodes = alerts?.repository?.vulnerabilityAlerts?.nodes; - const found = nodes.find((a) => (version === "" || a.vulnerableRequirements === `= ${version}`) && trimSlashes(a.vulnerableManifestPath) === trimSlashes(`${directory}/${a.vulnerableManifestFilename}`) && a.securityVulnerability.package.name === name); + ghsaId + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + }`; +} +function createFindAlertFunction(name, version, directory) { + return function(repoAlert) { + return (version === "" || repoAlert.vulnerableRequirements === `${version}` || repoAlert.vulnerableRequirements === `= ${version}`) && trimSlashes(repoAlert.vulnerableManifestPath) === trimSlashes(`${directory}/${repoAlert.vulnerableManifestFilename}`) && repoAlert.securityVulnerability.package.name === name; + }; +} +async function fetchAndFilterVulnerabilityAlerts(client, repoOwner, repoName, fetchDepth, findFn, endCursor) { + let fetchedResults = 0; + while (true) { + debug(`Fetching vulnerability alerts for cursor ${endCursor ?? "start"}`); + const query = createFetchVulnerabilityAlertsQuery(repoOwner, repoName, fetchDepth - fetchedResults, endCursor); + const result = await client.graphql(query); + const vulnerabilityAlerts = result.repository.vulnerabilityAlerts; + const nodes = vulnerabilityAlerts.nodes; + const found = nodes.find(findFn); + if (found) { + return found; + } + const pageInfo = vulnerabilityAlerts.pageInfo; + if (!pageInfo.hasNextPage) { + return void 0; + } + fetchedResults += nodes.length; + if (fetchDepth > 0 && fetchedResults >= fetchDepth) { + warning("Query has more results, but reached number of max results configured via fetch-depth"); + break; + } + endCursor = pageInfo.endCursor; + if (!endCursor) { + warning("pageInfo.hasNextPage is true, but endCursor is missing; stopping pagination to avoid infinite loop"); + break; + } + } + return void 0; +} +async function getAlert(name, version, directory, client, context3, fetchDepth = 0) { + const findFn = createFindAlertFunction(name, version, directory); + const repoAlert = await fetchAndFilterVulnerabilityAlerts(client, context3.repo.owner, context3.repo.repo, fetchDepth, findFn); + if (repoAlert) { + debug(`Found matching vulnerability alert`); + return { + alertState: repoAlert?.state ?? "", + ghsaId: repoAlert?.securityAdvisory.ghsaId ?? "", + cvss: repoAlert?.securityAdvisory.cvss.score ?? 0 + }; + } + debug(`Did not find matching vulnerability alert`); return { - alertState: found?.state ?? "", - ghsaId: found?.securityAdvisory.ghsaId ?? "", - cvss: found?.securityAdvisory.cvss.score ?? 0 + alertState: "", + ghsaId: "", + cvss: 0 }; } function trimSlashes(value) { @@ -31647,6 +31697,14 @@ function getTitle(context3) { const { pull_request: pr } = context3.payload; return pr?.title || ""; } +function getNumberInput(inputName, defaultVal) { + const inputStr = getInput(inputName); + let num = Number.parseInt(inputStr, 10); + if (Number.isNaN(num)) { + return defaultVal; + } + return num; +} // src/main.ts async function run() { @@ -31665,7 +31723,8 @@ async function run() { const title = getTitle(context2); let alertLookup; if (getInput("alert-lookup")) { - alertLookup = (name, version, directory) => getAlert(name, version, directory, githubClient, context2); + const fetchDepth = getNumberInput("fetch-depth", 0); + alertLookup = (name, version, directory) => getAlert(name, version, directory, githubClient, context2, fetchDepth); } const scoreLookup = getInput("compat-lookup") ? getCompatibility : void 0; if (commitMessage) {