From b6264c544739158cff7a2d7223449f8988b6e98f Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 10:14:56 -0400 Subject: [PATCH 1/2] version number bump --- api-docs/openapi.json | 2 +- package.json | 2 +- src/swagger.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 84fe59bcf..3031f2cf1 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.8.4", + "version": "2.8.5", "title": "CVE Services API", "description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:

CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located here.

Contact the CVE Services team", "contact": { diff --git a/package.json b/package.json index 00409c63f..b63447db2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.8.4", + "version": "2.8.5", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/src/swagger.js b/src/swagger.js index 5edc22173..fd775f5cd 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -21,7 +21,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re /* eslint-disable no-multi-str */ const doc = { info: { - version: '2.8.4', + version: '2.8.5', title: 'CVE Services API', description: "The CVE Services API supports automation tooling for the CVE Program. Credentials are \ required for most service endpoints. Representatives of \ From 771cb6719f689182cf6a7ff89c45234ca66ee37e Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 12:27:41 -0400 Subject: [PATCH 2/2] feat(org): require joint approval for last-resort and top-level flags --- src/constants/index.js | 4 +--- src/controller/registry.controller/index.js | 2 ++ .../registry-org/rootOrgTest.js | 16 +++++++++++++--- .../review-object/reviewObjectTest.js | 2 ++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index c4db3affc..3bc1dff19 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], @@ -55,8 +55,6 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', - 'is_top_level_root', - 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: { diff --git a/src/controller/registry.controller/index.js b/src/controller/registry.controller/index.js index 2735f8cdf..7350a564d 100644 --- a/src/controller/registry.controller/index.js +++ b/src/controller/registry.controller/index.js @@ -557,6 +557,8 @@ router.put('/registry/org/:shortname',
  • aliases
  • oversees
  • top_level_root
  • +
  • is_top_level_root
  • +
  • is_last_resort
  • charter_or_scope
  • product_list
  • disclosure_policy
  • diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 05f9d1fba..e61254eaf 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -274,7 +274,7 @@ describe('Testing ROOT Organization Type', () => { }) }) - it('ROOT admin cannot edit is_top_level_root', async () => { + it('ROOT admin can request a change to is_top_level_root through joint approval', async () => { await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) @@ -283,8 +283,18 @@ describe('Testing ROOT Organization Type', () => { is_top_level_root: true }) .then((res) => { - expect(res).to.have.status(403) - expect(res.body.error).to.equal('SECRETARIAT_ONLY') + expect(res).to.have.status(200) + expect(res.body.message).to.include('joint approval is required') + expect(res.body.updated.is_top_level_root).to.equal(false) + }) + + return chai.request(app) + .get(`/api/review/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.status).to.equal('pending') + expect(res.body).to.have.nested.property('new_review_data.is_top_level_root', true) }) }) diff --git a/test/integration-tests/review-object/reviewObjectTest.js b/test/integration-tests/review-object/reviewObjectTest.js index d9fbe661f..a65412170 100644 --- a/test/integration-tests/review-object/reviewObjectTest.js +++ b/test/integration-tests/review-object/reviewObjectTest.js @@ -215,6 +215,7 @@ describe('Review Object Controller Integration Tests', () => { updateData.long_name = 'Approve Test Organization' updateData.authority = ['CNA'] updateData.id_quota = 1000 + updateData.is_last_resort = true updateData.contact_info = { websites: ['https://www.example.com'] } const res = await chai .request(app) @@ -232,6 +233,7 @@ describe('Review Object Controller Integration Tests', () => { expect(reviewRes.body).to.have.property('uuid') expect(reviewRes.body.status).to.equal('pending') expect(reviewRes.body).to.have.nested.property('new_review_data.long_name', 'Approve Test Organization') + expect(reviewRes.body).to.have.nested.property('new_review_data.is_last_resort', true) expect(reviewRes.body.new_review_data.contact_info.websites[0]).to.equal('https://www.example.com') approveTestReviewUUID = reviewRes.body.uuid })