Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
327d503
feat(registry-org): add ROOT metadata, CNA last-resort flag, and inte…
jdalphond-mitre Aug 10, 2026
2cf2dfe
Removing secretariat only fields from joint approval array
jdalphond-mitre Aug 10, 2026
1efc25e
Merge pull request #1958 from CVEProject/jd_1953_1954
jdalphond-mitre Aug 10, 2026
8447c79
Merge branch 'master' into dev
jdalphond-mitre Aug 13, 2026
835a834
Merge branch 'dev' into 2.8.5-release
jdalphond-mitre Aug 17, 2026
843057c
Merge pull request #1967 from CVEProject/2.8.5-release
jdaigneau5 Aug 17, 2026
b6264c5
version number bump
jdalphond-mitre Aug 25, 2026
8326148
Merge branch 'dev' into 2.8.5-release
jdalphond-mitre Aug 25, 2026
25a1b5c
version number bump
jdalphond-mitre Aug 25, 2026
230e5c4
feat(registry-org): add ROOT metadata, CNA last-resort flag, and inte…
jdalphond-mitre Aug 10, 2026
e8d1211
Removing secretariat only fields from joint approval array
jdalphond-mitre Aug 10, 2026
6a78f40
version number bump
jdalphond-mitre Aug 25, 2026
771cb67
feat(org): require joint approval for last-resort and top-level flags
jdalphond-mitre Aug 25, 2026
bd04b8b
Merge branch 'dev' into 2.8.5-release
jdalphond-mitre Aug 25, 2026
2f47fda
Merge pull request #1972 from CVEProject/2.8.5-release
jdalphond-mitre Aug 25, 2026
863bfab
Merge branch 'staging' into dev
jdalphond-mitre Aug 25, 2026
e855463
j-a fields
jdalphond-mitre Aug 25, 2026
32fd521
Merge pull request #1973 from CVEProject/dev
jdaigneau5 Aug 25, 2026
403842b
Require joint approval for organization contact and partner fields
jdalphond-mitre Aug 26, 2026
df888e8
Merge pull request #1977 from CVEProject/2.8.5-release
david-rocca Aug 26, 2026
2beb10f
Merge pull request #1978 from CVEProject/dev
jdalphond-mitre Aug 26, 2026
f5dfa8e
Merge branch 'master' into staging
jdalphond-mitre Aug 27, 2026
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
2 changes: 1 addition & 1 deletion api-docs/openapi.json
Original file line number Diff line number Diff line change
@@ -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 <a href='https://www.cve.org/ProgramOrganization/CNAs'>CVE Numbering Authorities (CNAs)</a> should use one of the methods below to obtain credentials: <ul><li>If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials</li> <li>Contact your Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/Google'>Google</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/INCIBE'>INCIBE</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/jpcert'>JPCERT/CC</a>, or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/redhat'>Red Hat</a>) or Top-Level Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/icscert'>CISA ICS</a> or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/mitre'>MITRE</a>) to request credentials </ul> <p>CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located <a href='https://github.com/CVEProject/cve-schema/releases/tag/v5.2.0' target='_blank'>here</a>.</p> <a href='https://cveform.mitre.org/' class='link' target='_blank'>Contact the CVE Services team</a>",
"contact": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
3 changes: 3 additions & 0 deletions schemas/registry-org/CNAOrg.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@
"minimum": 0,
"maximum": 100000
},
"is_last_resort": {
"type": "boolean"
},
"oversees": {
"type": "array",
"uniqueItems": true,
Expand Down
9 changes: 9 additions & 0 deletions schemas/registry-org/RootOrg.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@
},
"top_level_root": {
"$ref": "/BaseOrg#/properties/top_level_root"
},
"is_top_level_root": {
"type": "boolean"
},
"charter_or_scope": {
"type": "string"
},
"disclosure_policy": {
"type": "string"
}
},
"required": [
Expand Down
8 changes: 8 additions & 0 deletions schemas/registry-org/create-registry-org-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
"type": "string",
"description": "Indicates if the organization is a root or top-level root"
},
"is_top_level_root": {
"type": "boolean",
"description": "Indicates whether a ROOT organization is top-level"
},
"is_last_resort": {
"type": "boolean",
"description": "Indicates whether a CNA organization is a CNA of last resort."
},
"users": {
"type": "array",
"items": {
Expand Down
8 changes: 8 additions & 0 deletions schemas/registry-org/create-registry-org-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@
"type": "string",
"description": "Indicates if the organization is a root or top-level root"
},
"is_top_level_root": {
"type": "boolean",
"description": "Indicates whether a ROOT organization is top-level"
},
"is_last_resort": {
"type": "boolean",
"description": "Indicates whether a CNA organization is a CNA of last resort"
},
"users": {
"type": "array",
"items": {
Expand Down
8 changes: 8 additions & 0 deletions schemas/registry-org/get-registry-org-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
"type": "string",
"description": "Indicates if the organization is a root or top-level root"
},
"is_top_level_root": {
"type": "boolean",
"description": "Indicates whether a ROOT organization is top-level"
},
"is_last_resort": {
"type": "boolean",
"description": "Indicates whether a CNA organization is a CNA of last resort"
},
"reports_to": {
"type": [
"string",
Expand Down
8 changes: 8 additions & 0 deletions schemas/registry-org/list-registry-orgs-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
"type": "string",
"description": "Indicates if the organization is a root or top-level root"
},
"is_top_level_root": {
"type": "boolean",
"description": "Indicates whether a ROOT organization is top-level"
},
"is_last_resort": {
"type": "boolean",
"description": "Indicates whether a CNA organization is a CNA of last resort"
},
"reports_to": {
"type": [
"string",
Expand Down
8 changes: 8 additions & 0 deletions schemas/registry-org/update-registry-org-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@
"type": "string",
"description": "Indicates if the organization is a root or top-level root"
},
"is_top_level_root": {
"type": "boolean",
"description": "Indicates whether a ROOT organization is top-level"
},
"is_last_resort": {
"type": "boolean",
"description": "Indicates whether a CNA organization is a CNA of last resort"
},
"users": {
"type": "array",
"items": {
Expand Down
8 changes: 8 additions & 0 deletions schemas/registry-org/update-registry-org-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@
"type": "string",
"description": "Indicates if the organization is a root or top-level root"
},
"is_top_level_root": {
"type": "boolean",
"description": "Indicates whether a ROOT organization is top-level"
},
"is_last_resort": {
"type": "boolean",
"description": "Indicates whether a CNA organization is a CNA of last resort"
},
"users": {
"type": "array",
"items": {
Expand Down
2 changes: 1 addition & 1 deletion src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', 'contact_info', 'partner_role_type', 'partner_number', 'partner_country', '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', 'industry', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'],
Comment thread
david-rocca marked this conversation as resolved.
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'],
Expand Down
2 changes: 2 additions & 0 deletions src/controller/registry.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,8 @@ router.put('/registry/org/:shortname',
<li>aliases</li>
<li>oversees</li>
<li>top_level_root</li>
<li>is_top_level_root</li>
<li>is_last_resort</li>
<li>charter_or_scope</li>
<li>product_list</li>
<li>disclosure_policy</li>
Expand Down
1 change: 1 addition & 0 deletions src/model/cnaorg.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const validate = ajv.compile(CnaOrgSchema)
const schema = {
oversees: [String],
id_quota: Number,
is_last_resort: Boolean,
charter_or_scope: String,
disclosure_policy: String,
product_list: String
Expand Down
5 changes: 4 additions & 1 deletion src/model/rootorg.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ ajv.addSchema(BaseOrgSchema)
const validate = ajv.compile(RootOrgSchema)

const schema = {
oversees: [String]
oversees: [String],
is_top_level_root: Boolean,
charter_or_scope: String,
disclosure_policy: String
}

const options = { discriminatorKey: 'kind' }
Expand Down
2 changes: 1 addition & 1 deletion src/swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
19 changes: 17 additions & 2 deletions test/integration-tests/registry-org/registryOrgCRUDTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const testRegistryOrg = {
partner_number: 'Initial Partner Number',
partner_country: 'US',
advisory_locations: ['https://example.com/advisories'],
charter_or_scope: 'This is a normal string, not a URI'
charter_or_scope: 'This is a normal string, not a URI',
is_last_resort: true
}
let createdOrg

Expand Down Expand Up @@ -82,6 +83,9 @@ describe('Testing /registry/org endpoints', () => {
expect(res.body.created).to.haveOwnProperty('charter_or_scope')
expect(res.body.created.charter_or_scope).to.equal(testRegistryOrg.charter_or_scope)

expect(res.body.created).to.haveOwnProperty('is_last_resort')
expect(res.body.created.is_last_resort).to.equal(true)

expect(res.body.created).to.haveOwnProperty('program_data')
expect(res.body.created.program_data.status).to.equal('inactive')
expect(res.body.created.program_data).to.haveOwnProperty('partner_inactive_date')
Expand Down Expand Up @@ -671,7 +675,8 @@ describe('Testing /registry/org endpoints', () => {
partner_role_type: ['Researcher', 'Vendor'],
partner_number: 'Updated Partner Number',
partner_country: 'UK',
advisory_locations: ['https://example.com/updated_advisories']
advisory_locations: ['https://example.com/updated_advisories'],
is_last_resort: false
})
.then((res, err) => {
expect(err).to.be.undefined
Expand Down Expand Up @@ -708,6 +713,16 @@ describe('Testing /registry/org endpoints', () => {

expect(res.body.updated).to.haveOwnProperty('advisory_locations')
expect(res.body.updated.advisory_locations).to.deep.equal(['https://example.com/updated_advisories'])
expect(res.body.updated.is_last_resort).to.equal(false)
})
})
it('Retrieves the CNA last-resort flag', async () => {
await chai.request(app)
.get('/api/registry/org/registry_org_test')
.set(secretariatHeaders)
.then((res) => {
expect(res).to.have.status(200)
expect(res.body.is_last_resort).to.equal(false)
})
})
it('Allows Secretariat to update program_data', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,13 @@ describe('Testing Joint approval', () => {
await chai.request(app)
.put('/api/registry/org/non_secretariat_org')
.set(nonAdminHeaders)
.send({ ...testRegistryOrgForReview, short_name: 'new_non_secretariat_org', contact_info: { websites: ['https://www.example.com'] } })
.send({
...testRegistryOrgForReview,
short_name: 'new_non_secretariat_org',
contact_info: { websites: ['https://www.example.com'] },
partner_country: 'US',
industry: 'Technology'
})
.then((res) => {
expect(res).to.have.status(200)
expect(res.body.message).to.contain('organization was successfully updated, but joint approval is required for some fields.')
Expand All @@ -192,6 +198,9 @@ describe('Testing Joint approval', () => {
expect(res.body).to.have.property('status', 'pending')
expect(res.body.target_object_uuid).to.equal(orgUUID)
expect(res.body.new_review_data.short_name).to.equal('new_non_secretariat_org')
expect(res.body.new_review_data.contact_info.websites).to.deep.equal(['https://www.example.com'])
expect(res.body.new_review_data.partner_country).to.equal('US')
expect(res.body.new_review_data.industry).to.equal('Technology')
reviewUUID = res.body.uuid
})
})
Expand All @@ -203,7 +212,9 @@ describe('Testing Joint approval', () => {
expect(err).to.be.undefined
expect(res).to.have.status(200)
expect(res.body.short_name).to.equal('non_secretariat_org')
expect(res.body.contact_info.websites[0]).to.equal('https://www.example.com')
expect((res.body.contact_info && res.body.contact_info.websites) || []).to.not.include('https://www.example.com')
expect(res.body).to.not.haveOwnProperty('partner_country')
expect(res.body).to.not.haveOwnProperty('industry')
})
})
it('Secretariat can approve the ORG review with body parameter', async function () {
Expand Down Expand Up @@ -306,7 +317,7 @@ describe('Testing Joint approval', () => {
expect(err).to.be.undefined
expect(res).to.have.status(200)
expect(res.body.short_name).to.equal('non_with_comments')
expect(res.body.contact_info.websites[0]).to.equal('https://www.example.com')
expect((res.body.contact_info && res.body.contact_info.websites) || []).to.not.include('https://www.example.com')
})
})
it('Secretariat leaves a public comment on the org review', async () => {
Expand Down Expand Up @@ -380,6 +391,7 @@ describe('Testing Joint approval', () => {
expect(res).to.have.status(200)
expect(res.body.short_name).to.equal('new_non_with_comments')
expect(res.body.id_quota).to.equal(1000)
expect(res.body.contact_info.websites[0]).to.equal('https://www.example.com')
})
})
})
Expand Down Expand Up @@ -500,7 +512,7 @@ describe('Testing Joint approval', () => {
expect(res.body.message).to.contain('organization was successfully updated, but joint approval is required for some fields.')
expect(res.body.updated.UUID).to.equal(orgUUID)
expect(res.body.updated.short_name).to.equal(testRegistryOrgForNewShortNameReview.short_name)
expect(res.body.updated.contact_info.websites[0]).to.equal('https://www.example.com/new-short-name')
expect((res.body.updated.contact_info && res.body.updated.contact_info.websites) || []).to.not.include('https://www.example.com/new-short-name')
})
})
it('Check to see if the new_short_name review was created', async () => {
Expand Down
Loading
Loading