From f15adf903d91213e0042bdc108ddd8dab356769a Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Fri, 2 Jan 2026 12:55:25 -0300 Subject: [PATCH 1/3] fix: improve suggestion next patch or next minor version (#1014) --- lib/update_security_release.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/update_security_release.js b/lib/update_security_release.js index ffe56df6..df636d75 100644 --- a/lib/update_security_release.js +++ b/lib/update_security_release.js @@ -12,6 +12,7 @@ import fs from 'node:fs'; import auth from './auth.js'; import Request from './request.js'; import nv from '@pkgjs/nv'; +import semver from 'semver'; export default class UpdateSecurityRelease extends SecurityRelease { async sync() { @@ -268,17 +269,26 @@ Summary: ${summary}\n`, async calculateVersions(affectedVersions, supportedVersions) { const h1AffectedVersions = []; const patchedVersions = []; + let isPatchRelease = true; for (const affectedVersion of affectedVersions) { - const major = affectedVersion.split('.')[0]; - const latest = supportedVersions.find((v) => v.major === Number(major)).version; + const affectedMajor = affectedVersion.split('.')[0]; + const latest = supportedVersions.find((v) => v.major === Number(affectedMajor)).version; const version = await this.cli.prompt( `What is the affected version (<=) for release line ${affectedVersion}?`, { questionType: 'input', defaultAnswer: latest }); - const nextPatchVersion = parseInt(version.split('.')[2]) + 1; + const nextPatchVersion = semver.inc(version, 'patch'); + const nextMinorVersion = semver.inc(version, 'minor'); const patchedVersion = await this.cli.prompt( `What is the patched version (>=) for release line ${affectedVersion}?`, - { questionType: 'input', defaultAnswer: nextPatchVersion }); + { + questionType: 'input', + defaultAnswer: isPatchRelease ? nextPatchVersion : nextMinorVersion + }); + + if (patchedVersion !== nextPatchVersion) { + isPatchRelease = false; // is a minor release + } patchedVersions.push(patchedVersion); h1AffectedVersions.push({ From 89df0538426b32826db9f8e39a3d1f687d42abaa Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Fri, 2 Jan 2026 12:55:37 -0300 Subject: [PATCH 2/3] feat: automatically include cveId trailling (#1022) Fixes: https://github.com/nodejs-private/security-release/issues/47 --- components/git/release.js | 10 +++++-- lib/cherry_pick.js | 14 ++++++++-- lib/landing_session.js | 39 ++++++++++++++++++++++----- lib/prepare_release.js | 55 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 105 insertions(+), 13 deletions(-) diff --git a/components/git/release.js b/components/git/release.js index 597fe2ed..69265b6d 100644 --- a/components/git/release.js +++ b/components/git/release.js @@ -50,8 +50,14 @@ const releaseOptions = { type: 'boolean' }, security: { - describe: 'Demarcate the new security release as a security release', - type: 'boolean' + describe: 'Demarcate the new security release as a security release. ' + + 'Optionally provide path to security-release repository for CVE auto-population', + type: 'string', + coerce: (arg) => { + // If --security=path is used, return the path + if (arg === '' || arg === true) return true; + return arg; + } }, skipBranchDiff: { describe: 'Skips the initial branch-diff check when preparing releases', diff --git a/lib/cherry_pick.js b/lib/cherry_pick.js index df0503fa..44968d2f 100644 --- a/lib/cherry_pick.js +++ b/lib/cherry_pick.js @@ -14,20 +14,30 @@ export default class CherryPick { upstream, gpgSign, lint, - includeCVE + includeCVE, + cveIds, + vulnCveMap } = {}) { this.prid = prid; this.cli = cli; this.dir = dir; this.upstream = upstream; this.gpgSign = gpgSign; - this.options = { owner, repo, lint, includeCVE }; + this.options = { owner, repo, lint, includeCVE, cveIds, vulnCveMap }; } get includeCVE() { return this.options.includeCVE ?? false; } + get cveIds() { + return this.options.cveIds ?? null; + } + + get vulnCveMap() { + return this.options.vulnCveMap ?? null; + } + get owner() { return this.options.owner || 'nodejs'; } diff --git a/lib/landing_session.js b/lib/landing_session.js index 19944ba4..15b67ed7 100644 --- a/lib/landing_session.js +++ b/lib/landing_session.js @@ -345,12 +345,39 @@ export default class LandingSession extends Session { } if (!containCVETrailer && this.includeCVE) { - const cveID = await cli.prompt( - 'Git found no CVE-ID trailer in the original commit message. ' + - 'Please, provide the CVE-ID', - { questionType: 'input', defaultAnswer: 'CVE-2023-XXXXX' } - ); - amended.push('CVE-ID: ' + cveID); + let cveID; + if (this.cveIds && this.cveIds.length > 0) { + cveID = this.cveIds.join(', '); + cli.ok(`Using CVE-ID from vulnerabilities.json: ${cveID}`); + } else { + // Fallback: check if the original commit has a PR-URL trailer + // and use it to look up CVE-IDs from the vulnerabilities map + if (this.vulnCveMap) { + const prUrlMatch = original.match(PR_RE); + if (prUrlMatch) { + const prUrl = prUrlMatch[1]; + const cveIds = this.vulnCveMap.get(prUrl); + if (cveIds && cveIds.length > 0) { + cveID = cveIds.join(', '); + cli.ok(`Using CVE-ID from backport PR-URL (${prUrl}): ${cveID}`); + } + } + } + + // Fall back to prompt if still not found + if (!cveID) { + cveID = await cli.prompt( + 'Git found no CVE-ID trailer in the original commit message. ' + + 'Please, provide the CVE-ID or leave it empty', + { questionType: 'input', defaultAnswer: 'CVE-2026-XXXXX' } + ); + } + } + // Some commits might not address a vulnerability, but it is necessary + // for the security release to happen. + if (cveID !== '') { + amended.push('CVE-ID: ' + cveID); + } } const message = amended.join('\n'); diff --git a/lib/prepare_release.js b/lib/prepare_release.js index eea137a8..785d5ea0 100644 --- a/lib/prepare_release.js +++ b/lib/prepare_release.js @@ -1,5 +1,5 @@ import path from 'node:path'; -import { promises as fs } from 'node:fs'; +import { promises as fs, existsSync, readFileSync } from 'node:fs'; import semver from 'semver'; import { replaceInFile } from 'replace-in-file'; @@ -21,7 +21,11 @@ const isWindows = process.platform === 'win32'; export default class ReleasePreparation extends Session { constructor(argv, cli, dir) { super(cli, dir); - this.isSecurityRelease = argv.security; + // argv.security can be either: + // - true (boolean) if --security was used without parameter + // - string if --security=path was used + this.isSecurityRelease = !!argv.security; + this.securityReleaseRepo = typeof argv.security === 'string' ? argv.security : null; this.isLTS = false; this.isLTSTransition = argv.startLTS; this.runBranchDiff = !argv.skipBranchDiff; @@ -63,17 +67,62 @@ export default class ReleasePreparation extends Session { return false; } + const vulnCveMap = new Map(); + if (this.isSecurityRelease && this.securityReleaseRepo) { + const vulnPath = path.join( + this.securityReleaseRepo, + 'security-release', + 'next-security-release', + 'vulnerabilities.json' + ); + + if (!existsSync(vulnPath)) { + cli.error(`vulnerabilities.json not found at ${vulnPath}. ` + + 'Skipping CVE auto-population.'); + cli.warn('PRs will require manual CVE-ID entry.'); + } else { + try { + cli.startSpinner(`Reading vulnerabilities.json from ${vulnPath}..`); + const vulnData = JSON.parse(readFileSync(vulnPath, 'utf-8')); + cli.stopSpinner(`Done reading vulnerabilities.json from ${vulnPath}`); + + if (vulnData.reports && Array.isArray(vulnData.reports)) { + vulnData.reports.forEach(report => { + if (report.prURL && report.cveIds && report.cveIds.length > 0) { + vulnCveMap.set(report.prURL, report.cveIds); + } + }); + } + cli.ok(`Loaded ${vulnCveMap.size} CVE mappings from vulnerabilities.json`); + } catch (err) { + cli.error(`Failed to read vulnerabilities.json: ${err.message}`); + cli.warn('Continuing without CVE auto-population.'); + } + } + } + for (const pr of prs) { if (pr.mergeable !== 'MERGEABLE') { this.warnForNonMergeablePR(pr); } + + // Look up CVE-IDs from vulnerabilities.json + const prUrl = `https://github.com/${this.owner}/${this.repo}/pull/${pr.number}`; + const cveIds = vulnCveMap.get(prUrl); + + if (!cveIds || cveIds.length === 0) { + cli.warn(`No CVE-IDs found in vulnerabilities.json for ${prUrl}`); + } + const cp = new CherryPick(pr.number, this.dir, cli, { owner: this.owner, repo: this.repo, gpgSign: this.gpgSign, upstream: this.isSecurityRelease ? `https://${this.username}:${this.config.token}@github.com/${this.owner}/${this.repo}.git` : this.upstream, lint: false, - includeCVE: true + includeCVE: true, + cveIds: cveIds || null, + vulnCveMap }); const success = await cp.start(); if (!success) { From 219ead464d38edf383298891ea9b20cec8b4c8ca Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Fri, 2 Jan 2026 18:34:06 +0000 Subject: [PATCH 3/3] chore(main): release 6.1.0 (#1023) --- CHANGELOG.md | 12 ++++++++++++ npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c519ccb..5a08a432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [6.1.0](https://github.com/nodejs/node-core-utils/compare/v6.0.0...v6.1.0) (2026-01-02) + + +### Features + +* automatically include cveId trailling ([#1022](https://github.com/nodejs/node-core-utils/issues/1022)) ([89df053](https://github.com/nodejs/node-core-utils/commit/89df0538426b32826db9f8e39a3d1f687d42abaa)) + + +### Bug Fixes + +* improve suggestion next patch or next minor version ([#1014](https://github.com/nodejs/node-core-utils/issues/1014)) ([f15adf9](https://github.com/nodejs/node-core-utils/commit/f15adf903d91213e0042bdc108ddd8dab356769a)) + ## [6.0.0](https://github.com/nodejs/node-core-utils/compare/v5.16.2...v6.0.0) (2025-12-21) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f91d647a..74eb2d61 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "@node-core/utils", - "version": "6.0.0", + "version": "6.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@node-core/utils", - "version": "6.0.0", + "version": "6.1.0", "license": "MIT", "dependencies": { "@inquirer/prompts": "^7.4.1", diff --git a/package.json b/package.json index bee6b404..a427eb55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@node-core/utils", - "version": "6.0.0", + "version": "6.1.0", "description": "Utilities for Node.js core collaborators", "type": "module", "engines": {