Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
bb1f0bb
Fixing hung sessions
david-rocca Jun 24, 2026
2bf10f1
oversized json fix
david-rocca Jun 25, 2026
40489f5
Fixing the state of conversation middleware
david-rocca Jun 25, 2026
ef5e5e7
futureproof registry only users
david-rocca Jun 29, 2026
f55d7d8
Fixing some gt / lt searching
david-rocca Jun 29, 2026
c5b06c2
fixing wrongly named index
david-rocca Jun 30, 2026
f61d935
Merge pull request #1871 from CVEProject/dr_session_closures_fix
jdaigneau5 Jun 30, 2026
1bc8126
Merge branch 'dev' into dr_oversized_json_fix
david-rocca Jun 30, 2026
b333978
Fixing trimJSONWHITESPACE to avoiud queuing null values
david-rocca Jun 30, 2026
caa4f6f
Merge pull request #1872 from CVEProject/dr_oversized_json_fix
jdaigneau5 Jun 30, 2026
612dcee
Merge branch 'dev' into dr_conversation_route_validation_fixes
david-rocca Jun 30, 2026
0f85f10
Prevent append-audit requests without history from returning a 500
david-rocca Jun 30, 2026
449404f
validates cursor pagination limits correctly
david-rocca Jun 30, 2026
868e6c7
Merge pull request #1873 from CVEProject/dr_conversation_route_valida…
jdaigneau5 Jun 30, 2026
1306d18
Merge branch 'dev' into dr_1881
david-rocca Jun 30, 2026
a61244f
Fixes date only query filkter sanitization
david-rocca Jun 30, 2026
4fdd5f7
Import Monday updates data into private conversations as part of Mond…
cberger8 Jun 30, 2026
accabaf
Merge pull request #1887 from CVEProject/dr_1881
jdaigneau5 Jun 30, 2026
e355bb9
Merge branch 'dev' into dr_1874
jdaigneau5 Jun 30, 2026
4f4637e
Merge pull request #1888 from CVEProject/dr_1874
jdaigneau5 Jun 30, 2026
b0de1dc
Merge branch 'dev' into dr_1875
jdaigneau5 Jun 30, 2026
93842d4
Merge pull request #1889 from CVEProject/dr_1875
jdaigneau5 Jun 30, 2026
31549cf
Merge branch 'dev' into dr_1880
jdaigneau5 Jun 30, 2026
4bb974a
Hardens cve id modification against unauthenticated org short-name fa…
david-rocca Jun 30, 2026
3221933
Merge pull request #1890 from CVEProject/dr_1880
jdaigneau5 Jun 30, 2026
033c009
Merge branch 'dev' into dr_1882
jdaigneau5 Jun 30, 2026
e576e17
Merge pull request #1891 from CVEProject/dr_1882
jdaigneau5 Jun 30, 2026
e3f542e
Merge branch 'dev' into dr_1877
jdaigneau5 Jun 30, 2026
9510805
limits CVE-API-USER header length
david-rocca Jun 30, 2026
41b8b2d
Merge pull request #1892 from CVEProject/dr_1877
jdaigneau5 Jun 30, 2026
92d1d75
Merge branch 'dev' into dr_1879
jdaigneau5 Jun 30, 2026
8ba8f57
Merge pull request #1893 from CVEProject/dr_1879
jdaigneau5 Jun 30, 2026
b606bc0
Merge branch 'dev' into dr_1868
jdaigneau5 Jun 30, 2026
f153801
Merge pull request #1895 from CVEProject/dr_1868
jdaigneau5 Jun 30, 2026
ae9a42e
Merge branch 'dev' into dr_1869
jdaigneau5 Jun 30, 2026
99b21e5
hardens auth context helpers so unauthenticated requests cannot use u…
david-rocca Jun 30, 2026
cbc1b23
Merge pull request #1896 from CVEProject/dr_1869
jdaigneau5 Jun 30, 2026
d524f3b
Merge branch 'dev' into dr_1870
david-rocca Jun 30, 2026
0800b18
Merge pull request #1897 from CVEProject/dr_1870
jdaigneau5 Jul 1, 2026
2ed29e0
Updating some documentation
david-rocca Jul 1, 2026
8ef34dc
lockout cps
david-rocca Jul 1, 2026
51d76e5
Merge branch 'dev' into dr_documenation_cleanup_jul_1
david-rocca Jul 1, 2026
b88aebb
Merge pull request #1898 from CVEProject/dr_documenation_cleanup_jul_1
david-rocca Jul 1, 2026
ccbca7f
Removing cps user from tests
david-rocca Jul 1, 2026
37731a9
Incrementing to v2.8.2
afoote-mitre Jul 2, 2026
4747507
Merge branch 'dev' into dr_cps_cutoff
afoote-mitre Jul 2, 2026
da0a348
Reverting unintentional package.json change
afoote-mitre Jul 2, 2026
5f97e55
Merge branch 'dev' into dr_cps_cutoff
afoote-mitre Jul 2, 2026
c5c0525
Merge pull request #1899 from CVEProject/dr_cps_cutoff
afoote-mitre Jul 2, 2026
1284e91
Merge branch 'dev' into cb_monday_updates_migration
afoote-mitre Jul 2, 2026
28e8c77
Merge pull request #1894 from CVEProject/cb_monday_updates_migration
afoote-mitre Jul 2, 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
98 changes: 49 additions & 49 deletions api-docs/openapi.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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.1",
"version": "2.8.2",
"license": "(CC0)",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
Expand Down
1 change: 1 addition & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ function getConstants () {
},
MAX_SHORTNAME_LENGTH: 32,
MIN_SHORTNAME_LENGTH: 2,
MAX_USERNAME_LENGTH: 128,
MAX_FIRSTNAME_LENGTH: 100,
MAX_LASTNAME_LENGTH: 100,
MAX_MIDDLENAME_LENGTH: 100,
Expand Down
44 changes: 27 additions & 17 deletions src/controller/audit.controller/audit.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ const validateUUID = require('uuid').validate
*/
async function createAuditDocumentForOrg (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const body = req.ctx.body
let returnValue

Expand All @@ -30,6 +27,10 @@ async function createAuditDocumentForOrg (req, res, next) {
return res.status(400).json(error.invalidUUID('target_uuid'))
}

const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const session = await mongoose.startSession()

try {
session.startTransaction()

Expand Down Expand Up @@ -117,9 +118,6 @@ async function createAuditDocumentForOrg (req, res, next) {
*/
async function appendToAuditHistoryForOrg (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const body = req.ctx.body
let returnValue

Expand All @@ -135,6 +133,15 @@ async function appendToAuditHistoryForOrg (req, res, next) {
return res.status(400).json(error.invalidUUID('target_uuid'))
}

if (!Array.isArray(body.history) || body.history.length === 0) {
logger.info({ uuid: req.ctx.uuid, message: 'Missing required field: history' })
return res.status(400).json(error.missingRequiredField('history'))
}

const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const session = await mongoose.startSession()

try {
session.startTransaction()

Expand Down Expand Up @@ -207,8 +214,8 @@ async function appendToAuditHistoryForOrg (req, res, next) {
*/
async function getAllOrgAuditDocuments (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
const session = await mongoose.startSession()
let returnValue

try {
Expand All @@ -230,8 +237,6 @@ async function getAllOrgAuditDocuments (req, res, next) {
*/
async function getOrgAuditByDocumentUUID (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
const documentUUID = req.ctx.params.document_uuid
let returnValue

Expand All @@ -245,6 +250,9 @@ async function getOrgAuditByDocumentUUID (req, res, next) {
return res.status(400).json(error.invalidUUID('document_uuid'))
}

const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()

try {
returnValue = await repo.findOneByUUID(documentUUID, { session })

Expand All @@ -269,17 +277,18 @@ async function getOrgAuditByDocumentUUID (req, res, next) {
*/
async function getOrgAuditByOrgIdentifier (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const identifier = req.ctx.params.org_identifier
const identifierIsUUID = validateUUID(identifier)
let returnValue

if (!identifier) {
return res.status(400).json(error.missingRequiredField('identifier'))
}

const identifierIsUUID = validateUUID(identifier)
const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const session = await mongoose.startSession()

try {
session.startTransaction()

Expand Down Expand Up @@ -335,11 +344,7 @@ async function getOrgAuditByOrgIdentifier (req, res, next) {
*/
async function getLastXChanges (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const identifier = req.ctx.params.org_identifier
const identifierIsUUID = validateUUID(identifier)
const numberOfChanges = parseInt(req.ctx.params.number_of_changes)
let returnValue

Expand All @@ -352,6 +357,11 @@ async function getLastXChanges (req, res, next) {
return res.status(400).json(error.invalidNumberOfChanges())
}

const identifierIsUUID = validateUUID(identifier)
const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const session = await mongoose.startSession()

try {
session.startTransaction()

Expand Down
2 changes: 1 addition & 1 deletion src/controller/audit.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ router.get('/audit/org/document/:document_uuid',
controller.AUDIT_GET_BY_UUID
)

// Get audit by org identifier (Secretariat or Admin)
// Get audit by org identifier (Secretariat only)
router.get('/audit/org/:org_identifier',
/*
#swagger.tags = ['Audit']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ async function getAllConversations (req, res, next) {

const options = CONSTANTS.PAGINATOR_OPTIONS
options.sort = { posted_at: 'desc' }
options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE

const response = await repo.getAll(options)
return res.status(200).json(response)
Expand Down Expand Up @@ -45,6 +46,7 @@ async function createConversationForTargetUUID (req, res, next) {
const user = await authContext.getRequesterUser(req, userRepo, orgRepo, { session })

if (typeof body !== 'object' || !body.body || !repo.validateConversation(body)) {
await session.abortTransaction()
return res.status(400).json(error.invalidConversationObject())
}

Expand All @@ -53,6 +55,7 @@ async function createConversationForTargetUUID (req, res, next) {
if (!isSecretariat) {
const orgUUID = await authContext.getRequesterOrgUUID(req, orgRepo, { session })
if (targetUUID !== orgUUID) {
await session.abortTransaction()
return res.status(403).json({ error: 'UNAUTHORIZED', message: 'Unauthorized' })
}
}
Expand Down Expand Up @@ -98,12 +101,14 @@ async function updateConversationByUUID (req, res, next) {
const conversation = await repo.findOneByUUID(conversationUUID, { session })
if (!conversation) {
logger.info({ uuid: req.ctx.uuid, message: `No conversation found with UUID ${conversationUUID}` })
await session.abortTransaction()
return res.status(404).json(error.conversationDne(conversationUUID))
}

// Validate body
if (typeof body !== 'object' || !(body.body || body.visibility) || !repo.validateConversation(body)) {
logger.info({ uuid: req.ctx.uuid, message: 'The conversation could not be edited because the request body was invalid.' })
await session.abortTransaction()
return res.status(400).json(error.invalidConversationEditObject())
}

Expand Down
37 changes: 37 additions & 0 deletions src/controller/conversation.controller/conversation.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const { validationResult } = require('express-validator')
const utils = require('../../utils/utils')
const errors = require('./error')
const error = new errors.ConversationControllerError()

function parseGetParams (req, res, next) {
utils.reqCtxMapping(req, 'query', ['page'])
next()
}

function parseTargetParams (req, res, next) {
utils.reqCtxMapping(req, 'params', ['uuid'])
utils.reqCtxMapping(req, 'query', ['page'])
next()
}

function parseUuidParams (req, res, next) {
utils.reqCtxMapping(req, 'params', ['uuid'])
next()
}

function parseError (req, res, next) {
const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => {
return { msg: msg, param: param, location: location }
})
if (!err.isEmpty()) {
return res.status(400).json(error.badInput(err.array()))
}
next()
}

module.exports = {
parseGetParams,
parseTargetParams,
parseUuidParams,
parseError
}
18 changes: 14 additions & 4 deletions src/controller/conversation.controller/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const router = require('express').Router()
const { param, query } = require('express-validator')
const controller = require('./conversation.controller')
const { parseGetParams, parseTargetParams, parseUuidParams, parseError } = require('./conversation.middleware')
const mw = require('../../middleware/middleware')
const getConstants = require('../../../src/constants').getConstants
const CONSTANTS = getConstants()
Expand Down Expand Up @@ -100,6 +101,8 @@ router.get('/conversation',
query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }),
query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }),
parseError,
parseGetParams,
controller.getAllConversations
)

Expand Down Expand Up @@ -199,20 +202,23 @@ router.get('/conversation/target/:uuid',
query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }),
query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }),
parseError,
parseTargetParams,
controller.getConversationsForTargetUUID
)

// Post conversation for target UUID - SEC only
// Post conversation for target UUID - Secretariat or target org admin
router.post('/conversation/target/:uuid',
/*
#swagger.tags = ['Conversation']
#swagger.operationId = 'createConversationForTargetUUID'
#swagger.summary = "Creates a conversation for a specific target UUID (accessible to Secretariat only)"
#swagger.summary = "Creates a conversation for a specific target UUID (accessible to Secretariat or target organization Admin)"
#swagger.description = "
<h2>Access Control</h2>
<p>User must belong to an organization with the <b>Secretariat</b> role</p>
<p>User must belong to an organization with the <b>Secretariat</b> role or be an <b>Admin</b> of the target organization</p>
<h2>Expected Behavior</h2>
<p><b>Secretariat:</b> Creates a conversation for the specified target UUID</p>"
<p><b>Secretariat:</b> Creates a conversation for the specified target UUID</p>
<p><b>Organization Admin:</b> Creates a conversation only when the target UUID is the admin's organization UUID</p>"
#swagger.parameters['uuid'] = { description: 'The UUID of the target entity' }
#swagger.parameters['$ref'] = [
'#/components/parameters/apiEntityHeader',
Expand Down Expand Up @@ -301,6 +307,8 @@ router.post('/conversation/target/:uuid',
mw.validateUser,
mw.onlySecretariatOrAdmin,
param(['uuid']).isUUID(4),
parseError,
parseUuidParams,
controller.createConversationForTargetUUID
)

Expand Down Expand Up @@ -410,6 +418,8 @@ router.put('/conversation/:uuid',
mw.validateUser,
mw.onlySecretariat,
param(['uuid']).isUUID(4),
parseError,
parseUuidParams,
controller.updateConversationByUUID
)

Expand Down
6 changes: 5 additions & 1 deletion src/controller/cve-id.controller/cve-id.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,14 @@ async function modifyCveId (req, res, next) {
const cveIdRepo = req.ctx.repositories.getCveIdRepository()
const userRepo = req.ctx.repositories.getUserRepository()
const cveRepo = req.ctx.repositories.getCveRepository()
if (!req.ctx.authenticated) {
return res.status(403).json(error.orgCannotReserveForOther())
}

const requesterOrgUUID = await authContext.getRequesterOrgUUID(req, orgRepo)
const org = requesterOrgUUID && typeof orgRepo.findOneByUUID === 'function'
? await orgRepo.findOneByUUID(requesterOrgUUID)
: (!req.ctx.authenticated ? await orgRepo.findOneByShortName(req.ctx.org) : null)
: null
if (!org) {
return res.status(403).json(error.orgCannotReserveForOther())
}
Expand Down
5 changes: 3 additions & 2 deletions src/controller/cve-id.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ router.get('/cve-id',
/*
#swagger.tags = ['CVE ID']
#swagger.operationId = 'cveIdGetFiltered'
#swagger.summary = "Retrieves information about CVE IDs after applying the query parameters as filters (accessible to all registered users)"
#swagger.summary = "Retrieves information about CVE IDs after applying the query parameters as filters (accessible to registered users, Secretariat, and Bulk Download)"
#swagger.description = "
<h2>Access Control</h2>
<p>All registered users can access this endpoint</p>
<p>Registered users can access this endpoint. Secretariat and Bulk Download organizations can retrieve CVE IDs across organizations; other users are limited to CVE IDs owned by their own organization.</p>
<h2>Expected Behavior</h2>
<p><b>Regular, CNA & Admin Users:</b> Retrieves filtered CVE IDs owned by the user's organization</p>
<p><b>Secretariat:</b> Retrieves filtered CVE IDs owned by any organization</p>
<p><b>Bulk Download:</b> Retrieves filtered CVE IDs owned by any organization with owner and requester details redacted</p>
#swagger.parameters['$ref'] = [
'#/components/parameters/cveIdGetFilteredState',
'#/components/parameters/cveIdGetFilteredCveIdYear',
Expand Down
16 changes: 10 additions & 6 deletions src/controller/cve.controller/cve.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,22 @@ async function getFilteredCves (req, res, next) {
const query = {}

if (timeModified.timeStamp.length > 0) {
if (!cnaModified) { query['time.modified'] = {} }
if (cnaModified) {
query['cve.containers.cna.providerMetadata.dateUpdated'] = {}
} else {
query['time.modified'] = {}
}

for (let i = 0; i < timeModified.timeStamp.length; i++) {
if (timeModified.dateOperator[i] === 'lt') {
if (cnaModified) {
query['cve.containers.cna.providerMetadata.dateUpdated'] = {}
// Due to this not being the mongo created date object, we need to actually check the "ISO String" version of this _NOT_ the date object that is being created in the middleware
query['cve.containers.cna.providerMetadata.dateUpdated'].$lt = timeModifiedLtDateObject.toISOString()
} else {
query['time.modified'].$lt = timeModified.timeStamp[i]
}
} else {
if (cnaModified) {
query['cve.containers.cna.providerMetadata.dateUpdated'] = {}
// Due to this not being the mongo created date object, we need to actually check the "ISO String" version of this _NOT_ the date object that is being created in the middleware
query['cve.containers.cna.providerMetadata.dateUpdated'].$gt = timeModifiedGtDateObject.toISOString()
} else {
Expand Down Expand Up @@ -294,20 +296,22 @@ async function getFilteredCvesCursor (req, res, next) {
const query = {}

if (timeModified.timeStamp.length > 0) {
if (!cnaModified) { query['time.modified'] = {} }
if (cnaModified) {
query['cve.containers.cna.providerMetadata.dateUpdated'] = {}
} else {
query['time.modified'] = {}
}

for (let i = 0; i < timeModified.timeStamp.length; i++) {
if (timeModified.dateOperator[i] === 'lt') {
if (cnaModified) {
query['cve.containers.cna.providerMetadata.dateUpdated'] = {}
// Due to this not being the mongo created date object, we need to actually check the "ISO String" version of this _NOT_ the date object that is being created in the middleware
query['cve.containers.cna.providerMetadata.dateUpdated'].$lt = timeModifiedLtDateObject.toISOString()
} else {
query['time.modified'].$lt = timeModified.timeStamp[i]
}
} else {
if (cnaModified) {
query['cve.containers.cna.providerMetadata.dateUpdated'] = {}
// Due to this not being the mongo created date object, we need to actually check the "ISO String" version of this _NOT_ the date object that is being created in the middleware
query['cve.containers.cna.providerMetadata.dateUpdated'].$gt = timeModifiedGtDateObject.toISOString()
} else {
Expand Down
Loading
Loading